闭包
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')