python装饰器_Python装饰器是个什么鬼?
不知道大家的Python入門的怎么樣了啊?后面幾篇Python的文章涉及一些Python中高級的內容,建議還沒入門的朋友好好閱讀一下如何快速入門Python趕緊上車。后臺回復"python"關鍵詞獲取python入門和進階的經典電子書pdf。
這一篇我們主要介紹一下Python中裝飾器的常見用法。
所謂的裝飾器,其實就是通過裝飾器函數,來修改原函數的一些功能,使得原函數不需要修改。
函數也是對象,可以賦值給變量,可以做為參數,也可以嵌套在另一個函數內。
對于第三種情況,如果在一個函數的內部定義了另一個函數,外部的我們叫他外函數,內部的我們叫他內函數。在一個外函數中定義了一個內函數,內函數里運用了外函數的臨時變量,并且外函數的返回值是內函數的引用。這樣就構成了一個閉包。
一般情況下,在我們認知當中,如果一個函數結束,函數的內部所有東西都會釋放掉,還給內存,局部變量都會消失。但是閉包是一種特殊情況,如果外函數在結束的時候發現有自己的臨時變量將來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,然后自己再結束。
裝飾器從0到1
Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.
所謂的裝飾器,其實就是通過裝飾器函數,來修改原函數的一些功能,使得原函數不需要修改。實際工作中,裝飾器通常運用在身份認證(登錄認證)、日志記錄、性能測試、輸入合理性檢查及緩存等多個領域中。合理使用裝飾器,可極大提高程序的可讀性及運行效率。
在面向對象(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數實現,也可以用類實現。
def my_decorator(func): def inner_wrapper(): print('inner_wrapper of decorator') func() return inner_wrapper@my_decorator def hello(): print('hello world')hello()"""inner_wrapper of decoratorhello world"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'這里的@,我們稱之為語法糖。@my_decorator?相當于?greet=my_decorator(greet)。
對于需要傳參數的函數,可以在在對應的裝飾器函數inner_wrapper()上,加上相應的參數:
def my_decorator(func): def inner_wrapper(arg1): print('inner_wrapper of decorator') func(arg1) return inner_wrapper@my_decoratordef hello(arg1): print('hello world') print(arg1)hello("I'm arg1")"""inner_wrapper of decoratorhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'但是,假設我們有一個新函數需要兩個參數,前面定義的@my_decorator就會不適用。如:
@my_decoratordef hello(arg1,arg2): print('hello world') print(arg1) print(arg2)我們可以把*args和**kwargs,作為裝飾器內部函數inner_wrapper()的參數 ,表示接受任意數量和類型的參數,因此裝飾器就可以寫成下面的形式:
def my_decorator(func): def inner_wrapper(*args, **kwargs): print('inner_wrapper of decorator') func(*args, **kwargs) return inner_wrapper還可以給decorator函數加參數:
def loginfo(info, n): def my_decorator(func): def inner_wrapper(*args, **kwargs): for i in range(n): print(f' loginfo: {info}') func(*args, **kwargs) return inner_wrapper return my_decorator@loginfo("NOBUG", 3)def hello(arg1): print('hello world') print(arg1)hello("I'm arg1")"""<0> loginfo: NOBUGhello worldI'm arg1<1> loginfo: NOBUGhello worldI'm arg1<2> loginfo: NOBUGhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'但是經過裝飾器裝飾之后,hello()函數的元信息被改變,它不再是以前的那個?hello()函數,而是被inner_wrapper()取代了:
hello.__name__ # 'inner_wrapper'help(hello)"""Help on function inner_wrapper in module __main__:inner_wrapper(*args, **kwargs)"""這個問題很好解決:
內置的裝飾器@functools.wrap,它會幫助保留原函數的元信息(也就是將原函數的元信息,拷貝到對應的裝飾器函數里)。
import functoolsdef my_decorator(func): @functools.wraps(func) def inner_wrapper(*args, **kwargs): print('inner_wrapper of my_decorator.') func(*args, **kwargs) return inner_wrapper@my_decoratordef hello(): print("hello world")hello.__name__# 'hello'上面的例子可以寫成:
import functoolsdef loginfo(info,n): def my_decorator(func): @functools.wraps(func) def inner_wrapper(*args, **kwargs): for i in range(n): print(f' loginfo: {info}') func(*args, **kwargs) return inner_wrapper return my_decorator@loginfo("NOBUG",3)def hello(arg1): print('hello world') print(arg1)hello("I'm arg1")"""<0> loginfo: NOBUGhello worldI'm arg1<1> loginfo: NOBUGhello worldI'm arg1<2> loginfo: NOBUGhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'hello'用類作為裝飾器
絕大多數裝飾器都是基于函數和閉包實現的,但這并非構造裝飾器的唯一方式。事實上,Python 對某個對象是否能通過裝飾器(?@decorator)形式使用只有一個要求:decorator 必須是一個“可被調用(callable)的對象。
函數自然是“可被調用”的對象。但除了函數外,我們也可以讓任何一個類(class)變得“可被調用”(callable),只要自定義類的?__call__?方法即可。
因此不僅僅是函數,類也可以做為裝飾器來用。但作為裝飾器的類需要包含__call__()方法。
import functoolsclass Count: def __init__(self, func): self.func = func self.num_calls = 0 functools.update_wrapper(self, func) # 類似于函數方法中的:@functools.wraps(func) def __call__(self, *args, **kwargs): self.num_calls += 1 print('num of calls is: {}'.format(self.num_calls)) return self.func(*args, **kwargs)@Countdef hello(): print("hello world")hello()# # 輸出# num of calls is: 1# hello worldhello()# # 輸出# num of calls is: 2# hello worldhello()# # 輸出# num of calls is: 3# hello worldhello.__name__# 'hello'通過名為__call__的特殊方法,可以使得類的實例能像python普通函數一樣被調用:
class Count: def __init__(self, num_calls=5): self.num_calls = num_calls def __call__(self): print('num of calls is: {}'.format(self.num_calls))a = Count(666)a()"""num of calls is: 666"""裝飾器的嵌套使用
import functoolsdef my_decorator1(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator1') func(*args, **kwargs) return wrapperdef my_decorator2(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator2') func(*args, **kwargs) return wrapperdef my_decorator3(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator3') func(*args, **kwargs) return wrapper@my_decorator1@my_decorator2@my_decorator3def hello(message): print(message)# 類似于調用:decorator1(decorator2(decorator3(func)))hello('hello world')hello.__name__# 輸出# execute decorator1# execute decorator2# execute decorator3# hello world# 'hello'裝飾器的一些常見用途
1. 記錄函數運行時間(日志)
import timeimport functoolsdef log_execution_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() res = func(*args, **kwargs) end = time.perf_counter() print(f'{func.__name__} took {(end - start) * 1000} ms') return res return wrapper@log_execution_timedef calculator(): for i in range(1000000): i = i**2**(1/3)**(1/6) return icalculator()"""calculator took 109.1254340026353 ms48525172657.38456"""import functoolsdef log(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'call {func.__name__}():') return func(*args, **kwargs) return wrapper@logdef now(): print('2019-3-25')def logger(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'{text} {func.__name__}():') return func(*args, **kwargs) return wrapper return decorator@logger('DEBUG')def today(): print('2019-3-25')now()# call now():# 2019-3-25today()# DEBUG today():# 2019-3-25today.__name__# today2. 登錄驗證
有些網頁的權限是需要登錄后才有的。可以寫一個裝飾器函數驗證用戶是否登錄,而不需要重復寫登錄驗證的邏輯。
3. 輸入合理性檢查
對于一些需要做合理性檢驗的地方,可以抽象出合理性檢驗的邏輯,封裝為裝飾器函數,實現復用。例如:
def validate_summary(func): @functools.wraps(func) def wrapper(*args, **kwargs): data = func(*args, **kwargs) if len(data["summary"]) > 80: raise ValueError("Summary too long") return data return wrapper @validate_summary def fetch_customer_data(): # ... @validate_summary def query_orders(criteria): # ... @validate_summary def create_invoice(params): # ...4. 緩存
LRU cache,在 Python 中的表示形式是@lru_cache,它會緩存進程中的函數參數和結果,當緩存滿了以后,會刪除 least recenly used 的數據。
from functools import lru_cache@lru_cache(maxsize=16) # default :128def sum2(a, b): print(f"Invoke func: sum2()") print(f"Calculating {a} + {b}") return a + bprint(sum2(1, 2))print("====================")print(sum2(1, 2))print("====================")print(sum2.cache_info())print(sum2.cache_clear())print(sum2.cache_info())"""Invoke func: sum2()Calculating 1 + 23====================3CacheInfo(hits=1, misses=1, maxsize=16, currsize=1)NoneCacheInfo(hits=0, misses=0, maxsize=16, currsize=0)"""5. 類中常用的@staticmethod和@classmethod
?@classmethod?裝飾的類方法?@staticmethod裝飾的靜態方法?不帶裝飾器的實例方法
用@classmethod修飾的方法,第一個參數不是表示實例本身的self,而是表示當前對象的類本身的clf。@staticmethod是把函數嵌入到類中的一種方式,函數就屬于類,同時表明函數不需要訪問這個類。通過子類的繼承覆蓋,能更好的組織代碼。
class A(object): def foo(self, x): print("executing foo(%s,%s)" % (self, x)) print('self:', self) @classmethod def class_foo(cls, x): print("executing class_foo(%s,%s)" % (cls, x)) print('cls:', cls) @staticmethod def static_foo(x): print("executing static_foo(%s)" % x) if __name__ == '__main__': a = A() # foo方法綁定對象A的實例,class_foo方法綁定對象A,static_foo沒有參數綁定。 print(a.foo) # > print(a.class_foo) # > print(a.static_foo) #普通的類方法foo()需要通過self參數隱式的傳遞當前類對象的實例。?@classmethod修飾的方法class_foo()需要通過cls參數傳遞當前類對象。@staticmethod修飾的方法定義與普通函數是一樣的。
self和cls的區別不是強制的,只是PEP8中一種編程風格。self通常用作實例方法的第一參數,cls通常用作類方法的第一參數。即通常用self來傳遞當前類對象的實例,cls傳遞當前類對象。
# foo可通過實例a調用,類對像A直接調用會參數錯誤。a.foo(1)"""executing foo(<__main__.a object at>,1)self: <__main__.a object at>"""A.foo(1)"""Traceback (most recent call last): File "", line 1, in TypeError: foo() missing 1 required positional argument: 'x'"""# 但foo如下方式可以使用正常,顯式的傳遞實例參數a。A.foo(a, 1)"""executing foo(<__main__.a object at>,1)self: <__main__.a object at>"""# class_foo通過類對象或對象實例調用。A.class_foo(1)"""executing class_foo(,1)cls: """a.class_foo(1)"""executing class_foo(,1)cls: """a.class_foo(1) == A.class_foo(1)"""executing class_foo(,1)cls: executing class_foo(,1)cls: True"""# static_foo通過類對象或對象實例調用。A.static_foo(1)"""executing static_foo(1)"""a.static_foo(1)"""executing static_foo(1)"""a.static_foo(1) == A.static_foo(1)"""executing static_foo(1)executing static_foo(1)True"""繼承與覆蓋普通類函數是一樣的。
class B(A): passb = B()b.foo(1)b.class_foo(1)b.static_foo(1)"""executing foo(<__main__.b object at>,1)self: <__main__.b object at>executing class_foo(,1)cls: executing static_foo(1)"""REFERENCE
[1]?5 reasons you need to learn to write Python decorators:?https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators[2]?Meaning of @classmethod and @staticmethod for beginner?:?https://stackoverflow.com/questions/12179271/meaning-of-classmethod-and-staticmethod-for-beginner[3]?staticmethod-and-classmethod:?https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod?rq=1[4]?Python 中的 classmethod 和 staticmethod 有什么具體用途?:?https://www.zhihu.com/question/20021164/answer/537385841[5]?正確理解Python中的 @staticmethod@classmethod方法:?https://zhuanlan.zhihu.com/p/28010894[6]?Python 工匠:使用裝飾器的技巧:?https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/8-tips-on-decorators.md[7]?Finally understanding decorators in Python:?https://pouannes.github.io/blog/decorators/
往期推薦
氣象遇見機器學習
背向NCL,面向對象
告別NCL 擁抱Python
用機器學習應對氣候變化?
xarray尾聲:TIFF與GRIB處理
Nature(2019)-地球系統科學領域的深度學習及其理解
總結
以上是生活随笔為你收集整理的python装饰器_Python装饰器是个什么鬼?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java websocket修改为同步_
- 下一篇: js表格中将每行的两个数据求和显示_py