装饰器基础知识
- 装饰器是可调用对象,其参数是另一个函数
- 装饰器会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象
def deco(func): def inner(): print("inner is called") return inner
@decodef func(): print("func is called")# func被装饰器修改成innerfunc()print(func)
输出:inner is called
Python何时执行装饰器
- 装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。
- 即在模块导入初始化时运行
registry = []def register(func): print('running register(%s)' % func)
registry.append(func) return func
@registerdef func1(): print('func1 is running')
@registerdef func2(): print('func2 is running')def func3(): print('func3 is running')def main(): # 在main()运行之前,register已经运行了两次
# 这两次就是在被装饰函数定义时
# 所以registry也已经被初始化
print('main is running') print('registry:',registry)
func1()
func2()
func3()if __name__ == '__main__':
main()
输出:running register(
running register(
main is running
registry: [
func1 is running
func2 is running
func3 is running
变量作用域规则
- Python不要求声明变量,但是假定在函数定义体中有赋值的变量是局部变量
b = 3def f1(a): print(a) # 这里b是局部变量,所以错误提示为在赋值之前使用 # 如果想获取全局变量b,需要加上global b print(b) b = 9f1(1)
闭包
- 只有涉及嵌套函数时才有闭包问题
- 闭包指延伸了作用域的某个函数,它包含了不在函数内定义的非全局变量
def make_avg():
vars = [] def avg(value):
vars.append(value)
total = sum(vars) return total/len(vars) return avg# avg中包含了在make_avg中定义的vars列表# 即使make_avg已经返回,但vars仍然被保留下来avg = make_avg()print(avg(10))print(avg(11))print(avg(12))# 闭包中引用的非自己定义的的非全局变量称为自由变量# 自由变量的名称被保存在__code__.co_freevars中# 自由变量的值被保存在__closure__[i].cell_contents中print('avg.__code__.co_freevars:',avg.__code__.co_freevars)print('avg.__closure__[0].cell_contents',avg.__closure__[0].cell_contents)
输出:10.0
10.5
11.0
avg.__code__.co_freevars: ('vars',)
avg.__closure__[0].cell_contents [10, 11, 12]
nonlocal声明
- 在闭包中对自由变量进行赋值,会生成一个局部变量覆盖自由变量,并且报错:局部变量使用前未赋值
def make_avg(): count=0 total=0 def avg(value): # count+=1就是count = count + 1 # 这里会自动生成一个未赋初始值的局部变量count覆盖自由变量count count+=1 # total和count相同情况 total+=value return total/count return avg avg = make_avg()print(avg(10))print(avg(11))print(avg(12)) 输出: UnboundLocalError: local variable 'count' referenced before assignment
- 使用nonlocal声明,不会覆盖自由变量
def make_avg(): count=0 total=0 def avg(value): # 使用nonlocal声明,不会覆盖自由变量 nonlocal count,total count+=1 total+=value return total/count return avg avg = make_avg()print(avg(10))print(avg(11))print(avg(12)) 输出:10.0 10.5 11.0
一个函数执行时间的装饰器
- func的函数__name__和__doc__属性将被clocked相应属性覆盖
import timedef clock(func): def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
func_name = func.__name__
func_args = ','.join(str(args))
s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result) print(s) return result return clocked
@clockdef snooze(n):
time.sleep(n)
@clockdef factorial(n): if n == 1: return 1 else: return n*factorial(n-1)
snooze(0.123)print('-' * 100)print('factorial(6):',factorial(6))print('-' * 100)print('the name of factorial:',factorial.__name__)
输出:
[0.12324016s] snooze((,0,.,1,2,3,,,)) --> None----------------------------------------------------------------------------------------------------[0.00000032s] factorial((,1,,,)) --> 1[0.00001091s] factorial((,2,,,)) --> 2[0.00002855s] factorial((,3,,,)) --> 6[0.00003401s] factorial((,4,,,)) --> 24[0.00003914s] factorial((,5,,,)) --> 120[0.00004588s] factorial((,6,,,)) --> 720factorial(6): 720
----------------------------------------------------------------------------------------------------the name of factorial: clocked- 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖
import timeimport functoolsdef clock(func): # 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖 @functools.wraps(func) def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
func_name = func.__name__
func_args = ','.join(str(args))
s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result) print(s) return result return clocked
@clockdef factorial(n): if n == 1: return 1 else: return n*factorial(n-1)print('the name of factorial:',factorial.__name__)
输出:
the name of factorial: factorial
使用functools.lru_cache做备忘
import timeimport functoolsdef clock(func): # 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖 @functools.wraps(func) def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
func_name = func.__name__
func_args = ','.join([str(a) for a in args if str(a) != ''])
s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result) print(s) return result return clocked# 不使用lru_cache,fib(6)要调用fib(1)8次,fic(2)5次等等@clockdef fib(n): if n < 2: return n else: return fib(n-2) + fib(n-1)# 使用lru_cache,可以缓存中间结果,避免重复调用。@functools.lru_cache()
@clockdef fib2(n): if n < 2: return n else: return fib2(n-2) + fib2(n-1)print('fib(6):',fib(6))print('-' * 100)print('fib2(6):',fib2(6))
输出:
[0.00000032s] fib(0) --> 0
[0.00000032s] fib(1) --> 1[0.00004235s] fib(2) --> 1[0.00000000s] fib(1) --> 1[0.00000000s] fib(0) --> 0
[0.00000032s] fib(1) --> 1[0.00001059s] fib(2) --> 1[0.00002117s] fib(3) --> 2[0.00007443s] fib(4) --> 3[0.00000032s] fib(1) --> 1[0.00000032s] fib(0) --> 0
[0.00000000s] fib(1) --> 1[0.00001091s] fib(2) --> 1[0.00002181s] fib(3) --> 2[0.00000032s] fib(0) --> 0
[0.00000000s] fib(1) --> 1[0.00001123s] fib(2) --> 1[0.00000032s] fib(1) --> 1[0.00000032s] fib(0) --> 0
[0.00000032s] fib(1) --> 1[0.00001123s] fib(2) --> 1[0.00002246s] fib(3) --> 2[0.00004459s] fib(4) --> 3[0.00007731s] fib(5) --> 5[0.00016297s] fib(6) --> 8fib(6): 8
----------------------------------------------------------------------------------------------------[0.00000000s] fib2(0) --> 0
[0.00000032s] fib2(1) --> 1[0.00001476s] fib2(2) --> 1[0.00000064s] fib2(3) --> 2[0.00002695s] fib2(4) --> 3[0.00000064s] fib2(5) --> 5[0.00003882s] fib2(6) --> 8fib2(6): 8
使用单分派函数
- 类似其他面向对象语言的方法重载,以不同方式执行相同操作的一组函数
from functools import singledispatchimport htmlfrom collections import abcimport numbers# 单分派函数的基函数@singledispatchdef htmlize(obj): return '{}'.format(html.escape(repr(obj)))# 各个专门函数使用@.register()修饰@htmlize.register(str)# 函数名无关紧要,_是个不错的选择,简单明了def _(txt):
s = html.escape(txt).replace('n','
n') return '{}
'.format(s)# numbers.Integral是int的虚拟超类@htmlize.register(numbers.Integral)def _(n): return '
{0}(0x{0:X})'.format(n)# 可以叠放多个register装饰器,让同一个函数支持不同类型@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)def _(seq):
inner = 'n'.join(htmlize(item) for item in seq)
outer = 'n- ' + inner + '
n
'
return outerprint(htmlize({1, 2, 3}))print(htmlize(abs))print(htmlize(42))print(htmlize(['alpha',66,{3,2,1}]))
输出:
{1, 2, 3}
<built-in function abs>
42(0x2A)
alpha
66(0x42)
{1, 2, 3}
一个参数化的装饰器
registry = set()# register是装饰器工厂,返回一个装饰器# 它接受一个可选关键字参数def register(active=True): # decorate是真正的装饰器,它的参数是一个函数
def decorate(func): print('running register(active=%s) -> decorate(%s)' % (active,func)) # active为真,注册func
if active:
registry.add(func) # active为假,删除func
else:
registry.discard(func) return func return decorate# register必须作为函数调用,并且可以传入参数@register(active=True)def func1(): print('running func1')# 即使不传入参数,也要作为函数调用@register()def func2(): print('running func2')# active为假,不注册func3@register(active=False)def func3(): print('running func3')if __name__ == '__main__':
func1()
func2()
func3() print(registry)
输出:
running register(active=True) -> decorate()
running register(active=True) -> decorate()
running register(active=False) -> decorate()
running func1
running func2
running func3
{, }
三层嵌套的装饰器
import time
default_fmt = '[{elapsed:0.8f}s] {name}({args}) -> {result}'# 一个三层嵌套的装饰器def clock(fmt=default_fmt): # @clock()返回decorate
def decorate(func): # 再把被装饰函数传递给decorate,运行它
# 返回clocked,替代被装饰函数
def clocked(*args):
t0 = time.time()
_result = func(*args)
elapsed = time.time()-t0
name = func.__name__
args = ','.join(repr(item) for item in args)
result = repr(_result) print(fmt.format(**locals())) return _result return clocked return decorate# func被clocked替代@clock()def func():
time.sleep(0.123)if __name__ == '__main__': for i in range(3):
func()
输出:
[0.12314367s] func() -> None
[0.12322927s] func() -> None
[0.12370729s] func() -> None作者:StackNeverOverFlow
原文出处:https://www.cnblogs.com/StackNeverOverFlow/p/10453240.html



