python闭包和装饰器的例子

1 篇文章 0 订阅
0 篇文章 0 订阅

这篇文章上周起了个头,把代码帖上来了,没有写完。在今年最后一天,把它写完。


装饰器是闭包的一种扩展。装饰器不仅仅用于函数和类的成员函数,还用于对类的装饰。不过,殊路同归,都是名字的重绑定。只是有的名字是函数,有的是类罢了。

装饰器让我想到了基于方面的编程。基于对象的编程在纵向的模块解耦设计方面有天生的优势,但是在横向的模块通用设计(如日志)方面的优势并不明显。闭包和装饰器在这方面却有得天独厚的优势。

什么是闭包呢?

闭包相当于函数对象。函数对象不仅包含一段代码,更重要的是包含了状态,也就跟类对象平起平坐了。换句话说,函数对象不再像函数一样是全局唯一的了。你在A处调用的函数F,跟在B处调用的函数F,是同一个函数。但是,包含了状态的函数对象不再是这样的了。在A处调用的函数对象FO,跟在B处调用的函数对象FO可能就不再是同一个对象了。为啥?因为A处的FO跟B处的FO可能有不同的状态。想想类和对象的区分。

闭包有什么用呢?一个很重要的作用是闭包将函数调用跟参数绑定解耦了。如下函数func,在调用的时候,必须要把参数一起传入。

func(a, b, c)

而闭包怎么处理呢?如下,第一句是将func的参数绑定,第二句是不带参数的调用func。

func_closure = func(a, b, c)

func_closure()

python的闭包怎么实现呢?从上面第一句可以看出,func_closure也是一个函数,也就是说,func返回值是一个函数。

返回函数的函数就是闭包呢?也不尽然,当func_closure引用的函数访问func的参数或者func定义的变量时,func_closure才是闭包。

说了这么多,下面以一个日志的例子来说明。需求是记录函数的参数,返回值和执行时间。

代码如下:

import time

def log_decorator(show_para):
    def log_decorator_impl(func):
        def wrapper(*args, **kwargs):
            if func.__doc__ is not None:
                print func.__doc__
            starttime = time.time()
            retcode = func(*args, **kwargs)
            endtime = time.time()
            print func.__name__, '(',
            if show_para:
                if args != ():
                    print '#', args, '#',
                if kwargs != {}:
                    print '##', kwargs, '##',
            print') return', retcode, 'costs', endtime - starttime, 's.'
            return retcode
        return wrapper 
    return log_decorator_impl

@log_decorator(False)
def hello():
'''say hello to everybody.'''
    print 'hello'

@log_decorator(True)
def calc(x=0, y=0):
'''add two number.'''
    return x + y

@log_decorator(True)
def calc_numbers(numbers):
'''add numbers in list'''
    total = 0
    for anumber in numbers:
        total += anumber
    return total

if __name__ == '__main__':
    hello() 
    calc(3, 9)
    calc(y=11)
    numbers = [1, 3, 5, 9]
    calc_numbers(numbers)

