程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

python 中的裝飾器及其原理

編輯:Python

1. 引言

熟悉 Java 的程序員一定對 Java 中強大的注解有所了解,Python 在一定程度上受到了 Java 的影響,誕生了 Python 的裝飾器特性。 Python 的裝飾器是一個非常強大的功能,本文我們就來詳細介紹一下 Python 的裝飾器特性。 例如我們熟知的類靜態方法在定義時就常常會使用 @staticmethod 與 @classmethod 裝飾器:

class TestClass:
@staticmethod
def staticfoo():
pass
@classmethod
def clsfoo(cls):
pass
if __name__ == '__main__':
Test.staticfoo()
Test.clsfoo()

2. 裝飾器

裝飾器是一種可調用對象,通常用來增強或替換函數,Python 也支持類裝飾器。

def decorator(func):
func()
print('this is decorator')
@decorate
def target():
print('running target()')

2.1. 裝飾器的原理

本質上,上面的例子與下面的效果是一樣的:

def decorator(func):
func()
print('this is decorator')
def target():
print('running target()')
target = decorate(target)

3. 裝飾器的執行時機

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('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()

執行程序,打印出了:

>>> python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3() >>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)

上面的例子說明,裝飾器是在模塊被導入時立即執行的,而被裝飾的函數只在明確調用時才運行。

4. 閉包與裝飾器

我們看到當模塊一被導入,裝飾器中的代碼就被執行了,通常我們希望在被裝飾方法執行時增強該方法,這樣我們顯然不希望裝飾器代碼在模塊導入的時機執行。 通過上面閉包的例子我們就可以解決這個問題。

def decorator(func):
def restructure():
func()
print('this is decorator')
return restructure
@decorator
def target():
print('this is target')

上例中,在模塊載入時,裝飾器被執行,於是 restructure 方法被定義,而由於閉包的特性,restructure 內部可以訪問自由變量 func,從而執行對 func 的增強代碼。

5. 通過裝飾器實現裝飾器模式

5.1. 裝飾器模式

此前的文章中我們介紹過裝飾器模式:

裝飾器模式中具體的 Decorator 實現類通過將對組建的請求轉發給被裝飾的對象,並在轉發前後執行一些額外的動作來修改原有的部分行為,實現增強 Component 對象中被請求方法的目的。 裝飾器模式是一種十分靈活的,可以動態添加和分離額外操作的設計模式,python 中的裝飾器正是因為這個模式而得名,也是實現這個設計模式的得力工具。

5.2. python 裝飾器實現自動監控

裝飾器模式的一個典型的應用場景就是對所有需要被監控的方法實現無差別的自動日志打印和監控上報的一些統計功能。 下面的例子展示了函數執行時間的自動打印:

import time
import functiontools
import logging
def clock(func):
@functools.wraps(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)
logging.info('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked

上面的例子中,通過裝飾器與閉包,實現了對 func 的增強,通過裝飾器 clock,自動在 log 中打印了方法的執行時間。 我們定義函數:

@clock
def snooze(seconds):
time.sleep(seconds)
return seconds
if __name__ == '__main__':
print(snooze(.123))

打印出了:

[0.12405610s] snooze(.123) -> 0.123 0.123

5.2.1. functools.wraps

functools.wraps 是標准庫中的一個裝飾器,他把相關的屬性從 func 復制到 clocked 中,從而讓裝飾器的外部表現與被裝飾函數的表現林亮一直。

5.3. 監控優化 — 增加參數

很多時候,我們需要在裝飾器上傳遞一些參數,以實現不同場景的定制化需求,例如有些時候我們希望打印出全部參數、返回值,有些時候需要在發生異常時隱藏異常,返回預設的默認值等。 了解了裝飾器的原理以後,包含參數的裝飾器就很容易寫出來,事實上,無論嵌套多少層,只要記得開始的時候我們提到的裝飾器的原理,就都不難理解了。

import inspect
import logging
import time
import uuid
from functools import wraps
def report(project=None, type=None, name=None, raise_exception=True, result=None, paramslog=False, returnlog=False):
def decorator(func):
@wraps(func)
def wrapper(*arg, **kvargs):
cattype = type
if cattype is None:
cattype = inspect.getfile(func)
if project is not None:
cattype = '[:: ' + project + ' ::] ' + cattype
catname = name if name is not None else func.__name__
randid = str(uuid.uuid1())
if paramslog:
logging.info('<%s::%s> [%s] PARAMS: %r; %r' % (cattype, catname, randid, arg, kvargs))
try:
t0 = time.perf_counter()
res = func(*arg, **kvargs)
elapsed = time.perf_counter() - t0
if returnlog:
logging.info('<%s::%s> [%s;%0.8fs] RESULT: %r' % (cattype, catname, randid, elapsed, res))
return res
except Exception as e:
logging.error('<%s::%s> [%s] ERROR: %r' % (cattype, catname, randid, e), exc_info=True)
if raise_exception:
raise e
else:
return result
return wrapper
return decorator

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved