1. 装饰器基础
@decorate
def target():
print('running target()')
# 等效于:
def target():
print('running target()')
target = decorate(target)
def deco(func):
def inner():
print('running inner()')
return inner
@deco
def target():
print('running target()')
>> > target()
running inner()
>> > target
<function deco. <locals>.inner at 0x00000000030D28C8>
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。
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('register ->', register)
f1()
f2()
f3()
if __name__ == '__main__':
main()
运行结果:
running register()
running register()
running main()
register ->
running f1()
running f2()
running f3()
导入模块:
>>> import os
>>> os.chdir(r'c:\Users\Administrator\Desktop')
>>> import decorate
running register()
running register()
使用装饰器改进“策略”模式
# 使用装饰器解决最佳折扣方式
# 装饰器在加载模块时立即执行,必须定义在 @promotion 前
promos=[]
def promotion(func):
promos.append(func)
return func
@promotion
def fidelity(order):
return order.total()*0.05 if order.customer.fidelity>=1000 else 0
@promotion
def bulkitem(order):
discount=0
for item in order.cart:
if item.quantity>=20:
discount+=item.total()*0.1
return discount
@promotion
def large(order):
distinct_items={item.product for item in order.cart}
return order.total()*0.07 if len(distinct_items) else 0
def best_promo(order):
return max(promo(order) for promo in promos)
2. 闭包
闭包指延伸了作用域的函数, 其中包含函数定义体中引用、 但是不在定义体中定义的非全局变量。
>>> 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
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
averager 的闭包延伸到函数的作用域之外,包含自由变量 series 的绑定。
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12] |
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
3. 实现装饰器
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ','.join(repr(arg) for arg in args)
print('[%.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
import time
from clockdeco import clock
@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, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))
**************************************** Calling snooze(.123)
[0.12225036s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000106s] factorial(1) -> 1
[0.00944052s] factorial(2) -> 2
[0.01895358s] factorial(3) -> 6
[0.02370675s] factorial(4) -> 24
[0.02791068s] factorial(5) -> 120
[0.03220307s] factorial(6) -> 720
6! = 720
现在 factorial 保存的是 clocked 函数的引用
>>> import clockdeco_demo
>>> clockdeco_demo.factorial.__name__
'clocked'
使用 functools.wraps 装饰器把相关的属性从 func 复制到 clocked 中
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 kwargs.items()]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
return result
return clocked
>>> import os
>>> os.chdir(r'c:\Users\Administrator\Desktop')
>>> import clockdeco_demo
>>> clockdeco_demo.factorial
>>> clockdeco_demo.factorial.__name__
'factorial'