[Python]对于 Decorator 装饰器的理解
Decorator 装饰器
程式设计中的“函式”是为了解决程式码的重复利用、模组化,同时也增加可读性。而 Python 的“装饰器”可以在不改变原本函式的功能之下,又再进一步强化 (简化) 不同函式之间程式码的共用。
Python 的“函式”是所谓的“一级函式”,支援“高阶函式”的用法,可被当成参数来传递。“装饰器”就是一个“两层”的函式,先接收一个函式进行包装 (处理),包装完再回传新的函式。就是“吃进去又吐出来”的意思!
我简单统整出两种装饰器函式的写法:
# 针对没有输入/输出的函式 def DecoratorName1(callback): def wrapper(): # Some Code... callback() # Some Code... return wrapper # 针对有输入/输出的函式 def DecoratorName2(callback): def wrapper(*args, **kwargs): # Some Code... result = callback(*args, **kwargs) # Some Code... return result return wrapper
使用装饰器的方法:
# 定义 OriginalFunction 函式,并将它套用 DecoratorName 装饰器 @DecoratorName def OriginalFunction(): # Some Code... # 执行原本的函式 OriginalFunction() 即可 OriginalFunction()
此处的 @ 是一个语法糖,相当于在定义完 OriginalFunction 之后,执行了一次:
OriginalFunction = DecoratorName(OriginalFunction)
所以后面执行的 OriginalFunction(),是被装饰器 DecoratorName() 吃进去又吐出来的产物。
范例
我写了一个范例,用一个“计时器”做为装饰器,包装一个“费波那契数列”的函数:
import time def timer(callback): """计时器-装饰器""" def wrapper(*args, **kwargs): print(callback.__name__) # 看一下呼叫了哪个函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{elapsed_time:.06f} ms") return wrapper # 用 timer() 包装 fib() @timer def fib(n): """求费波那契数列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 执行 fib() fib(1000) # 执行结果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # 0.123456 ms
PS.如果没加上 @timer 来包装 fib() 函式,那么执行 timer(fib)(1000) 也有一样的结果。
再厉害一点,让装饰器也能传入参数:
import time def showTimer(title): """计时器-装饰器工厂""" def timer(callback): def wrapper(*args, **kwargs): print(callback.__name__) # 看一下呼叫了哪个函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{title} {elapsed_time:.06f} ms") return wrapper return timer # 用 showTimer() 包装 fib(),并于修饰器传入参数 @showTimer("Elapsed Time:") def fib(n): """求费波那契数列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 执行 fib() fib(1000) # 执行结果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # Elapsed Time: 0.123456 ms
PS.如果没加上 @showTimer() 来包装 fib() 函式,那么执行 showTimer("Elapsed Time:")(fib)(1000) 也有一样的结果。
从程式码可以看出,装饰器函式由两层变成了三层,这种做法称为“Decorator Factory (装饰器工厂)”。每多加一项功能又要多包一层,像洋葱一样。
如果不用 Decorator 装饰器?
从装饰器的原理来看,跟我写一个函式,在函式里呼叫另一个函式也是一样的效果。或是在呼叫函式时,直接传入另一个函式的执行结果,EX: func3(func1(), func2())。
像前面的程式,我一样可以用两个函式来处理:
import time def showTimerFunction(title, callback, *args, **kwargs): """计时器-函式""" print(callback.__name__) # 看一下呼叫了哪个函式 start_time = time.time() callback(*args, **kwargs) elapsed_time = (time.time() - start_time) * 1000 print(f"\n{title} {elapsed_time:.06f} ms") def fib(n): """求费波那契数列""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a + b # 执行 showTimerFunction() 去呼叫 fib() showTimerFunction("Elapsed Time:", fib, 1000) # 执行结果: # fib # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 # Elapsed Time: 0.123456 ms
回归原本函式呼叫函式的作法,这样其实更不容易迷惑,要执行哪个函式还更有弹性。我可以决定要执行原本的 fib() 或是 showTimerFuncion(),而非只能执行被 showTimer() 包装后的 fib()。
函式也不用一层一层的回传自己。
如果不用传递参数,那函式写起来还更简单。
结论
目前还没找到非得使用装饰器的场景!
No comments yet.