严格来讲,装饰器只是语法糖。如前所示,装饰器能够像常规的可调用
对象那样调用,其参数是另外一个函数。有时,这样作更方便,尤为是作
元编程(在运行时改变程序的行为)时。html
它们在被装饰的函数定义以后当即运行。这一般是在导入时(即 Python 加载模块时)python
registry = [] def register(func): print('running register(%s)' % func) registry.append(func) return func @register def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): print('running f3()') def main(): print('running main()') print('registry ->', registry) f1() f2() f3() if __name__=='__main__': main()
把 registration.py 看成脚本运行获得的输出以下:编程
$ python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3()
>>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)
此时查看 registry 的值,获得的输出以下:缓存
>>> registration.registry [<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]
装饰器函数与被装饰的函数在同一个模块中定义。实际状况是,装
饰器一般在一个模块中定义,而后应用到其余模块中的函数上。闭包
promos = [] def promotion(promo_func): promos.append(promo_func) return @promotion def fidelity(order): """为积分为1000或以上的顾客提供5%折扣""" return order.total() * .05 if order.customer.fidelity >= 1000 else 0 @promotion def bulk_item(order): """单个商品为20个或以上时提供10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount @promotion def large_order(order): """订单中的不一样商品达到10个或以上时提供7%折扣""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * .07 return 0 def best_promo(order): """选择可用的最佳折扣""" return max(promo(order) for promo in promos)
神奇的例子app
>>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... b = 9 ... >>> f2(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f2 UnboundLocalError: local variable 'b' referenced before assignment
利用global就能够啦ide
>>> b = 6 >>> def f3(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 6
人们有时会把闭包和匿名函数弄混。
这是有历史缘由的:在函数内部定义函数不常见,直到开始使用匿名函数才会这样作,模块化
假若有个名为 avg 的函数,它的做用是计算不断增长的系列值的均值;函数
初学者可能会这样性能
class Averager(): def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total / len(self.series)
>>> avg = Averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager
在 averager 函数中,series 是自由变量(free variable)。这是一个
技术术语,指未在本地做用域中绑定的变量.
>>> avg.__code__.co_varnames ('new_value', 'total') >>> avg.__code__.co_freevars ('series',)
>>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x107a44f78: list object at 0x107a91a48>,) >>> avg.__closure__[0].cell_contents [10, 11, 12]
计算移动平均值的高阶函数,不保存全部历史值,但有
缺陷
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
count += 1 语句的做用其实与 count = count + 1 同样。
所以,咱们在 averager 的定义体中为 count 赋值了,这会把 count 变成局部变量。
示例 7-9 没遇到这个问题,由于咱们没有给 series 赋值,咱们只是调
用 series.append,并把它传给 sum 和 len。也就是说,咱们利用了
列表是可变的对象这一事实。可是对数字、字符串、元组等不可变类型来讲,只能读取,不能更新。
若是尝试从新绑定,例如 count = count + 1,其实会隐式建立局部
变量 count。这样,count 就不是自由变量了,所以不会保存在闭包
中。
为了解决这个问题,Python 3 引入了 nonlocal 声明。它的做用是把变
量标记为自由变量,即便在函数中
为变量赋予新值了,也会变成自由变
量。若是为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更
新。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total / count return averager
基本上,这种处理方式是把内部函数须要修改
的变量(如 count 和 total)存储为可变对象(如字典或简单的
实例)的元素或属性,而且把那个对象绑定给一个自由变量。
import time def clock(func): def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ args_str = ''.join(repr(arg) for arg in args) print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, args_str, result)) return result return clocked @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n * factorial(n - 1) if __name__ == "__main__": print("*" * 40) snooze(0.123) print("*" * 40) factorial(6) ## 这里的函数对象变成了从clocked print(factorial.__name__)
常)返回被装饰的函数本该返回的值,同时还会作些额外操做。
上面实现的 clock 装饰器有几个缺点:不支持关键字参数,并且遮盖了被装饰函
数的 name 和 doc 属性。使用 functools.wraps 装饰器把相关的属性从 func 复制到 clocked 中。此外,这个新版还能正确处理关键字参数。
import time import functools def clock(func): @functools.wraps(func) ###这里 保留__name__ 和 __doc__ 属性 def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked
functools.lru_cache 是很是实用的装饰器,它实现了备忘(memoization)功能。
就是更加利用缓存干活
import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked @clock def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == '__main__': print(fibonacci(6))
浪费时间的地方很明显:fibonacci(1) 调用了 8 次,fibonacci(2) 调用了 5 次……可是,若是增长两行代码,使用 lru_cache,性能会显著改善,
import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) if kwargs: pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(', '.join(pairs)) arg_str = ', '.join(arg_lst) print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result)) return result return clocked @functools.lru_cache() # @clock # def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == '__main__': print(fibonacci(6))
❶ 注意,必须像常规函数那样调用 lru_cache。这一行中
有一对括
号:@functools.lru_cache()。这么作的缘由是,lru_cache 能够接受配置参数,稍
后说明。
分开。
个函数绑在一块儿组成一个泛函数
from functools import singledispatch from collections import abc import numbers import html @singledispatch def htmlize(obj): 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) 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>' print(htmlize({1, 2, 3})) print(htmlize(abs)) print(htmlize('Heimlich & Co.\n- a game')) print(htmlize(42)) ##这个强啊!!! print(htmlize(['alpha', 66, {3, 2, 1}]))
❷ 各个专门函数使用 @«base_function».register(«type») 装饰。
❸ 专门函数的名称可有可无;_ 是个不错的选择,简单明了。
为每一个须要特殊处理的类型注册一个函数。numbers.Integral 是 int 的虚拟超类。
❺ 能够叠放多个 register 装饰器,让同一个函数支持不一样类型。
只要可能,注册的专门函数应该处理抽象基类(如 numbers.Integral 和
abc.MutableSequence),不要处理具体实现(如 int 和 list)。
这样,代码支持的兼容类型更普遍。例如,Python 扩展能够子类化 numbers.Integral,使用固定的位数实
现 int 类型。
@d1 @d2 def f(): print('f')
等同于
def f(): print('f') f = d1(d2(f))
为了便于启用或禁用 register 执行的函数注册功能,咱们为它提供一个可选的 active
参数,设为 False 时,不注册被装饰的函数。
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()') @register() def f2(): print('running f2()') def f3(): print('running f3()') if __name__ =="__main__": print(registry)
参数化装饰器一般会把被装饰的函数替换掉,并且结构上须要多一层嵌套。
import time DEFAULT_FMT = '花费时间:[{elapsed:0.5f}s] 程序名:{name} 参数:({args}) -> 结果:{result}' def clock(fmt=DEFAULT_FMT): def decorate(func): def clocked(*_args): t0 = time.time() _result = func(*_args) ### locals() 局部变量 elapsed = time.time() - t0 name = func.__name__ args = ', '.join(repr(arg) for arg in _args) result = repr(_result) # 这里不知道他为何这么能用 print(fmt.format(**locals())) return _result return clocked return decorate if __name__ == '__main__': # ## 第一种状况 # @clock() # def snooze(seconds): # time.sleep(seconds) ## 第二种状况 # @clock('程序名:{name}: 花费时间:{elapsed}s') # def snooze(seconds): # time.sleep(seconds) ## 第三种状况 @clock('程序名:{name} 参数:({args}) 花费时间:dt={elapsed:0.3f}s') def snooze(seconds): time.sleep(seconds) snooze(0.123)
clock 是参数化装饰器工厂函数
❷ decorate 是真正的装饰器。
❸ clocked 包装被装饰的函数。
❹ _result 是被装饰的函数返回的真正结果
def runnoob(arg:'int'): z = 1 print(arg + 1) # 返回字典类型的局部变量。 print('==='*30) print(locals()) # 返回字典类型的所有变量。 print('=' * 50) print(globals()) num = 8 runnoob(num)
量标记为自由变量