首页 > 技术文章 > python闭包、装饰器、生成器

laof 2017-09-04 14:07 原文

闭包

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

在开始介绍闭包之前先看看Python中的namespace。

Python中的namespace

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:

  • local namespace: 作用范围为当前函数或者类方法
  • global namespace: 作用范围为当前模块
  • build-in namespace: 作用范围为所有模块

当函数/方法、变量等信息发生重名时,Python会按照 “local namespace -> global namespace -> build-in namespace”的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。

 1 def person(pre):
 2     def greeting(name):
 3         print(pre, name)
 4     return greeting
 5 
 6 p = person("Hello ")
 7 print(dir(p.__closure__[0]))
 8 print(p.__closure__[0].cell_contents)
 9 p("man")
10 p("woman")
11 
12 输入结果为
13 ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
14 Hello 
15 Hello  man
16 Hello  woman

通过__closure__属性看到,它对应了一个tuple,tuple的内部包含了cell类型的对象。

对于这个例子,可以得到cell的值(内容)为”Good Morning”,也就是变量”pre”的值。

从这里可以看到闭包的原理,当内嵌函数引用了包含它的函数(enclosing function)中的变量后,这些变量会被保存在enclosing function的__closure__属性中,成为enclosing function本身的一部分;也就是说,这些变量的生命周期会和enclosing function一样。

Python中怎么创建闭包

在Python中创建一个闭包可以归结为以下三点:

  • 闭包函数必须有内嵌函数
  • 内嵌函数需要引用该嵌套函数上一级namespace中的变量
  • 闭包函数必须返回内嵌函数

通过这三点,就可以创建一个闭包,Python装饰器就是使用了闭包

装饰器

函数装饰器

 1 # 带参数的函数装饰器
 2 def f(pre="Hi"):
 3     def f(func):
 4         def wrapper(*args, **kwargs):
 5             print("%s woman" % pre)
 6             return func(pre, *args, **kwargs)
 7         return wrapper
 8     return f
 9 
10 @f("Hi")
11 def f1(*args):
12     return " ".join(args)
13 
14 print(f1("man"))
15 
16 输出结果
17 Hi woman
18 Hi man
# 双重装饰
def p(func):
    print("p")
    def wrapper(*args, **kwargs):
        print("start1")
        return func(*args, **kwargs)
    return wrapper


def f(func):
    print("f")
    def wrapper(*args, **kwargs):
        print("start2")
        return func(*args, **kwargs)
    return wrapper
@p
@f
def f1(a, b):
    print("f1")
    return a * b

print(f1(2, 3))

输出结果
f
p
start1
start2
f1
6

类装饰器

 1 # 类装饰器
 2 class Func():
 3 
 4     def __init__(self, func):
 5         self._func = func
 6 
 7     def __call__(self, *args, **kwargs):
 8         print("start")
 9         self._func(*args, **kwargs)
10         print("end")
11 @Func
12 def f():
13     print("OK")
14 
15 f()

 

 1 # 带参数的类装饰器
 2 class Func():
 3 
 4     def __init__(self, level='info'):
 5         self.level = level
 6 
 7     def __call__(self, func):
 8         def wrapper(*args, **kwargs):
 9             print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))
10             return func(*args, **kwargs)
11         return wrapper
12 
13 @Func(level="debug")
14 def f():
15     return "OK"
16 
17 ret = f()
18 print(ret)
19 
20 输出结果
21 [debug]: enter function f()
22 OK

Python内置装饰器

在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。

 1 # 不带wraps
 2 def my_decorator(func):  
 3     def wrapper(*args, **kwargs):  
 4         '''''decorator'''  
 5         print('Calling decorated function...')  
 6         return func(*args, **kwargs)  
 7     return wrapper    
 8  
 9 @my_decorator   
10 def example():  
11     """Docstring"""   
12     print('Called example function')  
13 print(example.__name__, example.__doc__)  # ('wrapper', 'decorator')
14 
15 # 带wraps
16 from functools import wraps     
17 def my_decorator(func):  
18     @wraps(func)  
19     def wrapper(*args, **kwargs):  
20         '''''decorator'''  
21         print('Calling decorated function...')  
22         return func(*args, **kwargs)  
23     return wrapper    
24  
25 @my_decorator   
26 def example():  
27     """Docstring"""   
28     print('Called example function')  
29 print(example.__name__, example.__doc__) # ('example', 'Docstring')

 

推荐阅读