Python各種類(lèi)型裝飾器詳細(xì)介紹
裝飾器說(shuō)明
Python中的裝飾器是一種可以裝飾其它對(duì)象的工具。該工具本質(zhì)上是一個(gè)可調(diào)用的對(duì)象(callable),所以裝飾器一般可以由函數(shù)、類(lèi)來(lái)實(shí)現(xiàn)。裝飾器本身需要接受一個(gè)被裝飾的對(duì)象作為參數(shù),該參數(shù)通常為函數(shù)、方法、類(lèi)等對(duì)象。裝飾器需要返回一個(gè)對(duì)象,該對(duì)象可以是 經(jīng)過(guò)處理的原參數(shù)對(duì)象、一個(gè)包裝且類(lèi)似原參數(shù)的對(duì)象;或者返回一個(gè)不相干內(nèi)容(通常不建議使用)
相信通過(guò)上述一段文字的描述,大家應(yīng)該更加的迷惑了!所以下面我們就結(jié)合代碼來(lái)理解Python中的裝飾器。
裝飾器分類(lèi)
最簡(jiǎn)單的裝飾器
def warp(obj):
return obj
沒(méi)錯(cuò)?。?!這就是最簡(jiǎn)單的裝飾器,并且是一個(gè)沒(méi)有任何用處的裝飾器。但是它確實(shí)是一個(gè)裝飾器,并且可以用的很好。比如:
@warp # 等價(jià)于 foo = warp(foo)
def foo():
print('hello decorator!')
foo() # => hello decorator!
而上面使用了裝飾器的代碼,其實(shí)我們可以通過(guò)其它方式達(dá)到相同的效果。具體見(jiàn)下:
def foo():
print('hello decorator!')
foo = warp(foo)
foo() # => hello decorator!
So,通過(guò)最簡(jiǎn)單的代碼,我們可以發(fā)現(xiàn)裝飾器其實(shí)就是接受了一個(gè)函數(shù)(對(duì)象),并且返回了一個(gè)函數(shù)(對(duì)象)的函數(shù)(可調(diào)用對(duì)象)。
用于修改對(duì)象的裝飾器
在理解了裝飾器的含義之后,再來(lái)看一個(gè)稍微有點(diǎn)作用的裝飾器。代碼如下:
def warp(obj):
obj.name = 'python'
return obj
這個(gè)裝飾器在上一個(gè)例子的基礎(chǔ)上,只添加了一行代碼,但是卻有了實(shí)際的作用。它的作用就是給被裝飾的對(duì)象,添加一個(gè)name屬性并且設(shè)置值為python。這個(gè)裝飾器的使用效果如下:
@warp # => Bar = warp(Bar)
class Bar(object):
def __init__(self):
pass
print(Bar.name) # => python
可以看到實(shí)際的使用過(guò)程中,warp裝飾器已經(jīng)成功的給Bar對(duì)象添加了name屬性。除了給類(lèi)對(duì)象添加屬性之外,它還可以給函數(shù)對(duì)象添加屬性。
@warp # => foo = warp(foo)
def foo():
pass
print(foo.name) # => python
用于模擬對(duì)象的裝飾器--函數(shù)裝飾器
上面例子中的裝飾器,是直接修改了傳入對(duì)象;而裝飾器最常用的方式卻是模擬一個(gè)傳入對(duì)象。即返回一個(gè)和原對(duì)象相似的對(duì)象(即調(diào)用接口完全一樣的另一個(gè)對(duì)象),并且該模擬對(duì)象是包裝了原對(duì)象在內(nèi)的。具體代碼如下:
def outer(func): # 函數(shù)裝飾器
def inner():
func()
return inner
上面是一個(gè)函數(shù)裝飾器,即用來(lái)修飾函數(shù)的裝飾器。因?yàn)樗祷亓艘粋€(gè)模擬func對(duì)象的inner對(duì)象。而這里inner對(duì)象是一個(gè)函數(shù),所以這個(gè)裝飾器只能裝飾函數(shù)。(因?yàn)閕nner對(duì)象只能模擬func這樣的函數(shù)對(duì)象,不能模擬class對(duì)象)
@outer # foo = outer(foo)
def foo():
print('hello foo')
foo() # => hello foo
上述代碼中最后一行foo(),其實(shí)質(zhì)上是執(zhí)行的inner()。為了證明這一點(diǎn),我們可以在inner中打印一條信息。并查看下foo的__name__屬性。
def outer(func): # 函數(shù)裝飾器
def inner():
print('hello inner')
func()
return inner
@outer # foo = outer(foo)
def foo():
print('hello foo')
print(foo.__name__)
foo()
上述代碼執(zhí)行后的結(jié)果如下:
inner hello inner hello foo
可以看到首先打印的是 foo.__name__代碼,注意內(nèi)容是inner而不是foo(說(shuō)明其本質(zhì)上是inner函數(shù));其次打印的時(shí)候,先打印inner函數(shù)中的內(nèi)容,后打印foo函數(shù)中的內(nèi)容。
用于模擬對(duì)象的裝飾器--類(lèi)方法裝飾器
與函數(shù)裝飾器類(lèi)似的還有類(lèi)方法裝飾器,其作用相同,格式相近。只是些微有些區(qū)別,下面就是類(lèi)方法裝飾器的代碼。
def outer(obj): # 類(lèi)方法裝飾器
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()
可以看到類(lèi)方法裝飾器和函數(shù)裝飾器,唯一的區(qū)別就是多了一個(gè)默認(rèn)的self參數(shù);這是因?yàn)轭?lèi)方法本身就比函數(shù)多這么一個(gè)參數(shù)。其執(zhí)行的結(jié)果如下:
inner hello inner hello zoo
所以最后一行代碼zoo.zoo函數(shù)執(zhí)行的其實(shí)是inner函數(shù)。
用于模擬對(duì)象的裝飾器--類(lèi)裝飾器
裝飾器除了可以裝飾函數(shù)、方法之外,還可以裝飾器類(lèi)對(duì)象。具體的代碼如下:
def outer(clss): # 類(lèi)裝飾器
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!
通過(guò)代碼可以看出,類(lèi)裝飾器與函數(shù)裝飾器類(lèi)似。即模擬一個(gè)與原參數(shù)接口一致的類(lèi)對(duì)象。所以對(duì)于模擬類(lèi)的裝飾器,只能用在其可以模擬的對(duì)象之上,并不能互相修飾其它類(lèi)型的對(duì)象。
特殊應(yīng)用的裝飾器
上面都是比較常規(guī)的裝飾器,python中還有另外一些特殊的裝飾器。比如:類(lèi)靜態(tài)屬性裝飾器。比如下面的代碼:
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裝飾器就是一個(gè)特殊的裝飾器,它把ratio方法變成了一個(gè)屬性。從最后一句調(diào)用代碼可以看出,使用的是foo.ratio而不是foo.ratio()。
對(duì)于這類(lèi)裝飾器需要Python的特定屬性和機(jī)制的支持才可以實(shí)現(xiàn),不同特性的裝飾器所需機(jī)制不同。如上述代碼中的@property裝飾器就可以使用下面的代碼來(lái)實(shí)現(xiàn)。
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裝飾器是一樣的。
類(lèi)實(shí)現(xiàn)的裝飾器
在之前對(duì)于裝飾器的說(shuō)明中,有說(shuō)道裝飾器是一個(gè)callable對(duì)象。除了函數(shù)可以實(shí)現(xiàn)裝飾器之外,還可以通過(guò)類(lèi)來(lái)實(shí)現(xiàn)。那么類(lèi)實(shí)現(xiàn)裝飾器的具體代碼如下:
class Warp(object):
def __init__(self):
pass
def __call__(self, obj):
obj.name = 'warp'
return obj
這個(gè)類(lèi)裝飾器實(shí)現(xiàn)的功能,也是給傳入的對(duì)象添加name屬性,并設(shè)置其值為warp。其調(diào)用效果如下:
@Warp()
def foo():
pass
print(foo.name) # => warp
裝飾帶參數(shù)/返回值的對(duì)象
前面列舉的所有例子,被裝飾的對(duì)象都是無(wú)參數(shù)的。如果你需要裝飾一個(gè)帶參數(shù)的對(duì)象。那么就需要響應(yīng)的修改下裝飾器代碼了。注意:這里特指那些模擬類(lèi)型的裝飾器。即函數(shù)裝飾器、類(lèi)方法裝飾器、類(lèi)裝飾器。
假設(shè)我們先有一個(gè)帶參數(shù)的函數(shù),其內(nèi)容如下:
def add(x, y):
return x * y
如果使用原來(lái)的函數(shù)裝飾器,肯定就會(huì)出錯(cuò)。主要因?yàn)檫@個(gè)函數(shù)帶參數(shù),并且也有返回值。而原來(lái)的函數(shù)裝飾器則不能支持,原函數(shù)裝飾器如下:
def outer(func): # 函數(shù)裝飾器
def inner():
func()
return inner
可以看到inner模擬的僅僅是一個(gè)無(wú)參數(shù)、無(wú)返回值的對(duì)象。所以需要進(jìn)行如下的修改:
def outer(func): # 函數(shù)裝飾器
def inner(x, y):
print('hello inner')
return func(x, y)
return inner
這樣的函數(shù)裝飾器就可以裝飾add函數(shù)了。因?yàn)閕nner函數(shù)添加了x,y參數(shù),調(diào)用func對(duì)象時(shí)也添加了參數(shù),并且返回了func對(duì)象的返回值。具體使用效果如下:
@outer
def add(x, y):
return x * y
print(add(2, 3)) # => 6
上述代碼雖然可以實(shí)現(xiàn)add的裝飾功能,但是如果現(xiàn)在我們?cè)诔霈F(xiàn)一個(gè)三個(gè)參數(shù)的函數(shù)需要裝飾,或者一個(gè)帶默認(rèn)值參數(shù)的韓式需要裝飾怎么辦。我們不可能為沒(méi)一個(gè)不同參數(shù)的函數(shù)都寫(xiě)一個(gè)相同功能的裝飾器。所以終極的函數(shù)裝飾器的寫(xiě)法如下:
def outer(func): # 函數(shù)裝飾器
def inner(*args, **kwargs):
print('hello inner')
return func(*args, **kwargs)
return inner
這里使用了python中動(dòng)態(tài)參數(shù)的概念,這樣裝飾器就可以支持任意的組合參數(shù)的函數(shù)了。
裝飾器帶參數(shù)
上面說(shuō)到的是被修飾的對(duì)象帶參數(shù)的情況,還有一種情況就是裝飾器本身希望支持帶參數(shù)。這種情況類(lèi)似于函數(shù)模塊通過(guò)帶參數(shù)可以更加靈活的道理一樣。通過(guò)給裝飾器帶上參數(shù),可以使得裝飾器的功能更加的靈活。代碼如下:
url_mapping = {}
def route(url):
def decorator(func): # 函數(shù)裝飾器
url_mapping[url] = func
return func
return decorator
上面是一個(gè)URL路由映射的裝飾器,可以給不同的函數(shù)綁定不同的路由。如果裝飾器不能帶參數(shù),則無(wú)法實(shí)現(xiàn)這樣的功能。其使用效果如下:
@route('/home')
def home():
pass
@route('/index')
def index():
pass
print(url_mapping) # => {'/home': <function home at 0x01DAD810>, '/index': <function index at 0x01DAD7C8>}
裝飾器應(yīng)用
Python裝飾器的應(yīng)用比較廣泛,大部分場(chǎng)景的公共處理邏輯都可以使用裝飾器去簡(jiǎn)化。(使用上類(lèi)似于JAVA中的注解)一般比較常見(jiàn)的場(chǎng)景比如:
日志記錄
權(quán)限驗(yàn)證單
例模式競(jìng)爭(zhēng)
資源管理
到此這篇關(guān)于Python各種類(lèi)型裝飾器詳細(xì)介紹的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用pushplus+python監(jiān)控亞馬遜到貨動(dòng)態(tài)推送微信
這篇文章主要介紹了用pushplus+python監(jiān)控亞馬遜到貨動(dòng)態(tài)推送微信的示例,幫助大家利用python搶購(gòu)商品,感興趣的朋友可以了解下2021-01-01
scrapy處理python爬蟲(chóng)調(diào)度詳解
在本篇文章里小編給大家整理的是一篇關(guān)于scrapy處理python爬蟲(chóng)調(diào)度的相關(guān)內(nèi)容,有興趣的朋友們學(xué)習(xí)下。2020-11-11
Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了Python操作SQLite/MySQL/LMDB數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11
python腳本請(qǐng)求數(shù)量達(dá)到上限,http請(qǐng)求重試問(wèn)題
這篇文章主要介紹了python腳本請(qǐng)求數(shù)量達(dá)到上限,http請(qǐng)求重試問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Python運(yùn)維開(kāi)發(fā)之psutil庫(kù)的使用詳解
這篇文章主要介紹了Python運(yùn)維開(kāi)發(fā)之psutil庫(kù)的使用,psutil能夠輕松實(shí)現(xiàn)獲取系統(tǒng)運(yùn)行的進(jìn)程和系統(tǒng)利用率。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
5道關(guān)于python基礎(chǔ) while循環(huán)練習(xí)題
這篇文章主要給大家分享的是5道關(guān)于python基礎(chǔ) while循環(huán)練習(xí)題,無(wú)論學(xué)習(xí)什么語(yǔ)言,練習(xí)都是必不可少的,下面文章的練習(xí)題挺精湛的,需要的朋友可以參考一下2021-11-11

