Python各種類型裝飾器詳細介紹
裝飾器說明
Python中的裝飾器是一種可以裝飾其它對象的工具。該工具本質上是一個可調用的對象(callable),所以裝飾器一般可以由函數、類來實現。裝飾器本身需要接受一個被裝飾的對象作為參數,該參數通常為函數、方法、類等對象。裝飾器需要返回一個對象,該對象可以是 經過處理的原參數對象、一個包裝且類似原參數的對象;或者返回一個不相幹內容(通常不建議使用)
相信通過上述一段文字的描述,大傢應該更加的迷惑瞭!所以下面我們就結合代碼來理解Python中的裝飾器。
裝飾器分類
最簡單的裝飾器
def warp(obj): return obj
沒錯!!!這就是最簡單的裝飾器,並且是一個沒有任何用處的裝飾器。但是它確實是一個裝飾器,並且可以用的很好。比如:
@warp # 等價於 foo = warp(foo) def foo(): print('hello decorator!') foo() # => hello decorator!
而上面使用瞭裝飾器的代碼,其實我們可以通過其它方式達到相同的效果。具體見下:
def foo(): print('hello decorator!') foo = warp(foo) foo() # => hello decorator!
So,通過最簡單的代碼,我們可以發現裝飾器其實就是接受瞭一個函數(對象),並且返回瞭一個函數(對象)的函數(可調用對象)。
用於修改對象的裝飾器
在理解瞭裝飾器的含義之後,再來看一個稍微有點作用的裝飾器。代碼如下:
def warp(obj): obj.name = 'python' return obj
這個裝飾器在上一個例子的基礎上,隻添加瞭一行代碼,但是卻有瞭實際的作用。它的作用就是給被裝飾的對象,添加一個name屬性並且設置值為python。這個裝飾器的使用效果如下:
@warp # => Bar = warp(Bar) class Bar(object): def __init__(self): pass print(Bar.name) # => python
可以看到實際的使用過程中,warp裝飾器已經成功的給Bar對象添加瞭name屬性。除瞭給類對象添加屬性之外,它還可以給函數對象添加屬性。
@warp # => foo = warp(foo) def foo(): pass print(foo.name) # => python
用於模擬對象的裝飾器–函數裝飾器
上面例子中的裝飾器,是直接修改瞭傳入對象;而裝飾器最常用的方式卻是模擬一個傳入對象。即返回一個和原對象相似的對象(即調用接口完全一樣的另一個對象),並且該模擬對象是包裝瞭原對象在內的。具體代碼如下:
def outer(func): # 函數裝飾器 def inner(): func() return inner
上面是一個函數裝飾器,即用來修飾函數的裝飾器。因為它返回瞭一個模擬func對象的inner對象。而這裡inner對象是一個函數,所以這個裝飾器隻能裝飾函數。(因為inner對象隻能模擬func這樣的函數對象,不能模擬class對象)
@outer # foo = outer(foo) def foo(): print('hello foo') foo() # => hello foo
上述代碼中最後一行foo(),其實質上是執行的inner()。為瞭證明這一點,我們可以在inner中打印一條信息。並查看下foo的__name__屬性。
def outer(func): # 函數裝飾器 def inner(): print('hello inner') func() return inner @outer # foo = outer(foo) def foo(): print('hello foo') print(foo.__name__) foo()
上述代碼執行後的結果如下:
inner hello inner hello foo
可以看到首先打印的是 foo.__name__代碼,註意內容是inner而不是foo(說明其本質上是inner函數);其次打印的時候,先打印inner函數中的內容,後打印foo函數中的內容。
用於模擬對象的裝飾器–類方法裝飾器
與函數裝飾器類似的還有類方法裝飾器,其作用相同,格式相近。隻是些微有些區別,下面就是類方法裝飾器的代碼。
def outer(obj): # 類方法裝飾器 def inner(self): print('hello inner') obj(self) return inner class Zoo(object): def __init__(self): pass @outer # => zoo = outer(zoo) def zoo(self): print('hello zoo') zoo = Zoo() print(zoo.zoo.__name__) zoo.zoo()
可以看到類方法裝飾器和函數裝飾器,唯一的區別就是多瞭一個默認的self參數;這是因為類方法本身就比函數多這麼一個參數。其執行的結果如下:
inner hello inner hello zoo
所以最後一行代碼zoo.zoo函數執行的其實是inner函數。
用於模擬對象的裝飾器–類裝飾器
裝飾器除瞭可以裝飾函數、方法之外,還可以裝飾器類對象。具體的代碼如下:
def outer(clss): # 類裝飾器 class Inner(object): def __init__(self): self.clss = clss() def __getattr__(self, attr): return getattr(self.clss, attr) return Inner @outer # Zoo = outer(Zoo) class Zoo(object): def __init__(self): pass def say(self): print('hello world!') zoo = Zoo() print(zoo.__class__) # <class '__main__.outer.<locals>.Inner'> zoo.say() # hello world!
通過代碼可以看出,類裝飾器與函數裝飾器類似。即模擬一個與原參數接口一致的類對象。所以對於模擬類的裝飾器,隻能用在其可以模擬的對象之上,並不能互相修飾其它類型的對象。
特殊應用的裝飾器
上面都是比較常規的裝飾器,python中還有另外一些特殊的裝飾器。比如:類靜態屬性裝飾器。比如下面的代碼:
class Foo(object): def __init__(self, height, weigth): self.height = height self.weigth = weigth @property def ratio(self): return self.height / self.weigth foo = Foo(176, 120) print(foo.ratio) # => 1.4666666666666666
上述代碼中的@property裝飾器就是一個特殊的裝飾器,它把ratio方法變成瞭一個屬性。從最後一句調用代碼可以看出,使用的是foo.ratio而不是foo.ratio()。
對於這類裝飾器需要Python的特定屬性和機制的支持才可以實現,不同特性的裝飾器所需機制不同。如上述代碼中的@property裝飾器就可以使用下面的代碼來實現。
class Prop(object): def __init__(self, fget): self.fget = fget def __get__(self, instance, owner): return self.fget(instance)
具體的使用效果如下:
class Foo(object): def __init__(self, height, weigth): self.height = height self.weigth = weigth @Prop def ratio(self): return self.height / self.weigth foo = Foo(176, 120) print(foo.ratio) # => 1.4666666666666666
可以看到效果和原生的@property裝飾器是一樣的。
類實現的裝飾器
在之前對於裝飾器的說明中,有說道裝飾器是一個callable對象。除瞭函數可以實現裝飾器之外,還可以通過類來實現。那麼類實現裝飾器的具體代碼如下:
class Warp(object): def __init__(self): pass def __call__(self, obj): obj.name = 'warp' return obj
這個類裝飾器實現的功能,也是給傳入的對象添加name屬性,並設置其值為warp。其調用效果如下:
@Warp() def foo(): pass print(foo.name) # => warp
裝飾帶參數/返回值的對象
前面列舉的所有例子,被裝飾的對象都是無參數的。如果你需要裝飾一個帶參數的對象。那麼就需要響應的修改下裝飾器代碼瞭。註意:這裡特指那些模擬類型的裝飾器。即函數裝飾器、類方法裝飾器、類裝飾器。
假設我們先有一個帶參數的函數,其內容如下:
def add(x, y): return x * y
如果使用原來的函數裝飾器,肯定就會出錯。主要因為這個函數帶參數,並且也有返回值。而原來的函數裝飾器則不能支持,原函數裝飾器如下:
def outer(func): # 函數裝飾器 def inner(): func() return inner
可以看到inner模擬的僅僅是一個無參數、無返回值的對象。所以需要進行如下的修改:
def outer(func): # 函數裝飾器 def inner(x, y): print('hello inner') return func(x, y) return inner
這樣的函數裝飾器就可以裝飾add函數瞭。因為inner函數添加瞭x,y參數,調用func對象時也添加瞭參數,並且返回瞭func對象的返回值。具體使用效果如下:
@outer def add(x, y): return x * y print(add(2, 3)) # => 6
上述代碼雖然可以實現add的裝飾功能,但是如果現在我們在出現一個三個參數的函數需要裝飾,或者一個帶默認值參數的韓式需要裝飾怎麼辦。我們不可能為沒一個不同參數的函數都寫一個相同功能的裝飾器。所以終極的函數裝飾器的寫法如下:
def outer(func): # 函數裝飾器 def inner(*args, **kwargs): print('hello inner') return func(*args, **kwargs) return inner
這裡使用瞭python中動態參數的概念,這樣裝飾器就可以支持任意的組合參數的函數瞭。
裝飾器帶參數
上面說到的是被修飾的對象帶參數的情況,還有一種情況就是裝飾器本身希望支持帶參數。這種情況類似於函數模塊通過帶參數可以更加靈活的道理一樣。通過給裝飾器帶上參數,可以使得裝飾器的功能更加的靈活。代碼如下:
url_mapping = {} def route(url): def decorator(func): # 函數裝飾器 url_mapping[url] = func return func return decorator
上面是一個URL路由映射的裝飾器,可以給不同的函數綁定不同的路由。如果裝飾器不能帶參數,則無法實現這樣的功能。其使用效果如下:
@route('/home') def home(): pass @route('/index') def index(): pass print(url_mapping) # => {'/home': <function home at 0x01DAD810>, '/index': <function index at 0x01DAD7C8>}
裝飾器應用
Python裝飾器的應用比較廣泛,大部分場景的公共處理邏輯都可以使用裝飾器去簡化。(使用上類似於JAVA中的註解)一般比較常見的場景比如:
日志記錄
權限驗證單
例模式競爭
資源管理
到此這篇關於Python各種類型裝飾器詳細介紹的文章就介紹到這瞭,更多相關Python裝飾器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!