函数装饰器和闭包
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为,用闭包实现。
nolocal是在Python3.0新增的保留关键字。
闭包除了在装饰器中有用之外,还回调式一步编程和函数式编程风格的基础。
装饰器基础,何时执行装饰器
装饰器是可调用对象,参数是另一个函数(被装饰的函数)。
函数装饰器再导入模块是立即执行,而被装饰的函数只有在调用时运行。
装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
registry = [] def register(func): print('running :{}'.format(func)) registry.append(func) return func @register def f1(): print('f1 running.') @register def f2(): print('f2 running.') def f3(): print('f3 running.') def main(): print(registry) f1() f2() f3() if __name__ == '__main__': main() 打印 running :<function f1 at 0x034F9618> running :<function f2 at 0x034F96A8> [<function f1 at 0x034F9618>, <function f2 at 0x034F96A8>] f1 running. f2 running. f3 running.
以上可以看到,在加载模块时,函数装饰器立即执行。被装饰的函数在调用时执行。
示例中的装饰器原封不动的返回被装饰的函数,这种并不是没有用途,比如在Python web框架中,这样的装饰器把函数添加到某种中央注册处,实现路由功能。
使用装饰器实现“策略”模式
使用装饰器,这种方案有几个优点:
- 促销策略函数无需特殊的名称(不需要以Promo结尾),或不需要放到指定模块中
- @promotion 装饰器,还便于临时禁用某个促销策略,只需要把装饰器去掉。
- 促销策略装饰器可以在其他模块中定义,在系统的任何地方都行,只需要导入后装饰。
示例,使用装饰器完成策略模式(部分代码)
promos = [] def promotion(func): promos.append(func) return func @promotion def FidelityPromo(order): """具体策略:有1000积分以上的顾客,整个订单可以享受5%的折扣""" return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 @promotion def BulkItemPromo(order): """具体策略:同一个订单中,单个商品的数量达到20个以上,单品享受10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount @promotion def LargeOrderPromo(order): """具体策略:订单中不同商品的数量达到10个以上,整个订单享受7%折扣""" return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0 def best_promo(oder): """选择最佳的折扣""" return max(promo(oder) for promo in promos)
变量作用域
这个是一个让人吃惊的示例
b = 2 def f(a): print(a) print(b) b = 2 f(1) 打印 1 Traceback (most recent call last): File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 53, in <module> f(1) File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 49, in f print(b) UnboundLocalError: local variable 'b' referenced before assignment
如果在函数内不加b = 2 程序可以正常执行,因为b会找到全局变量中的b=2
但是在函数最后加上了b=2,在Python编译函数的定义体时,判断出b是局部变量,因为在函数内给它赋值了,就不会获取全局变量。
比较字节码来论证:
dis模块为反汇编Python函数字节码提供了简单的方式。
def f2(a): print(a) print(b) from dis import dis print(dis(f2))
结果可以看到a为LOAD_FAST 本地名称,b为LOAD_GLOBAL全局名称。
b = 2 def f2(a): print(a) print(b) b =2 from dis import dis print(dis(f2))
可以看到这次b是LOAD_FAST 本地名称
另外补充:
LOAD_FAST(var_num)
将对本地co_varnames [var_num]的引用压入堆栈。