输出 如下:
say hello to everybody.
hello
hello ( ) return None costs 0.0330429077148 s.
add two number.
calc ( # (3, 9) # ) return 12 costs 4.05311584473e-06 s.
add two number.
calc ( ## {'y': 11} ## ) return 11 costs 4.05311584473e-06 s.
add numbers in list
calc_numbers ( # ([1, 3, 5, 9],) # ) return 18 costs 4.05311584473e-06 s.

log_decorator函数内部定义了两个嵌套函数,它是一个闭包吗?让python解释器来告诉我们:
>>> dir(log_decorator)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> log_decorator.__closure__
>>> print log_decorator.__closure__
None
>>> dir(log_decorator_impl)

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    dir(log_decorator_impl)
NameError: name 'log_decorator_impl' is not defined

 log_decorator有一个属性__closure__,这个属性保存环境变量,也就是log_decorator的状态。从执行结果看,log_decorator的__closure__是None,也就是说,它没有环境变量。所以它不是闭包。
嵌套的两个函数log_decorator_impl和wrapper是不是闭包呢?很可惜,它俩不在全局名字空间里,不能直接访问。
那就在代码里面加打印!一个声音高喊着。莫急,冲动是魔鬼。直来不行,咱就玩弯的。看看 log_decorator有什么特点。它返回log_decorator_impl。So...
>>> a=log_decorator(True)
>>> dir(a)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> a.__closure__
(<cell at 0x1048b0c20: bool object at 0x100172390>,)
>>> dir(a.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
a就是log_decorator_impl,它的__closure__不是空的,里面保存了一个bool对象!很可能就是调用log_decorator的参数。如果这个bool对象是True,那就肯定是了。
怎么查这个bool对象的值呢?
从dir来看,a.__closure__像极了列表,于是要使用大招了。。。
>>> type(a.__closure__)
<type 'tuple'>
>>> len(a.__closure__)
1
>>>a.__closure__[0]
<cell at 0x1048b0c20: bool object at 0x100172390>
>>> type(a.__closure__[0])
<type 'cell'>
>>> dir(a.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> a.__closure__[0].cell_contents
True
a.__closure__是一个有一个元素的元组(现实跟猜测总有一段距离),元组的元素类型是cell。cell只有一个正常的属性cell_contents(前后没有下划线),从名字上看应该是保存的值。再一查,果然是True!
回过看,再看看log_decorator和log_decorator_impl的区别。为什么前者不是闭包,后者才是?前者没有访问什么内部变量或者参数,所以它压根就不需要保存环境变量。后者要访问前者的参数show_para,自然需要用闭包保存这个bool对象。
再看看wrapper是何方神圣:
>>> def test4closure():
	pass

>>> test4closure
<function test4closure at 0x1048b1848>
>>> b=a(test4closure)
>>> dir(b)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> type(b)
<type 'function'>
>>> type(b.__closure__)
<type 'tuple'>
>>> dir(b.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
>>> len(b.__closure__)
2
>>>dir(b.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '<span style="color:#ff0000;">cell_contents</span>']
>>> b.__closure__[0]
<cell at 0x1048b0be8: function object at 0x1048b1848>
>>> b.__closure__[0].cell_contents
<function test4closure at 0x1048b1848>
>>> b.__closure__[1].cell_contents
True
wrapper也是个闭包,保存了log_decorator的参数show_para和log_decorator_impl的参数func。保存的顺序估计是由内到外。
再调用b看看:
>>> b()
test4closure ( ) return None costs 3.81469726562e-06 s.
>>> b(1)

Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    b(1)
  File "/Users/yangjia/decorator_test.py", line 9, in wrapper
    retcode = func(*args, **kwargs)
TypeError: test4closure() takes no arguments (1 given)
b调用test4closure,并统计了它的参数、返回值和执行时间。test4closure没有参数,所以传给b参数会返回失败。

说完闭包,再说说装饰器。
问装饰器为何物?装饰器简化了上面的过程,它的语法很简单:
@log_decorator(False)
def hello():
在函数定义前一行@一下返回函数的函数。其实装饰器是个语法糖。没有它也能用,有它更方便。
解释器自动函数实现体后面重新绑定一次函数名,大致会把装饰器翻译成这样:
def hello():
	'''say hello to everybody.'''
	print 'hello'
hello = log_decorator(False)(hello)
#       log_decorator(False)->log_decorator_impl
#       log_decorator_impl(hello)->wrapper
这有两次函数调用,第一次调用log_decorator(False)返回log_decorator_impl,第二次调用log_decorator_impl(hello)返回wrapper。
最终函数名hello重新绑定wrapper上。
再用dir、type和len查一把:
>>> type(hello)
<type 'function'>
>>> dir(hello)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> hello.__closure__
(<cell at 0x104878d38:function object at 0x1004f0cf8>, <cell at 0x104878d00: bool object at 0x100172370>)
>>> dir(hello.__closure__)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
>>> type(hello.__closure__)
<type 'tuple'>
>>> len(hello.__closure__)
2
>>> hello.__closure__[0]
<cell at 0x104878d38: function object at 0x1004f0cf8>
>>> type(hello.__closure__[0])
<type 'cell'>
>>> dir(hello.__closure__[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>> hello.__closure__[0].cell_contents
<function hello at 0x1004f0cf8>
>>> hello.__closure__[1].cell_contents
False
 hello是一个闭包,有两个元素,一个是hello函数本身,另一个是og_decorator的参数show_para(值是False)。
最后一个问题,hello是不是函数hello本身?hello跟另外两个函数calc和calc_numbers是不是一样的?
>>> hello
<function wrapper at 0x1004f0d70>
>>> calc
<function wrapper at 0x1050b79b0>
>>> calc_numbers
<function wrapper at 0x1050b7b18>
>>> print '%#x' %id(hello)
0x1004f0d70
>>> print '%#x' %id(calc)
0x1050b79b0
>>> print '%#x' %id(calc_numbers)
0x1050b7b18
>>> hello == calc
False
>>> hello == calc_numbers
False
>>> calc == calc_numbers
False
显然,hello不再是hello,而是个wrapper。calc和calc_numbers也是wrapper。hello、calc和calc_numbers是三个不同的wrapper对象。
顺便提一下id函数,这个函数返回一个对象的唯一标识。从上面的输出看,实际上返回的是对象的内存地址。

python的乐趣之一是在不清楚某个特性的时候,可以通过内建函数(如type、dir、len和id)探究一遍。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值