- 浏览: 125377 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
anicething:
没看到图啊
WSGI初探 -
powertech:
研究的挺深
WSGI初探 -
banxi1988:
wsgi的设计确实参考了servlet.见:http://ww ...
WSGI初探 -
luoboiqingcai:
好文
WSGI初探 -
Arbow:
实测scala没有这个问题:)scala> val li ...
Python闭包再研究
其实很早以前就想写这么一篇文章了。一直没有机会。正好今天和同事讨论Python闭包的问题,趁着没遗忘赶快记录下来。以下代码运行的Python版本是2.5。
问题还是那个很经典的问题:如下代码会抛一个错误
def foo(): a = 1 def bar(): a = a + 1 bar() print a
错误则是:
UnboundLocalError: local variable 'a' referenced before assignment
原因分析,直接上dis模块解析bar的汇编代码。得到以下结果:
12 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_FAST 0 (a) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
可以看到,造成这个异常的结果是LOAD_FAST没有找到local变量。STORE_FAST语句的作用是绑定一个local变量。那么在储存变量之前就先去读,当然是会报错了。可是,明明是a = a + 1。而按照赋值语句先执行右边的规律来看,他应该先去外层的a那里读取值,然后再新建一个local的名字a,把值赋给local的a啊?
原因暂且放下,先看一段能正常执行的代码。
把前面代码中的a = a + 1改成b = a + 1。反汇编得到以下代码。
13 0 LOAD_DEREF 0 (a) 3 LOAD_CONST 1 (1) 6 BINARY_ADD 7 STORE_FAST 0 (b) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
果然按照原来设想的一样,a在这个地方变成了LOAD_DEREF,变成了访问外围的值,然后和1想加以后,储存在一个本地的变量b里面。
正确的程序和错误的程序的差别就是,错误的里面,a是赋值语句的左边。
这看起来不经心的一个差别,会不会是原因呢?答案是YES!看python的PEP227中的一段话。
name within the block are treated as references to the current
block.
这句话非常拗口。我换一种通俗的方式来解释一下。模拟一下python编译器的行为。首先编译器看到了a = a + 1这句话,发现这是一个赋值语句。先检查右边,遇到了一个名字叫做a的东西。a是什么?编译器问自己。会不会是一个局部变量?于是编译器就傻傻的找到规则,规则表说:如果一个名字出现在参数声明,赋值语句(左边),函数声明,类声明,import语句,for语句和except语句中,这就是一个局部变量。ok。编译器从头到尾一看,a就在一个赋值语句的左边,那么a是一个局部变量没跑了。于是生成一条记录LOAD_FAST 0。你是局部变量,让你运行快一点。接着,分析完右边分析左边,赋值语句左边一定是一个局部变量,简单,你就在0号位置把,直接生成STORE_FAST 0,把栈顶的值给你。编译器顺利的编译结束。下面轮到虚拟机运行了。虚拟运行到这个语句就犯糊涂了,叫我LOAD_FAST 0。可是0里面什么东西都没有啊。我擦勒。只好报错了。
而第二段代码为什么能够正确执行呢?其实就是因为,编译器在整个代码块里面没有发现有绑定名字给a,也没有发现a是一个global对象,所以,就生成一个LOAD_DEREF 语句,告诉虚拟机,a不在这个里面。到别的地方去找他。
那么这个别的地方究竟是什么地方呢?如果python没有这个一定是局部变量的规则,是不是就能修改了呢?
我们继续分析。
先找到LOAD_DEREF的定义是什么?查看dis这个模块的说明,里面有如下的文字:
Loads the cell contained in slot i of the cell and free variable storage. Pushes a reference to the object the cell contains on the stack.
大意就是,加载cell[i]到栈顶。cell是一个什么?这时候,联想到Python的CodeObject里面有一个属性叫做co_cellvars.会不会和这个有关?
查了文档以后发现如下定义:
被嵌套的函数引用的局部变量?好奇特的说法啊。真这么神奇?执行下列代码。
def foo(): a = 1 def bar(): b = a + 1 print 'bar cellvars:', bar.func_code.co_cellvars foo() print 'foo cellvars:', foo.func_code.co_cellvars
执行结果是:
bar cellvars: () foo cellvars: ('a',)
还真是的,a在bar中引用了,所以被加入到cellvars里面。需要注意的是,他这里只是把名字放到了cellvar中,也就是说,这个闭包中的对象,依然只是一个引用而已。当这个bar调用的时候,是会顺着引用找到真正的值的。而如果真正的值被修改,在所有的bar里面都会体现。
这个过程是怎么加入的呢?反汇编一下foo的代码:
2 0 LOAD_CONST 1 (1) 3 STORE_DEREF 0 (a) 3 6 LOAD_CLOSURE 0 (a) 9 BUILD_TUPLE 1 12 LOAD_CONST 2 (<code object bar at 0x48f458, file "test.py", line 3>) 15 MAKE_CLOSURE 0 18 STORE_FAST 0 (bar) 21 LOAD_CONST 0 (None) 24 RETURN_VALUE
看到奇特的STORE_DEREF, LOAD_CLOSURE, MAKE_CLOSURE指令。
这三个指令的作用分别如下:
Stores TOS into the cell contained in slot i of the cell and free variable storage.
Pushes a reference to the cell contained in slot i of the cell and free variable storage. The name of the variable is co_cellvars[i] if i is less than the length of co_cellvars. Otherwise it is co_freevars[i - len(co_cellvars)].
Creates a new function object, sets its func_closure slot, and pushes it on the stack. TOS is the code associated with the function, TOS1 the tuple containing cells for the closure’s free variables. The function also has argc default parameters, which are found below the cells.
看来是编译器发现foo函数里面有一个嵌套的bar函数以后,就把在bar中引用的局部变量a放到一个cell当中,然后将所有的对象都生成成一个tuple,赋值给bar这个funcobject的func_closure。
为了查看神奇的效果,写下面一段代码运行一下看看:
def foo(): a = 1 def bar(): b = a + 1 return bar b = foo() print 'bar func_closure:', b.func_closure
如果这程序按照猜测的结果运行,那么将会返回一个cell的tuple。执行结果如下。
bar func_closure: (<cell at 0x454690: int object at 0x803388>,)
果然不出所料。那么func_closure的作用在文档里面怎么描述呢?
看来这个东东涉及到的是Python的名字查找顺序的问题。先local,再闭包,再global。
详细内容可以参看PEP227里面有这么一句话。
names in code objects. A variable can be either a cell variable
or a free variable for a particular code object. A cell variable
is referenced by containing scopes; as a result, the function
where it is defined must allocate separate storage for it on each
invocation. A free variable is referenced via a function's
closure.
The choice of free closures was made based on three factors.
First, nested functions are presumed to be used infrequently,
deeply nested (several levels of nesting) still less frequently.
Second, lookup of names in a nested scope should be fast.
Third, the use of nested scopes, particularly where a function
that access an enclosing scope is returned, should not prevent
unreferenced objects from being reclaimed by the garbage
collector.
相信看到前面func_closure是readonly,大家一定非常失望。看看别的语言的实现如何。
javascript的版本1。
function foo(){ var num = 1; function bar(){ var num = num + 1; alert(num); } bar() } foo();
这个版本会报NaN。。说明Python的问题Javascipt也有。
那如果说num不声明为var呢?
function foo(){ var num = 1; function bar(){ num = num + 1; alert(num); } bar() } foo();
正确提示2.。
要是Python也有这样的机制好了。。
令人高兴的是,python3里面终于改观了。从语法到底层全都支持了(貌似是一个性质)。
语法上加上了nonlocal关键字。
def foo(): a = 1 def bar(): nonlocal a a = a + 1 print(a) return bar foo()()
正确返回2!!
底层加上了可爱的下面两个函数。
PyObject* PyFunction_GetClosure(PyObject *op)¶ Return value: Borrowed reference. Return the closure associated with the function object op. This can be NULL or a tuple of cell objects. int PyFunction_SetClosure(PyObject *op, PyObject *closure) Set the closure associated with the function object op. closure must be Py_None or a tuple of cell objects. Raises SystemError and returns -1 on failure.
终于可以操作闭包了。哈哈哈哈。。
其实说到最后,如果python中有种机制能支持匿名代码块就好了。嘿嘿。到此结束。
评论
python 3 的实现方式不知道是怎样的, 不过看这样子用起来够呛的.
我觉得也许允许内部变量覆盖外部变量本身就是个大错误...
这难道不允许内部变量和外部重名?这个难度有点大了把。
这个可以有, 符号表略做点手脚就行了.
python 3 的实现方式不知道是怎样的, 不过看这样子用起来够呛的.
我觉得也许允许内部变量覆盖外部变量本身就是个大错误...
那个语言不是内部作用域覆盖外部作用域的啊...
我觉得根源是python不能区分赋值语句和声明语句,如果能区分这点,python就能很好区分内部变量和外部变量,就不存在所谓的“覆盖”问题吧
python 3 的实现方式不知道是怎样的, 不过看这样子用起来够呛的.
我觉得也许允许内部变量覆盖外部变量本身就是个大错误...
那个语言不是内部作用域覆盖外部作用域的啊...
python 3 的实现方式不知道是怎样的, 不过看这样子用起来够呛的.
我觉得也许允许内部变量覆盖外部变量本身就是个大错误...
这难道不允许内部变量和外部重名?这个难度有点大了把。
python 3 的实现方式不知道是怎样的, 不过看这样子用起来够呛的.
我觉得也许允许内部变量覆盖外部变量本身就是个大错误...
发表评论
-
WSGI初探
2010-10-31 23:59 40982wsgi初探 前言 本文不涉及WSGI的具体协议的介绍,也不 ... -
Python闭包再研究
2010-10-25 20:18 3357前两天写了一篇文章,讲了一下Python的闭包。刚好今天又看到 ... -
Python源代码中的编码声明字符串的作用
2010-10-15 18:57 3776原来以为Python编源代码头部的#encoding: utf ... -
MacOSX下Python2.5版本的locale的编码问题
2010-09-29 01:27 3027今天更新mercurial的时候遇到了一个问题。 执行hg, ... -
Python中globals对象的回收顺序分析
2010-08-24 17:50 6182先提示,本文需要一定的python源码基础。许多内容请参考《p ... -
如何输出错误信息到日志文件
2010-08-18 18:53 3610今天在做分析http错误请求处理的时候遇到一个问题:当发生50 ... -
MacOSX 10.6下安装python库不成功的总结
2009-12-13 23:00 1453最近装pycurl或者lxml的时候总是遇到问题。经过伟大的g ... -
Django在MAC下面的Bug
2009-07-18 16:12 1396首先感谢 http://code.djangoproject ... -
优化Python代码有感
2009-03-24 15:19 3426最近信息安全的老师布置了作业。要求实现DES算法。。写了1天, ... -
在GAE上面部署web2py应用小结总结
2009-03-16 22:59 3029首先感谢Limodou大大写了这篇文章指导。 http://b ... -
Django的Middleware开发有感。。。
2009-02-26 20:04 3367Django应该算 ... -
在googlecode上面建立了自己的代码库
2009-02-26 19:12 1298在googlecode上面建立了自己的代码库。。。。 地址是 ... -
今天是个值得纪念的日子
2008-06-22 23:28 1314第一个运行在java架构下的用jython写的servlet成 ... -
The Zen of Python
2008-06-12 22:35 1353The Zen of Python, by Tim Peter ...
相关推荐
第6章和第7章深入介绍了高级Python编程概念,如迭代器、闭包、装饰器、生成器。对异常处理的良好和深入的了解,使我们能够编写出可靠和健壮的代码。为了满足这一需要,第8章介绍了Python中异常处理的突出特点。第9章...
Python + NumPy程序的可组合转换:区分,矢量化,JIT到GPU / TPU,以及更多JAX:Autograd和XLA 转型| 安装指南| 神经网络库| 变更记录| 参考文档| ...它可以通过循环,分支,递归和闭包来区分,并且可以用d
它可以通过循环,分支,递归和闭包来区分,并且可以采用派生类的派生类。 它支持通过反向模式区分(aka反向传播)以及正向模式区分,并且两者可以任意组合为任意顺序。 新功能是JAX使用在GPU和TPU上编译和运行您的...
计算机视觉和模式识别(CVPR)的研究,2006年6月,纽约 该存储库还包含在Levin,Anat,Dani Lischinski和Yair Weiss中提出的背景/前景重建方法的实现。 “自然图像抠像的封闭形式解决方案。” IEEE Transactions on ...
JAX 是 Autograd 和 XLA,结合起来用于高性能机器学习研究。 借助 Autograd 的更新版本,JAX 可以自动区分原生 Python 和 NumPy 函数。 它可以通过循环、分支、递归和闭包进行微分,并且可以取导数的导数的导数。...
对TBC特征矩阵进行平移极差变换,利用指数切比雪夫距离法构建了模糊相似矩阵,采用模糊聚类中的传递闭包法构建进化树。该方法不需要多序列比对,计算简单。对两组基因组序列构建进化树,实验结果验证了该方法的有效...
是一个序列建模工具包,允许研究人员和开发人员为翻译、摘要、语言建模和其他文本生成任务训练自定义模型。 ) [ - 预测知识图中概念之间的链接的开源 Python 库。) [(PBG)是一个分布式系统,用于学习大型图的图...
我们研究了双字母组语言的后缀替换闭包。 2021年2月12日星期五 我们讨论了双语法例和语言。 我们讨论了精确度和冗余度的数学符号。 对于星期一,请阅读第4单元的最高2.3节(最高64页) 我们将在星期一讨论SL2...
支持,据我当前的研究,不同线程必须有各自的runtime对象,每个runtime可以有多个环境(context),同一个runtime下的多个环境可以自由交换数据,但环境之间不共享数据。也就是说api级别可以把环境1的数据带到环境2...