`
lin_llx
  • 浏览: 125369 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Python闭包再研究

阅读更多

前两天写了一篇文章,讲了一下Python的闭包。刚好今天又看到一个小问题,和Python闭包有点相关。顺手记录下来。

 

如下一段代码,

 

 

funcs = []
for i in xrange(10):
    def bar(n):
        return n + i
    funcs.append(bar)

print funcs[3](5)

 

这段代码中,我们期望得到的结果是3+5为8。但是实际得到的结果是什么呢?是14。

 

14是怎么来的?

 

反汇编看看:

 

 

  7           0 LOAD_FAST                0 (n)
              3 LOAD_GLOBAL              0 (i)
              6 BINARY_ADD
              7 RETURN_VALUE  

 

注意i是global。

 

得到14的原因就是,funcs[3]这个函数对象获取i的值,是在执行的时候。而i的作用域是global。也就是说,当这个func开始执行的时候,i已经变成9了。那么结果当然等于14了。

 

从这个结果看,以上代码和下面代码效果是等价的。

 

 

funcs = []
for i in xrange(10):
    pass

def bar(n):
    return n + i

funcs.append(bar)#这句重复10遍
print funcs[3](5)

 

很无趣吧。那么考虑一下,如果把i丢到闭包来做会怎样?

 

 

funcs = []
def foo(m):
    for i in xrange(m):
        def bar(n):
            return n + i
        funcs.append(bar)
foo(10)
print funcs[3](5)

 

很遗憾,结果依然是14. 

 

反汇编代码如下:

 

 

  9           0 LOAD_FAST                0 (n)
              3 LOAD_DEREF               0 (i)
              6 BINARY_ADD
              7 RETURN_VALUE 

 

唉,只是傻傻的远程访问而已。

 

“所有的bar代码中,i仅仅只是在closure中的一个引用而已。指向的依然是同一个对象。当这个对象被改变,所有的bar执行的时候获得的值都是修改后的值”。

 

顺手写了一段JavaScript来测试,发现结果是一样的。也是会全局改变。具体代码如下:

 

 

但是用haskell实现了一个,完全符合预期的结果。

 

 

main = do
    let funcs = [(\n -> n + i) | i <- [1..10] ]
    let x : xs = funcs
    return (x 4)
 

返回结果是5。

 

看来Python对FP的支持还是比不上Haskell这种正统的函数式语言。

 

个人觉得如果Python要发展FP的话,可以考虑如下解决方案:区别本地变量和闭包变量。当声明函数的时候将闭包变量拷贝一份到本地,同时保留指向原闭包对象的引用。在搜索名字的时候,始终都是先搜索本地变量,再搜索本地闭包变量副本,再搜索全局变量。当需要修改的时候,如果是本地变量就直接修改。如果是闭包变量的时候,将原来闭包真正指向的对象进行修改,同时覆盖掉本地的闭包副本。

 

这个方法只是暂时的考虑,没有仔细的推敲。乍看之下似乎可以解决问题。

 

不过,Python毕竟是OO的语言。没有Haskell或者Lisp那种天生的作用域的控制能力。唉。那就用OO的方式来搞Python把。

 

1
1
分享到:
评论
7 楼 Arbow 2011-09-06  
实测scala没有这个问题:)

scala> val list = scala.collection.mutable.ArrayBuffer[Int=>Int]()
list: scala.collection.mutable.ArrayBuffer[(Int) => Int] = ArrayBuffer()

scala> Range(0,11).foreach( i => list.append( (n:Int) => n+i ) )

scala> list(3)(5)
res20: Int = 8

scala> list(5)(5)
res21: Int = 10
6 楼 lin_llx 2011-03-03  
QLeelulu 写道
funcs = []

def bar(i):
    return lambda n: n+i

for i in xrange(10):
    funcs.append(bar(i))

print funcs[3](5)

呵呵,你这个写法是可以达到目的,不过你的和我说的有区别,因为在bar里面,i已经不是指向闭包中的变量了,而是单纯的local变量了。
5 楼 QLeelulu 2011-03-02  
funcs = []

def bar(i):
    return lambda n: n+i

for i in xrange(10):
    funcs.append(bar(i))

print funcs[3](5)
4 楼 lin_llx 2010-10-31  
ouchxp 写道
Python本就不是专注于FP的语言.包括Scala等语言在FP应用上都有或多或少的不足.
真正能成为主流项目开发的FP语言目前还寥寥无几.

PS:F#也不错可以看看


能让我感兴趣的FP语言也只剩下Lisp,Haskell和erlang了。f#的话,我倒是更加欣赏ocaml啊。。
3 楼 ouchxp 2010-10-30  
Python本就不是专注于FP的语言.包括Scala等语言在FP应用上都有或多或少的不足.
真正能成为主流项目开发的FP语言目前还寥寥无几.

PS:F#也不错可以看看
2 楼 lin_llx 2010-10-26  
whitesock 写道
funcs = []
for i in xrange(10):
    def bar(n, i=i):
        return n + i
    funcs.append(bar)
print funcs[3](5)


这个我知道。不过这种奇技淫巧只能当作是hack啊。如果当你真正想改变闭包变量的时候就不行了。
1 楼 whitesock 2010-10-26  
funcs = []
for i in xrange(10):
    def bar(n, i=i):
        return n + i
    funcs.append(bar)
print funcs[3](5)

相关推荐

    谷歌COLAB中的Python概念化

    第6章和第7章深入介绍了高级Python编程概念,如迭代器、闭包、装饰器、生成器。对异常处理的良好和深入的了解,使我们能够编写出可靠和健壮的代码。为了满足这一需要,第8章介绍了Python中异常处理的突出特点。第9章...

    JAX是Autograd和XLA的结合,用于高性能机器学习研究。-Python开发

    Python + NumPy程序的可组合转换:区分,矢量化,JIT到GPU / TPU,以及更多JAX:Autograd和XLA 转型| 安装指南| 神经网络库| 变更记录| 参考文档| ...它可以通过循环,分支,递归和闭包来区分,并且可以用d

    jax:Python + NumPy程序的可组合转换:区分,向量化,JIT到GPUTPU等

    它可以通过循环,分支,递归和闭包来区分,并且可以采用派生类的派生类。 它支持通过反向模式区分(aka反向传播)以及正向模式区分,并且两者可以任意组合为任意顺序。 新功能是JAX使用在GPU和TPU上编译和运行您的...

    matlab把代码导入-closed-form-matting:A.LevinD.Lischinski和Y.Weiss的Python实现。自然

    计算机视觉和模式识别(CVPR)的研究,2006年6月,纽约 该存储库还包含在Levin,Anat,Dani Lischinski和Yair Weiss中提出的背景/前景重建方法的实现。 “自然图像抠像的封闭形式解决方案。” IEEE Transactions on ...

    JAX:支持GPU/TPU的NumPy包(Autograd/XLA),面向高性能机器学习-python

    JAX 是 Autograd 和 XLA,结合起来用于高性能机器学习研究。 借助 Autograd 的更新版本,JAX 可以自动区分原生 Python 和 NumPy 函数。 它可以通过循环、分支、递归和闭包进行微分,并且可以取导数的导数的导数。...

    论文研究-一种新的关联特征和模糊聚类的进化树构建方法.pdf

    对TBC特征矩阵进行平移极差变换,利用指数切比雪夫距离法构建了模糊相似矩阵,采用模糊聚类中的传递闭包法构建进化树。该方法不需要多序列比对,计算简单。对两组基因组序列构建进化树,实验结果验证了该方法的有效...

    程序员面试刷题的书哪个好-awesome-links:很棒的链接

    是一个序列建模工具包,允许研究人员和开发人员为翻译、摘要、语言建模和其他文本生成任务训练自定义模型。 ) [ - 预测知识图中概念之间的链接的开源 Python 库。) [(PBG)是一个分布式系统,用于学习大型图的图...

    主要的

    我们研究了双字母组语言的后缀替换闭包。 2021年2月12日星期五 我们讨论了双语法例和语言。 我们讨论了精确度和冗余度的数学符号。 对于星期一,请阅读第4单元的最高2.3节(最高64页) 我们将在星期一讨论SL2...

    微软开源 JavaScript 引擎 ChakraCore-易语言

    支持,据我当前的研究,不同线程必须有各自的runtime对象,每个runtime可以有多个环境(context),同一个runtime下的多个环境可以自由交换数据,但环境之间不共享数据。也就是说api级别可以把环境1的数据带到环境2...

Global site tag (gtag.js) - Google Analytics