如下代码:
def test_closure():
x = 1
def closure():
x = 2
closure()
print x
test_closure()
x 仍然是 1
1
est 2016-01-19 09:22:21 +08:00 1
暴力遍历 call frame 修改局部变量。动态语言你值得拥有。
|
2
tabris17 OP |
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 |
13
vanxining 2016-01-19 10:05:51 +08:00 via Android
学习了。另外, upvalue 是 Lua 的说法?
|
16
xuboying 2016-01-19 10:22:00 +08:00
|
19
tabris17 OP |
20
tabris17 OP @clino 你给的代码是修改 upvalue 引用的对象而已,不是修改了 upvalue 。 upvalue 本身是不可变的
|
23
tabris17 OP @est
import sys def test_closure(): ----x = 1 ----def closure(): --------print sys._getframe().f_back.f_locals ----closure() ----return closure closure = test_closure() closure() |
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 并不是一个闭包。 |
33
Mark3K 2016-01-19 11:13:39 +08:00
我觉得闭包挺好用的,但是不应该这样使用, 我一般只在闭包里面使用外层作用域的变量,如果你需要改变外层作用域的变量,应该使用 x=closure()的方式。
|
34
tabris17 OP @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 有个小错误,改正了,你再试试 |
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
|
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 |
53
shyling 2016-01-19 13:13:28 +08:00 via iPad
函数式还修改外部变量?函数自己的返回值呢?
|
55
musicx 2016-01-19 13:22:46 +08:00
我其实好奇是什么样的场景需要修改闭包外的值。。。而且要求不能用引用不能用将闭包返回值赋给原变量这两种方法。。。
|
56
tabris17 OP |
58
xuboying 2016-01-19 14:20:40 +08:00
@ethego 如果 python 有这个自信,那么鉴别它做的好坏的方法是是否其他语言学习了他的精华,当你 porting python 到其他语言的时候没有很大的麻烦;如果大家都不像他,他也只能孤芳自赏了。
|
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 能被别的语言认同当然也是好事。
|
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
|
66
tabris17 OP @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 的。所以一个变通的办法就是创建一个数组,在匿名类中修改数组中的元素已达到修改外部变量的目的。
|