函数装饰器和闭包
装饰器
简介
装饰器是一个可调用对象,其参数是另一个函数,作用是返回一个被处理过或替换的函数。
它属于一种语法糖,以下两种写法是等价的
# 1
@decorate
def target():
print('running target')
#2
def target():
print('running target')
target = decorate(target)
装饰器的执行时机
python中,装饰器在被装饰的函数定义后立即运行。这通常发生在导入模块时。
使用装饰器改进策略模式
在策略模式中,我们可能需要在多个策略中选择一个最优的策略,这就需要我们知道目前已经声明了哪些可用的策略,利用一个装饰器在声明每个策略的时候进行一次注册可以很好地解决这个问题。
promo_list = []
def promotion(specific_promo):
promo_list.append(specific_promo)
return specific_promo
@promotion
def specific_promo1(...):
pass
@promotion
def specific_promo2(...):
pass
当我们想取消某条策略时,也只需将其前面的装饰器去除。
标准库中的装饰器
用于装饰方法的函数
- property
- classmethod
- staticmethod
其他常见装饰器
-
functools.wraps:协助构建行为良好的装饰器
-
functools.lru_cache:实现了备忘功能,是一项有效的优化技术(Least Recently Used)
可选参数maxsize = 128:指定存储多少个调用结果,typed = False,若为True则把不同参数类型得到的结果分开保存。
常见用途:
- 优化递归算法
- 从Web中获取信息
-
functools.singledispatch:可以将整体方案拆分成多个模块。使用@singledispatch装饰的函数会成为泛函数(generic function):根据第一个参数的类型(所以叫单分派),以不同的方式执行相同操作的一组函数(类似于重载)。如果没有相匹配的类型,执行泛函数下面的内容(类似于default)。
例:
from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch
def htmlize(obj): #generic function
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral) #int的抽象基类
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence) #可变序列的抽象基类
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
叠放装饰器
装饰器是可以叠放的,即
@d1
@d2
def f():
pass
等价于
def f():
pass
f = d1(d2(f))
参数化装饰器
在解析源码中的装饰器时,python会将被装饰的函数作为第一个参数传递给装饰器函数,那么如何让装饰器接收其他的参数呢?答案是创建一个装饰器工厂函数,把参数传给他,返回一个装饰器,然后再用返回的装饰器应用到响应的函数上。
例如:
registry = set()
def register(active=True): #装饰器工厂函数
def decorate(func):
print('running register(active=%s)->decorate(%s)'% (active, func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('running f1()')
闭包
python不要求声明变量,但是假定在函数定义体中幅值的变量是局部变量。
闭包指延伸了作用域的函数,它能够访问定义体之外的非全局变量。
函数的属性f.__closure__
是一个元祖,其中以cell的方式存储着函数运行是所需的自由变量的绑定。这与通过f.__code__.co_freevars
查看到的函数的自由变量列表相对应。
对于数字、字符串、元祖等不可变类型来说,在函数内部只能读取,不能更新。如果尝试重新绑定,则会隐式创建局部变量。为了解决这个问题,对相应的变量使用nonlocal进行声明。