本系列目的:但願能夠經過一篇文章,不望鞭辟入裏,但求在工程應用中駕輕就熟。python
包含八節內容:閉包(實現裝飾器的基礎),不帶參數的函數裝飾器,帶參數的函數裝飾器,不帶參數的類裝飾器,帶參數的類裝飾器,經常使用內建裝飾器,裝飾器總結(套路總結),裝飾器經典實例(單例模式)。git
閉包github
裝飾器是經過閉包實現的。閉包是一個比較複雜的話題,深了說能夠講到python對常量表和符號表的處理方式。這裏只作簡單介紹。我的認爲只要記住如下三個特性,就明白了閉包的概念。設計模式
不帶參數函數裝飾器
假設有一個需求,咱們須要在每一個函數運行時,打印當下時刻的時間戳。那麼有如下兩種寫法:安全
不使用裝飾器
編寫一個打印時間戳的工具函數,編寫一個業務函數。傳入業務函數對象到工具函數中,實現打印時間戳並執行業務函數的需求。代碼以下:多線程
import time def f(): print("f is running!") def f1(): print("f1 is running!") def print_running_time(f): print("running time:", time.time()) f() print_running_time(f) print_running_time(f1) >>> ('running time:', 1588864281.154459) f is running! ('running time:', 1588864281.154483) f1 is running!
使用裝飾器(函數裝飾器)閉包
編寫一個打印時間戳的裝飾器函數,編寫一個業務函數。裝飾器函數裝飾業務函數,實現打印時間戳並執行業務函數的需求。代碼以下:app
import time def print_running_time(f): def wrapper(): print("running time:", time.time()) f() return wrapper @print_running_time def f(): print("f is running!") @print_running_time def f1(): print("f1 is running!") f() f1() >>> ('running time:', 1588864281.154459) f is running! ('running time:', 1588864281.154483) f1 is running!
以上兩種寫法對比
經過對比以上兩種寫法,咱們能夠發現最明顯的區別是代碼在運行時,第一種寫法執行的print_running_time函數,第二種寫法執行的是f函數。那麼明顯第二種寫法中抽象出的語義更加接近咱們的業務需求。在一樣需求增長的狀況下,第一種寫法須要寫更多的工具函數,而且在執行業務函數時須要進行多層嵌套,極大地增長了代碼的複雜度。第二種寫法能夠增長多個裝飾函數裝飾到業務函數上方,在多需求下依舊保持代碼的可讀性和層次感,功能的獨立性和擴展性。函數
初探裝飾器原理工具
裝飾器的代碼運行分爲兩步,裝飾器初始化(在運行至被裝飾函數定義處)和執行被裝飾函數(在運行至被裝飾函數調用處)
以第二種寫法裝飾器的寫法爲例,裝飾器的原理以下:
在代碼加載過程當中,代碼從上往下執行,那麼在執行到#1代碼時,至關於執行了#2代碼。(#1和#2的代碼是等價的。@docorator_func裝飾f,就至關於執行decorator_func(f))。根據#2代碼中print_running_time可知,執行print_running_time(f)的返回值是wrapper(注意返回的是函數對象wrapper,不是wrapper()).
# 1 @print_running_time def f(): print("f is running!") # 2 print_running_time(f) # 3 def print_running_time(f): #3.1 def wrapper(): #3.2 print("running time:", time.time()) #3.3 f() #3.4 return wrapper # 4 f()
那麼源碼中#1處的三行代碼,返回值爲wrapper,即至關於經過增長@裝飾函數,f如今已經指向了wrapper對象。
根據以前提到閉包的特性:閉包能夠訪問做用以外的非局部變量,能夠將做用域"封裝",在閉包以外訪問閉包內的變量。因此wrapper能夠訪問#3.1中到本身外層函數的參數f變量(被裝飾器函數對象),而且能夠封裝wrapper做用域,保存f變量。
執行#4處的業務函數f(),即執行#3.2的wrapper()代碼,即執行#3.3和#3.4代碼。
整個過程當中須要注意的是,在代碼運行至#1時,f做爲裝飾器參數被#3.2wrapper閉包保留,在#1執行完以後,會存在兩個f對象,#4的f對象指向wrapper,#3.4的f對象依舊是#1處的f對象。
執行流程爲f()> wrapper()> 執行#7.1 #7.2代碼==>打印當前時刻時間戳,順利執行了原有的業務函數。
帶參數的函數裝飾器
如今有新的需求,根據調試和生產環境的不一樣,須要往復地開關打印時間戳的功能,那麼這時就須要爲裝飾器函數增長參數,來做爲是否打印時間戳的開關。如如下代碼所示,f()會打印當前函數的執行時間,f1()則不會打印函數的執行時間
import time # 1 def print_running_time(*flag): def outer_wrapper(f): def inner_wrapper(): if flag: print("running time:", time.time()) f() return inner_wrapper return outer_wrapper # 2 @print_running_time(1) def f(): print("f is running!") # 3 @print_running_time() def f1(): print("f1 is running!") f() f1() >>> ('running time:', 1588860065.265516) f is running! f1 is running!
帶參數裝飾器原理
不帶參數類裝飾器
準確來講,裝飾器的本質是將一個可調用對象做爲參數傳入另外一個可調用對象,而後經過閉包保存變量,在適當的時候執行。咱們知道,python有兩個特性
根據裝飾器的本質和以上Python兩個特性能夠得出如下結論:
將以前簡單函數裝飾器的例子換成類裝飾器,代碼以下(爲了與以前代碼保持一致,因此類名不符合Python命名規範):
import time class print_running_time: def __init__(self, f): # 至關於閉包,經過實例屬性保存變量f實現閉包中的變量封裝 self.f = f def __call__(self): # 類實例能夠被調用 print("running time:", time.time()) return self.f() @print_running_time def f(): print("f is running!") @print_running_time def f1(): print("f1 is running!") f() f1() >>> 輸出同簡單函數裝飾器
帶參數的類裝飾器
將以前簡單函數裝飾器的例子換成類裝飾器,代碼以下:
import time class print_running_time: def __init__(self, *flag): # 至關於閉包,經過實例屬性保存變量flag實現閉包中的變量封裝 self.flag = flag def __call__(self, f): # 類實例能夠被調用,傳入業務函數f def wrapper(): if self.flag: print("running time:", time.time()) f() return wrapper @print_running_time(1) def f(): print("f is running!") @print_running_time() def f1(): print("f1 is running!") f() f1() >>> 輸出同帶參數的函數裝飾器
經常使用內建裝飾器
裝飾器是Python最重要的特性之一,Python實現了不少對裝飾器的支持
wraps
wraps能夠保留被裝飾函數的__doc__。以下代碼所示,wraps裝飾器的開關會致使打印f.__doc__出現兩種結果
import time from functools import wraps # 1 def print_running_time(f): @wraps(f) # 1.1 def wrapper(): '''the func wrapper''' # 1.3 print("running time:", time.time()) f() return wrapper # 2 @print_running_time def f(): '''the func f''' # 2.1 print("f is running!") print(f.__doc__) >>> the func f
property 、setter、 deleter
這三個是孿生兄弟,其中property用的最多,setter和deleter依附property。
class Student(object): @property def name(self): return self._name @name.setter def name(self, name): if len(name) < 2: raise ValueError("無名大俠?") self._name = name @name.deleter def name(self): del self._name stu = Student() stu.name = "劉" # name.setter print(stu.name) ## property del stu.name # name.deleter print(stu.name) # raise AttributeError
多裝飾器疊加
多個裝飾器疊加是python中很常見的騷操做,如Flask和Django中都會用到,舉例以下:
import sys # 1 def f1(func): print('f1 start') def wrapper(): # 1.1 print('f1 ' + sys._getframe().f_code.co_name + ' start') func() # 1.2 print('f1 ' + sys._getframe().f_code.co_name + ' end') print('f1 end') return wrapper # 2 def f2(func): print('f2 start') def wrapper(): # 2.1 print('f2 ' + sys._getframe().f_code.co_name + ' start') func() # 2.2 print('f2 ' + sys._getframe().f_code.co_name + ' end') print('f2 end') return wrapper # 3 @f1 @f2 def func(): #3.1 print('the func') #4 func() #4.1 >>> f2 start f2 end f1 start f1 end f1wrapper start f2wrapper start the func f2wrapper end f1wrapper end
>>> f2 start f2 end f1 start f1 end
print('f1 ' + sys._getframe().f_code.co_name + ' start') func() # 1.2 print('f1 ' + sys._getframe().f_code.co_name + ' end')#1.2處的func <=> # 2.1處的wrapper,替換以後代碼以下
print('f1 ' + sys._getframe().f_code.co_name + ' start') print('f2 ' + sys._getframe().f_code.co_name + ' start') func() # 2.2 print('f2 ' + sys._getframe().f_code.co_name + ' end') print('f1 ' + sys._getframe().f_code.co_name + ' end')#2.2處的func即爲#4.1處的func,執行以上代碼,輸出結果以下:
>>> f1wrapper start f2wrapper start the func f2wrapper end f1wrapper end
裝飾器總結
# 1 @decorator_func def func(): pass # 2 decorator_func(func)
外層函數參數爲被裝飾函數對象
內層參數爲被裝飾函數的參數
帶參數的函數裝飾器須要有三層函數:
類裝飾器同理,最外層函數能夠用__init_函數代替,中層(若是有和內層函數寫在__call__中
裝飾器經典實例:單例模式
如下均單進程可行,多線程須要加鎖
單例模式
```python # eg:1 class Singleton: _singleton = None def __new__(cls): if cls._singleton is None: cls._singleton = super().__new__(cls) return cls._singleton ins1 = Singleton() ins2 = Singleton() print(ins1 is ins2) # eg:2 def singleton(cls): ins_pool = {} def inner(): if cls not in ins_pool: ins_pool[cls] = cls() return ins_pool[cls] return inner @singleton class Cls: def __init__(self): pass ins1 = Cls() ins2 = Cls() print(ins1 is ins2) # eg:3 class Singleton: def __init__(self, cls): self.ins_pool = {} self.cls = cls def __call__(self): print(self.ins_pool) if self.cls not in self.ins_pool: self.ins_pool[self.cls] = self.cls() return self.ins_pool[self.cls] @Singleton class Cls: def __init__(self): pass ins1 = Cls() ins2 = Cls() print(ins1 is ins2) ```
但願你們能夠經過本文掌握裝飾器這個殺手級特性。歡迎關注我的博客:藥少敏的博客