首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
V2EX  ›  Python

Python 闭包不支持修改 upvalue,有什么替代的解决方案?

  •  
  •   tabris17 · 2016-01-19 09:20:31 +08:00 · 4554 次点击
    这是一个创建于 1417 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如下代码:

    def test_closure():
        x = 1
        def closure():
            x = 2
        closure()
        print x
    test_closure()
    

    x 仍然是 1

    第 1 条附言  ·  2016-01-19 10:13:37 +08:00
    总结一下:

    1 、通过调用栈去查找上层函数的本地变量。这个好像闭包执行时上下文环境不在申明的上下文环境就不行了
    2 、用引用类型将 upvalue 封装一下。比如: list 、 dict 、 object 。
    3 、将 upvalue 作为函数的属性
    第 2 条附言  ·  2016-01-19 10:14:27 +08:00
    漏了
    4 、 python3 的 nonlocal 关键字
    67 回复  |  直到 2016-01-19 23:03:00 +08:00
        1
    est   2016-01-19 09:22:21 +08:00   ♥ 1
    暴力遍历 call frame 修改局部变量。动态语言你值得拥有。
        2
    tabris17   2016-01-19 09:28:03 +08:00
    @est ……难道没有优雅一些的方法么?

    不能修改 upvalue 岂不是连 PHP 都不如了? Python 当自强啊
        3
    est   2016-01-19 09:31:19 +08:00
    >>> a=123
    >>> def c():
    ... sys._getframe().f_back.f_locals['a'] = 456
    ...
    ...
    >>> a
    123
    >>> c
    <function c at 0x7f69c98df050>
    >>> c()
    >>> a
    456
        4
    est   2016-01-19 09:32:39 +08:00
    如此定义个函数:

    upvalue = sys._getframe().f_back.f_locals

    你想怎么改就怎么改。改别的进程里的 php 变量都可以做到。
        5
    clino   2016-01-19 09:34:55 +08:00   ♥ 1
    在 3 有新增关键字搞定这种情况

    用 2 我面对楼主这种情况,一般用的是这样的方式

    def test_closure():
    ----class v:pass
    ----v.x = 1
    ----def closure():
    --------v.x = 2
    ----closure()
    ----print v.x
    test_closure()
        6
    zhuangzhuang1988   2016-01-19 09:36:35 +08:00   ♥ 1
    def test_closure():
    x = [1]
    def closure():
    x[0] = 2
    closure()
    print x[0]
    test_closure()
        7
    arcas   2016-01-19 09:38:01 +08:00
    python 3 nonlocal 关键字
        8
    clino   2016-01-19 09:38:34 +08:00
    另外不是"闭包不支持修改 upvalue"
    而是在
    def closure():
    ----x = 2
    这里的赋值有声明的作用,所以 python 认为这里声明了一个新的局部变量
        9
    arcas   2016-01-19 09:40:04 +08:00   ♥ 1
    def func1():
    func1.x = 100
    def func2():
    func1.x = 200
    func2()
    print func1.x
        10
    tabris17   2016-01-19 09:53:01 +08:00
    @clino 你这是语法上的解释,本质就是 python 不支持修改 upvalue
        11
    tabris17   2016-01-19 10:04:49 +08:00
    @est 你这个办法,万一上层调用已经返回了咋办?
        12
    tabris17   2016-01-19 10:05:21 +08:00
    @arcas 看来要换 3
        13
    vanxining   2016-01-19 10:05:51 +08:00 via Android
    学习了。另外, upvalue 是 Lua 的说法?
        14
    tabris17   2016-01-19 10:08:50 +08:00
    @vanxining 不是,闭包所引用的函数外部变量就称作 upvalue
        15
    kkwezard   2016-01-19 10:18:00 +08:00
    @tabris17 我怎么只在 lua 里看到过 upvalue 这种说法。
        16
    xuboying   2016-01-19 10:22:00 +08:00
    我也遇到了一样的问题

    这里有个 remedy

    https://segmentfault.com/q/1010000004211385
        17
    tabris17   2016-01-19 10:22:04 +08:00
    @kkwezard lua 的叫法比较学术呗
        18
    clino   2016-01-19 10:26:37 +08:00 via Android
    @tabris17 可是我给的代码就不是在修改 upvalue 了吗
        19
    tabris17   2016-01-19 10:27:13 +08:00
    @vanxining
    @kkwezard

    除了 lua ,其他语言说到闭包只是强调了变量的作用域。但是实际上是有个专门术语来称呼这个 inherit variables from the parent scope 的,就是 upvalue 。

    以下摘自 wiki 的闭包词条:

    在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的 函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。
        20
    tabris17   2016-01-19 10:28:53 +08:00
    @clino 你给的代码是修改 upvalue 引用的对象而已,不是修改了 upvalue 。 upvalue 本身是不可变的
        21
    est   2016-01-19 10:34:21 +08:00
    @tabris17 你给我演示一下如何返回了?
        22
    clino   2016-01-19 10:38:13 +08:00 via Android
    @tabris17 你要这么理解也行 不过明明变了但又说不可变 你的描述是很糟糕的
        23
    tabris17   2016-01-19 10:39:51 +08:00
    @est

    import sys

    def test_closure():
    ----x = 1
    ----def closure():
    --------print sys._getframe().f_back.f_locals
    ----closure()
    ----return closure

    closure = test_closure()
    closure()
        24
    tabris17   2016-01-19 10:42:20 +08:00
    @clino 变了以及不变,两个主语不同,变的是 upvalue 引用的对象,不变的是 upvalue
        25
    clino   2016-01-19 10:45:40 +08:00 via Android
    upvalue 指向的值变了情况 你说 upvalue 没变 这是很误导人的
        26
    jmc891205   2016-01-19 10:58:39 +08:00
    这是 python 作用域导致的问题
    由于 python 规定在内部函数里给变量赋值的时候会屏蔽外部函数的同名变量,所以你给的例子里, closure 里的 x 已经不是对 test_closure 里的 x 的引用了。在我看来,这个例子中的 closure 并不是一个闭包。
        27
    clino   2016-01-19 11:06:34 +08:00
    @jmc891205 不是作用域 不信你只打印不赋值看看
        28
    clino   2016-01-19 11:08:46 +08:00
    @jmc891205 没仔细看你已经说了赋值的情况了
        29
    est   2016-01-19 11:11:05 +08:00
    @tabris17 呃,我再想一下,其他语言如果一个函数都返回了,你还能修改这函数里面的局部变量?
        30
    jmc891205   2016-01-19 11:11:26 +08:00
    @clino 不赋值的话外层函数的变量就不会被屏蔽咯 这也是 python 对变量作用域的规定吧
        31
    tabris17   2016-01-19 11:12:00 +08:00
    @est 是啊。 Javascript 、 PHP (通过引用实现)、 Lua 都支持
        32
    jmc891205   2016-01-19 11:12:23 +08:00
    @clino 这。。。麻烦你下次回别人帖子的时候 先把别人的帖子看完
        33
    Mark3K   2016-01-19 11:13:39 +08:00
    我觉得闭包挺好用的,但是不应该这样使用, 我一般只在闭包里面使用外层作用域的变量,如果你需要改变外层作用域的变量,应该使用 x=closure()的方式。
        34
    tabris17   2016-01-19 11:17:52 +08:00
    @est

    function test_closure() {
    var x = 1;
    function closure1() {
    x = 2;
    }
    function closure2() {
    console.log(x);
    }
    return [closure1, closure2];
    }
    closures = test_closure();
    (closures[0])();
    (closures[1])();
        35
    ethego   2016-01-19 11:20:01 +08:00
    ```python
    def test_closure():
    test_closure.x = 1
    def closure():
    test_closure.x = 2
    closure()
    print x
    test_closure()
    ```
    你再试试
        36
    nooper   2016-01-19 11:20:18 +08:00
    python3.
        37
    ethego   2016-01-19 11:21:19 +08:00
    因为 python 的闭包确实不是完整的闭包,变量的查找规则不依赖于闭包。
        38
    xuboying   2016-01-19 11:26:22 +08:00
    @ethego
    这样写是错的,如下例子,结果是 2 4 6 6 6 6 ,希望的结果是 2 4 6 6 4 2
    >def outer(s):
    > if s == 4:
    > return
    > outer.OuterVar = s
    > def inner():
    > outer.OuterVar = outer.OuterVar * 2
    > inner()
    > print outer.OuterVar ,
    > outer(s+1)
    > print outer.OuterVar ,
    >if __name__ == "__main__":
    > outer(1)

    正确的方法是用一个类来封装
    >class Namespace: pass
    >
    >def outer(s):
    > if s == 4:
    > return
    > ns = Namespace()
    > ns.OuterVar = s
    >
    > def inner():
    > ns.OuterVar = ns.OuterVar * 2
    > inner()
    >
    > print ns.OuterVar,
    >
    > outer(s+1)
    >
    > print ns.OuterVar,
    >if __name__ == "__main__":
    > outer(1)
        39
    xuboying   2016-01-19 11:28:18 +08:00
    重新排版
    @ethego
    这样写是错的,如下例子,结果是 2 4 6 6 6 6 ,希望的结果是 2 4 6 6 4 2

    def outer(s):
       if s == 4:
         return
       outer.OuterVar = s
       def inner():
         outer.OuterVar = outer.OuterVar * 2
       inner()
       print outer.OuterVar ,
       outer(s+1)
       print outer.OuterVar ,
    if __name__ == "__main__":
       outer(1)

    正确的方法是用一个类来封装

    class Namespace: pass

    def outer(s):
       if s == 4:
         return
       ns = Namespace()
       ns.OuterVar = s

       def inner():
         ns.OuterVar = ns.OuterVar * 2
       inner()

       print ns.OuterVar,

       outer(s+1)

       print ns.OuterVar,
    if __name__ == "__main__":
       outer(1)
        40
    ethego   2016-01-19 11:30:48 +08:00
    @xuboying
    ```python
    def test_closure():
    ----def closure():
    --------closure.x = 2
    ----closure.x = 1
    ----closure()
    ----print x

    test_closure()
    ```
    你再试试
        41
    ethego   2016-01-19 11:32:33 +08:00
    @xuboying
    ```python
    def test_closure():
    ----def closure():
    --------closure.x = 2
    ----closure.x = 1
    ----closure()
    ----print b.x

    test_closure()
    ```
    print 有个小错误,改正了,你再试试
        42
    est   2016-01-19 11:34:44 +08:00
    @tabris17 服。
        43
    ethego   2016-01-19 11:40:35 +08:00
    @xuboying 不需要用一个类来封装,闭包函数也是动态创建的,函数也是对象
        44
    ethego   2016-01-19 11:47:12 +08:00
    支持完整的闭包和不支持都没什么影响,能完成的功能一样可以完成,只是你心里舒服一些罢了,有人说 python 不纯,不是纯的函数式编程,卧槽,失望了,还不如 php 。我想说,你语言层面再纯,哪怕是 haskell 那样纯洁的小天使,编译成汇编机器码,一样是面向过程的,你追求的纯净的函数式编程只是镜花水月空中楼阁罢了。
        45
    BlackKey   2016-01-19 11:49:21 +08:00   ♥ 1
    PEP 227 里面解释了这个, Rebinding names in enclosing scopes 部分
    简单来说就是他们不愿意(懒得)搞这个
        46
    xuboying   2016-01-19 11:52:59 +08:00
    @ethego b.x 什么鬼?
    print b.x
    NameError: global name 'b' is not defined
        47
    ethego   2016-01-19 11:53:53 +08:00   ♥ 1
    @xuboying
    ```python
    def test_closure():
    ----def closure():
    --------closure.x = 2
    ----closure.x = 1
    ----closure()
    ----print closure.x

    test_closure()
    ```
    这里就一个 x ,你还不知道用哪个?
        48
    xuboying   2016-01-19 11:57:01 +08:00
    @BlackKey 这个功能还真的是挺重要的,对于喜欢写递归的人来说,递归内的函数可以正确访问父函数的是保证逻辑正确的前提。 perl , js 都能很好的实现
    如果因为不能处理好递归而被人要求去专用其他语言真的是和很伤心的事情
    不过还好还是有 workaround 了
        49
    xuboying   2016-01-19 12:07:52 +08:00
    @ethego 你这个写法也是对的!!!但是必须把函数放在最开头,而且这个变量从父函数变量变成子函数变量了,我不知道这样做是否妥当(感觉上逻辑变了),也不知道能不能解决需要访问父父函数变量的情况(虽然我从来没有这么尝试过)

    def outer(s):
       if s == 4:
         return
       def inner():
         inner.x = inner.x * 2
       inner.x = s
       inner()
       print inner.x,
       outer(s+1)
       print inner.x,
    if __name__ == "__main__":
       outer(1)


    result: 2 4 6 6 4 2
        50
    BlackKey   2016-01-19 12:31:37 +08:00
    @xuboying 我挺喜欢 Python 的,但有一点有时还是觉得挺烦的。他们经常喜欢让你接受他们认为好的方式,比如他们不提倡让你用匿名函数,结果 Python 匿名函数的就一直特别弱
        51
    xuboying   2016-01-19 12:36:43 +08:00
    @BlackKey 众所周知, Perl 和 Python 的文化在很多方面是不同的。 PEP20 中提到了其中一点, There should be one-- and preferably only one --obvious way to do it.

    然而却弄出了 Python 2 和 Python 3
        52
    clino   2016-01-19 12:41:23 +08:00
    @jmc891205 我的理解不是屏蔽,而是认为声明了一个新的局部变量导致访问不到外面的那一个同名的
        53
    shyling   2016-01-19 13:13:28 +08:00 via iPad
    函数式还修改外部变量?函数自己的返回值呢?
        54
    ethego   2016-01-19 13:15:48 +08:00
    @xuboying 正因为只选择一种最好的方式,所以才要彻底割裂 python 2 不好的地方。
        55
    musicx   2016-01-19 13:22:46 +08:00
    我其实好奇是什么样的场景需要修改闭包外的值。。。而且要求不能用引用不能用将闭包返回值赋给原变量这两种方法。。。
        56
    tabris17   2016-01-19 13:35:09 +08:00
    @shyling
    @musicx

    可以试试接触下 javascript ,回过头来你会发现不能修改 upvalue 才是很奇怪的设定
        57
    jmc891205   2016-01-19 13:36:56 +08:00
    @clino 我所说的“屏蔽”就是你说的这个意思
        58
    xuboying   2016-01-19 14:20:40 +08:00
    @ethego 如果 python 有这个自信,那么鉴别它做的好坏的方法是是否其他语言学习了他的精华,当你 porting python 到其他语言的时候没有很大的麻烦;如果大家都不像他,他也只能孤芳自赏了。
        59
    ethego   2016-01-19 14:33:31 +08:00
    @xuboying 然而 javascript 越来越像 python 也是事实,看看 ecmas6 的写法就知道了
        60
    fy   2016-01-19 14:44:27 +08:00
    我刚想说 nolocal
        61
    alsotang   2016-01-19 14:47:22 +08:00
    2.x 确实不支持 nonlocal ,还是用方法 2 吧。用个 dict 之类的,修改里面的属性。反正这个变量也是在函数内部用的, return 出去的时候该返回什么还是返回什么。
        62
    xuboying   2016-01-19 15:07:54 +08:00
    @ethego 我倒不是 Python 黑,相反很多时候用 python ,只是更赞同 TMTOWTDI , PEP20 那句让人觉得小家子气。 python 能被别的语言认同当然也是好事。
        63
    ethego   2016-01-19 15:24:06 +08:00   ♥ 1
    @xuboying 追求理论的和谐可以试试 haskell 或者 ruby
        64
    fy   2016-01-19 15:42:53 +08:00
    2 被续命了太久,早应迁移了。 3 解决了很多实际问题,但 3.0-3.2 都不是太理想, 3.3 以后就很顺了。
        65
    shyling   2016-01-19 15:43:15 +08:00
    @tabris17 实际上接触 javascript 更多一点。。

    var x =1;
    x=2;
    es6 有了块作用域是不是要再加个 let x=3;?
    语法是为了简化代码,让代码更易读的。
        66
    tabris17   2016-01-19 16:15:58 +08:00
    @shyling 块作用域解决了闭包的这个问题

    for (var i=0; i< 4;i++){
    setTimeout(function(){console.log(i);},1);
    }

    for (let i=0; i< 4;i++){
    setTimeout(function(){console.log(i);},1);
    }
        67
    kaneg   2016-01-19 23:03:00 +08:00
    Java 也类似。 匿名类如果要引用外部类的变量,该外部变量需要声明为 final 的。所以一个变通的办法就是创建一个数组,在匿名类中修改数组中的元素已达到修改外部变量的目的。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1187 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 29ms · UTC 18:05 · PVG 02:05 · LAX 10:05 · JFK 13:05
    ♥ Do have faith in what you're doing.