深入理解Python闭包概念(3)

但闭包的缺点也是很明显的,那就是经过装饰器装饰的函数或者类不再是原来的函数或者类了。这也是使用装饰器改变函数或者类的行为与直接修改定义���根本的差别。

实际应用的时候一定要注意这一点,下面看一个使用装饰器导致的一个很隐蔽的问题。

def counter(cls):
    obj_list = []
    def wrapper(*args, **kwargs):
        new_obj = cls(*args, **kwargs)
        obj_list.append(new_obj)
        print "class:%s'object number is %d" % (cls.__name__, len(obj_list))
        return new_obj
    return wrapper

@counter
class my_cls(object):
    STATIC_MEM = 'This is a static member of my_cls'
    def __init__(self, *args, **kwargs):
        print self, args, kwargs
        print my_cls.STATIC_MEM

这个例子中我们尝试使用装饰器来统计一个类创建的对象数量。当我们创建my_cls的对象时,会发现something is wrong!

Traceback (most recent call last):
  File "G:\Cnblogs\Alpha Panda\Main.py", line 360, in <module>
    my_cls(1,2, key = 'shijun')
  File "G:\Cnblogs\Alpha Panda\Main.py", line 347, in wrapper
    new_obj = cls(*args, **kwargs)
  File "G:\Cnblogs\Alpha Panda\Main.py", line 358, in __init__
    print my_cls.STATIC_MEM
AttributeError: 'function' object has no attribute 'STATIC_MEM'

如果对装饰器不是特别的了解,可能会对这个错误感到诧异。经过装饰器修饰后,我们定义的类my_cls已经成为一个函数。

my_cls.__name__ == 'wrapper' and type(my_cls) is types.FunctionType

my_cls被装饰器counter修饰,等价于 my_cls = counter(my_cls)

显然在上面的例子中,my_cls.STATIC_MEM是错误的,正确的用法是self.STATIC_MEM。

对象中找不到属性的话,会到类空间中寻找,因此被装饰器修饰的类的静态属性是可以通过其对象进行访问的。虽然my_cls已经不是类,但是其调用返回的值却是被装饰之前的类的对象。

该问题同样适用于staticmethod。那么有没有方法得到原来的类呢?当然可以,my_cls().__class__便是被装饰之前的类的定义。

那有没有什么方法能让我们还能通过my_cls来访问类的静态属性,答案是肯定的。

1 def counter(cls): 2 obj_list = [] 3 @functools.wraps(cls) 4 def wrapper(*args, **kwargs): 5 ... ... 6 return wrapper

改写装饰器counter的定义,主要是对wrapper使用functools进行了一次包裹更新,使经过装饰的my_cls看起来更像装饰之前的类或者函数。该过程的主要原理就是将被装饰类或者函数的部分属性直接赋值到装饰之后的对象。如WRAPPER_ASSIGNMENTS(__name__, __module__ and __doc__, )和WRAPPER_UPDATES(__dict__)等。但是该过程不会改变wrapper是函数这样一个事实。

my_cls.__name__ == 'my_cls' and type(my_cls) is types.FunctionType

5.闭包的实现

本着会用加理解的原则,可以从应用层的角度来稍微深入的理解一下闭包的实现。毕竟要先会用python么,如果一切都从源码中学习,那成本的确有点高。

def outer_func():
    loc_var = "local variable"
    def inner_func():
        return loc_var
    return inner_func

import dis
dis.dis(outer_func)
clo_func = outer_func()
print clo_func()
dis.dis(clo_func)

为了更加清楚理解上述过程,我们先尝试给出outer_func.func_code中的部分属性:

outer_func.func_code.co_consts: (None, 'local variable', <code object inner_func at 025F7770, file "G:\Cnblogs\Alpha Panda\Main.py", line 207>)

outer_func.func_code.co_cellvars:('loc_var',)

outer_func.func_code.co_varnames:('inner_func',)

尝试反汇编上面这个简单清晰的闭包例子,得到下面的结果:

0 LOAD_CONST              1 ('local variable')   # 将outer_func.func_code.co_consts[1]放到栈顶
STORE_DEREF              0 (loc_var)        # 将栈顶元素存放到cell对象的slot 0
          6 LOAD_CLOSURE            0 (loc_var)        # 将outer_func.func_code.co_cellvars[0]对象的索引放到栈顶
BUILD_TUPLE              1              # 将栈顶1个元素取出,创建元组并将元组压入栈中
LOAD_CONST              2 (<code object inner_func at 02597770, file "G:\Cnblogs\Alpha Panda\Main.py", line 207>) # 将outer_func.func_code.co_consts[2]放到栈顶
MAKE_CLOSURE            0              # 创建闭包,此时栈顶是闭包函数代码段的入口,栈顶下面则是函数的free variables,也就是本例中的'local variable ',将闭包压入栈顶
STORE_FAST              0 (inner_func)       # 将栈顶存放入outer_func.func_code.co_varnames[0]
          21 LOAD_FAST              0 (inner_func)       # 将outer_func.func_code.co_varnames[0]的引用放入栈顶
RETURN_VALUE                       # Returns with TOS to the caller of the function.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/0f37c84842a32880f62bf807825c52d1.html