Python 装饰器与闭包

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'


展开阅读全文