第七章 - 函数装饰起和闭包
装饰器和闭包
函数装饰器用于在源码中“标记”函数, 以某种方式增强函数的行为。 这是一项强大的功能,但是若想掌握,必须理解闭包。
nonlocal是新近出现的保留关键字,在Python3.0中引入。
出了在装饰器中有用处之外,闭包还是回调式异步编程和函数式编程风格的基础。
《设计模式: 可复用面向对象软件的基础》一书是这样概述“装饰器”模式的:“动态地给一个对象添加一些额外的职责。”
7.1 装饰器基础知识
示例7-1-1
def decorate(func): def wrapper(*args, **kwargs): print("running inner()") func(*args, **kwargs) return wrapper def target(): print("running target()") target = decorate(target) target()
示例7-2-2, 与上面的代码功能一样
def decorate(func): def wrapper(*args, **kwargs): print("running inner()") func(*args, **kwargs) return wrapper @decorate def target(): print("running target()") target()
综上所述,装饰器几大特性:
1、可以把被装饰函数替换为其他函数
2、可以为被装饰起函数增加功能
3、装饰器在加载模块时立即执行
7.2 Python何时执行装饰器
示例7-2:
def decorate(func): print("running decorate()") def wrapper(*args, **kwargs): print("running inner()") func(*args, **kwargs) return wrapper @decorate def target(): print("running target()") if __name__ == '__main__': print("running main") >>> running decorate() running main
示例7-2主要强调, 函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。 这突出了Python程序员所的导入时和运行时之间的区别。
7.3 使用装饰器改进“策略”模式
promos = [] def promotion(promo_func): """函数装饰器, 可以用在Django Admin中实现某些类的注册装饰器""" promos.append(promo_func) return promo_func @promotion def fidelity(order): """积分在1000或以上,折扣5%""" discount = 0 if order.customer.fidenlity >= 1000: discount = order.total * 0.05 return discount @promotion def bulk_item(order): """单个商品数量在20个或以上,折扣10%""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * 0.1 return discount @promotion def large_order(order): """订单中超过10个或以上不同商品,折扣7%""" discount = 0 distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: discount = order.total() * 0.07 return discount def best_promotion(order): """选择最佳折扣""" return max(promo(order) for promo in promos) """ 该方案与第六章的方案相比,有以下几个有点: 1、promotions中可以有其他非promotion函数 2、@promotion装饰器突出了被装饰函数的作用,还便于临时取消某个促销策略:只需要把装饰器注释 3、促销折扣策略可以在其他模块中定义,在系统中的任何地方都可以,只要使用@promotion装饰即可。 """
7.4 变量作用域规则
示例7-4-1
b = 6 def f1(a): print(a) print(b) f1(3) >>> 3 6
示例7-4-2
b = 6 def f1(a): print(a) print(b) # 之所以会报错,是因为下面对b进行了赋值操作,导致在f1函数内部被当成了一个局部变量,因此跟全局变量b冲突了! b = 9 f1(3) >>> UnboundLocalError: local variable 'b' referenced before assignment
示例7-4-3
b = 6 def f1(a): global b # 这里对b做了一个全局变量的申明 print(a) print(b) b = 9 f1(3)
>>>
3
6
7.5 闭包
闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量,主要功能是它能访问定义体之外定义的非全局变量。
示例7-5-1
class Averager(): def __init__(self): self.series = [] def __call__(self, *args, **kwargs): self.series.append(*args, **kwargs) total = sum(self.series) return total/len(self.series) avg = Averager() print(avg(10)) print(avg(11)) print(avg(12)) >>> 10.0 10.5 11.0
示例7-5-2
def make_averager(): series = [] def average(new_value): series.append(new_value) total = sum(series) return total/len(series) return average avg = make_averager() # 等同于 avg = average print(avg(10)) print(avg(11)) print(avg(12)) >>> 10.0 10.5 11.0
"""
make_averager()..... return average 之间的代码为“闭包”, series 为自由变量。
"""
7.6 nonlocal申明
示例7-6-1
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total/count return averager avg = make_averager() print(avg(10)) """ 对字符串、数字、元组等不可变类型,只能读取,不能更新。如果尝试重新绑定: 例如count = count + 1,其实会隐式创建局部变量count。这样count就不是 自由变量了,因此不会保存在闭包中。 为了解决这个问题,Python3引入了nonlocal申明。 """
示例7-6-2
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total # 申明为自由变量 count += 1 total += new_value return total/count return averager avg = make_averager() print(avg(10))
>>>
10.0
7.8 标准库中的装饰器
functools工具包
functools.wraps(): 我们在使用 Decorator 的过程中,难免会损失一些原本的功能信息,而functools.wraps 则可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module__、__name__、__doc__,或者通过参数选择。
import functools def decorate(func): print("running decorate()") @functools.wraps(func) def wrapper(*args, **kwargs): print("running inner()") func(*args, **kwargs) return wrapper @decorate def target(): print("running target()") if __name__ == '__main__': print("running main") print(target.__name__)
>>>
running decorate()
running main
target
functools.lru_cache():Least Recently Used, 它是非常实用的装饰器,它实现了备忘(memoization)功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。
import time def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__ == '__main__': time_start = time.perf_counter() print(fibonacci(55)) time_stop = time.perf_counter() print("time consume:", time_stop - time_start) """ 通过递归计算斐波那契序列,花费时间.....要好几分钟 """
通过functools.lru_cache装饰
import functools import time @functools.lru_cache() def fibonacci(n): if n < 2: return n return fibonacci(n-2) + fibonacci(n-1) if __name__ == '__main__': time_start = time.perf_counter() print(fibonacci(55)) time_stop = time.perf_counter() print("time consume:", time_stop - time_start) """ 139583862445 time consume: 0.00010351205273810766 # 优化后话费时间 """
基于生成器来实现
def fib(num): """基于生成器来实现""" n, a, b = 0, 0, 1 while n < num: yield b a, b = b, a + b n += 1
7.8.2 单分派泛函数
functools.singledispath 这个内置装饰器函数牛逼了......单一接口调度
from functools import singledispatch @singledispatch def func(arg): print("Let me just say:", arg) @func.register(int) def _(arg): print("Int number:", arg) @func.register(list) def _(arg): for elem in arg: print(elem, end=" ") func("Hello, world.") func(1) func([1, 2, 3, 4]) >>> Let me just say: Hello, world. Int number: 1 1 2 3 4 """ 根据传递的参数的类型,调用相应的函数进行处理! """