pygame实现文本转图片
用 PIL 制作 ASCII Art

python的闭包和装饰器

scturtle posted @ 2011年12月06日 21:02 in python , 4399 阅读
  • 闭包(closure)
>>> def F(name):
	def f():
		print name
	return f

>>> f1=F('f1')
>>> f2=F('f2')
>>> f1()
f1
>>> f2()
f2

F 是一个返回函数的函数。对于 F 产生的函数 f1、f2,其依赖于F的本地变量 name,但是在F执行结束后,函数 f1、f2 依然可以访问 name,而且相互独立。就像在返回内层函数时,把产生它的环境一块儿打包返回了,这种语法现象叫闭包(closure)。

在《Programming in Lua 2nd》里展示了闭包的各种绚丽的用法,感觉 lua 把闭包和元编程用到极致了。即使不打算用 lua,也强烈推荐读一读,对了解 python 和其他动态语言的本质和实现或有帮助。

在 lua 中有这样一种写法:

>> function gen_ins(init) 
..   local i=init-1 
..   return function() i=i+1; return i end 
.. end
>> ins5=gen_ins(5)
>> ins2=gen_ins(2)
>> ins5()
=> 5
>> ins2()
=> 2

但是这在 python 中却行不通:

>>> def gen_ins(init):
	i=init-1
	def ins():
		i=i+1
		return i
	return ins

>>> ins0=gen_ins(0)
>>> ins0()

Traceback (most recent call last):
  File "<pyshell#111>", line 1, in <module>
    ins0()
  File "<pyshell#108>", line 4, in ins
    i=i+1
UnboundLocalError: local variable 'i' referenced before assignment

这就是所谓的 python 不支持真闭包,python 中对外层变量不能赋值。一般在 python 的函数中,变量的查找顺序是局部符号表,全局符号表,内置符号表。我们知道,全局变量可以被引用,但是要赋值的话,必须用 global 声明一下,否则其实是新建了一个本地变量。从python解释器的角度看,“i=i+1”中 i 既然被赋值,就应该当做本地变量,所以就会发生上述 UnboundLocalError 错误。i 也并不是全局变量,所以在 ins 中用 global 声明了 i 则会找不到全局变量。按照 PEP3104 来看,这是一个由来已久的问题。

PEP3104 中提到一种解决办法如下:

>>> def gen_ins(init):
	class T: pass
	t=T()
	t.i=init-1
	def ins():
		t.i=t.i+1
		return t.i
	return ins

>>> ins0=gen_ins(0)
>>> ins0()
0

称为"wrapping it in a mutable object",看起来很诡异。因为Guido觉得不能改动关键字 global 的含义,所以在 python 3 中引入了 nonlocal 关键字,总算解决了这个问题。


  • 装饰器(decorators)

前面的闭包算是装饰器的引子了。装饰器有两种形式:

@A
def foo():
    pass

相当于:

def foo():
    pass
foo = A(foo)

而:

@A(arg)
def foo():
    pass

则相当于:

def foo():
    pass
foo = A(arg)(foo)

可以看出第一种的装饰器是个返回函数的函数,第二种的装饰器是个返回 返回函数的函数 的函数。两种装饰器的简单示例:

def A(func):
    def newfunc(*args, **argkw):
        print 'A'
        return func(*args, **argkw)
    return newfunc

def A(arg):
    def _A(func):
        def newfunc(*args, **argkw):
            print arg
            return func(*args, **argkw)
        return newfunc
    return _A

装饰器中的嵌套定义的函数就涉及到 python 中闭包的问题。

装饰器可以做很多事,比如在原函数调用前检查参数,或者检查登陆状态,调用后记录日志什么的。python 中默认有 staticmethod 和 classmethod 两个装饰器。

staticmethod用来声明类的静态方法,这样调用时就不会传入实例对象(self):

>>> class T:
    name = 'T'
    @staticmethod
    def getname():
        print T.name

        
>>> T.getname()
T

classmethod 修饰那些直接以类对象作为参数的方法:

>>> class T:
    name = 'T'
    @classmethod
    def getname(a_class): print a_class.name

    
>>> T.getname()
T

其实更”正常“的是不用装饰器:

>>> def getname(a_class): print a_class.name

>>> class T:
    name = 'T'
    getname = classmethod(getname)

    
>>> T.getname()
T
isnowfy 说:
2011年12月15日 12:41

啥叫更”正常“的是不用装饰器,装饰器不过是打字少的一种便捷方式吧。。。

Avatar_small
scturtle 说:
2011年12月15日 18:14

getname如果多个类共用的话,用装饰器反而会需要在每个类里声明一次吧,classmethod看起来是就是用来类间共享方法的,它的装饰器用法是否显得有点儿多余?


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter