深入了解Python裝飾器的高級(jí)用法
原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function
介紹
我寫(xiě)這篇文章的主要目的是介紹裝飾器的高級(jí)用法。如果你對(duì)裝飾器知之甚少,或者對(duì)本文講到的知識(shí)點(diǎn)易混淆。我建議你復(fù)習(xí)下裝飾器基礎(chǔ)教程。
本教程的目標(biāo)是介紹裝飾器的一些有趣的用法。特別是怎樣在類中使用裝飾器,怎樣給裝飾器傳遞額外的參數(shù)。
裝飾器 vs 裝飾器模式
Decorator模式是一個(gè)面向?qū)ο蟮脑O(shè)計(jì)模式,它允許動(dòng)態(tài)地往現(xiàn)有的對(duì)象添加行為。當(dāng)你裝飾了一個(gè)對(duì)象,在某種程度上,你是在獨(dú)立于同一個(gè)類的其他實(shí)例的基礎(chǔ)上擴(kuò)展其功能。
Python裝飾器不是裝飾器模式的實(shí)現(xiàn),它在函數(shù)、方法定義的時(shí)候添加功能,而不是在運(yùn)行的時(shí)候添加。Decorator設(shè)計(jì)模式本身可以在Python中實(shí)現(xiàn),因?yàn)镻ython是動(dòng)態(tài)編程語(yǔ)言,所以沒(méi)有必要這樣做。
一個(gè)基礎(chǔ)的裝飾器
這是裝飾器的最簡(jiǎn)單例子,在繼續(xù)往下面閱讀之前請(qǐng)確保理解此段代碼。如果你需要更多關(guān)于此代碼的解釋,請(qǐng)復(fù)習(xí)下基礎(chǔ)裝飾器教程。
def time_this(original_function):
def new_function(*args, **kwargs):
import datetime
before = datetime.datetime.now()
x = original_function(*args, **kwargs)
after = datetime.datetime.now()
print("Elapsed Time = {}".format(after-before))
return x
return new_function
@time_this
def func_a(stuff):
import time
time.sleep(stuff)
func_a(3)
# out:
Elapsed Time = 0:00:03.012472
帶參數(shù)的裝飾器
有時(shí)候帶參數(shù)的裝飾器會(huì)非常有用,這種技術(shù)經(jīng)常用在函數(shù)注冊(cè)中。在web框架Pyramid中經(jīng)常有用到,例如:
@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
return {'project': 'hello decorators'}
比方說(shuō),我們有一個(gè)用戶可以登錄并且可以和用戶交互的GUI應(yīng)用程序。用戶和GUI界面的交互觸發(fā)事件,導(dǎo)致Python函數(shù)執(zhí)行。假設(shè)有許多使用該圖形界面的用戶,他們各自的權(quán)限級(jí)別差異很大,不同的功能執(zhí)行需要不同的權(quán)限。比如,考慮以下功能:
# 假設(shè)這些函數(shù)是存在的 def current_user_id(): """ this function returns the current logged in user id, if the use is not authenticated the return None """ def get_permissions(iUserId): """ returns a list of permission strings for the given user. For example ['logged_in','administrator','premium_member'] """ # 在這些函數(shù)中我們需要實(shí)現(xiàn)權(quán)限檢查 def delete_user(iUserId): """ delete the user with the given Id. This function is only accessable to users with administrator permissions """ def new_game(): """ any logged in user can start a new game """ def premium_checkpoint(): """ save the game progress, only accessable to premium members """
一種實(shí)現(xiàn)這些權(quán)限檢查的方式是實(shí)現(xiàn)多個(gè)裝飾器,比如:
def requires_admin(fn):
def ret_fn(*args,**kwargs):
lPermissions = get_permissions(current_user_id())
if 'administrator' in lPermissions:
return fn(*args,**kwargs)
else: raise Exception("Not allowed")
return ret_fn
def requires_logged_in(fn):
def ret_fn(*args,**kwargs):
lPermissions = get_permissions(current_user_id())
if 'logged_in' in lPermissions:
return fn(*args,**kwargs)
else:
raise Exception("Not allowed")
return ret_fn
def requires_premium_member(fn):
def ret_fn(*args,**kwargs):
lPermissions = get_permissions(current_user_id())
if 'premium_member' in lPermissions:
return fn(*args,**kwargs)
else:
raise Exception("Not allowed")
return ret_fn
@requires_admin
def delete_user(iUserId):
""" delete the user with the given Id. This function is only accessable to users with administrator permissions """
@requires_logged_in
def new_game():
""" any logged in user can start a new game """ @requires_premium_member
def premium_checkpoint():
""" save the game progress, only accessable to premium members """
但是,這太可怕了。這需要大量的復(fù)制粘貼,每個(gè)裝飾器需要一個(gè)不同的名字,如果有任何關(guān)于權(quán)限檢查的改變,每個(gè)裝飾器都需要修改。就沒(méi)有一個(gè)裝飾器把以上三個(gè)裝飾器的工作都干了的嗎?
為了解決此問(wèn)題,我們需要一個(gè)返回裝飾器的函數(shù):
def requires_permission(sPermission):
def decorator(fn):
def decorated(*args,**kwargs):
lPermissions = get_permissions(current_user_id())
if sPermission in lPermissions:
return fn(*args,**kwargs)
raise Exception("permission denied")
return decorated
return decorator
def get_permissions(iUserId):
# this is here so that the decorator doesn't throw NameErrors
return ['logged_in',]
def current_user_id():
#ditto on the NameErrors
return 1
#and now we can decorate stuff...
@requires_permission('administrator')
def delete_user(iUserId):
""" delete the user with the given Id. This function is only accessible to users with administrator permissions """
@requires_permission('logged_in')
def new_game():
""" any logged in user can start a new game """ @requires_permission('premium_member')
def premium_checkpoint():
""" save the game progress, only accessable to premium members """
嘗試一下調(diào)用delete_user,new name和premium_checkpoint然后看看發(fā)生了什么。
premium_checkpoint 和delete_user 產(chǎn)生了一個(gè)“permission denied”的異常,new_game執(zhí)行正常。
下面是帶參數(shù)裝飾的一般形式,和例子的使用:
def outer_decorator(*outer_args,**outer_kwargs):
def decorator(fn):
def decorated(*args,**kwargs):
do_something(*outer_args,**outer_kwargs)
return fn(*args,**kwargs)
return decorated
return decorator
@outer_decorator(1,2,3)
def foo(a,b,c):
print(a)
print(b)
print(c)
foo()
等價(jià)于:
def decorator(fn):
def decorated(*args,**kwargs):
do_something(1,2,3)
return fn(*args,**kwargs)
return decorated
return decorator
@decorator
def foo(a,b,c):
print(a)
print(b)
print(c)
foo()
類裝飾器
裝飾器不僅可以修飾函數(shù),還可以對(duì)類進(jìn)行裝飾。比如說(shuō),我們有一個(gè)類,該類含有許多重要的方法,我們需要記錄每一個(gè)方法執(zhí)行的時(shí)間。我們可以使用上述的time_this裝飾此類:
class ImportantStuff(object): @time_this def do_stuff_1(self): pass @time_this def do_stuff_2(self): pass @time_this def do_stuff_3(self): pass
此方法可以運(yùn)行正常。但是在該類中存在許多多余的代碼,如果我們想建立更多的類方法并且遺忘了裝飾其中的一個(gè)方法,如果我們不想裝飾該類中的方法了,會(huì)發(fā)生什么樣的情況呢?這可能會(huì)存在出現(xiàn)認(rèn)為錯(cuò)誤的空間,如果寫(xiě)成這樣會(huì)更有好:
@time_all_class_methods
class ImportantStuff:
def do_stuff_1(self):
pass
def do_stuff_2(self):
pass
def do_stuff_3(self):
pass
等價(jià)于:
class ImportantStuff:
def do_stuff_1(self):
pass
def do_stuff_2(self):
pass
def do_stuff_3(self):
pass
ImportantStuff = time_all_class_methods(ImportantStuff)
那么time_all_class_methods是怎么工作的呢?
首先,我們需要采用一個(gè)類作為參數(shù),然后返回一個(gè)類,我們也要知道返回的類的功能應(yīng)該和原始類ImportantStuff功能一樣。也就是說(shuō),我們?nèi)匀幌M鲋匾氖虑?,我們希望記錄下每個(gè)步驟發(fā)生的時(shí)間。我們寫(xiě)成這樣:
def time_this(original_function):
print("decorating")
def new_function(*args,**kwargs):
print("starting timer")
import datetime
before = datetime.datetime.now()
x = original_function(*args,**kwargs)
after = datetime.datetime.now()
print("Elapsed Time = {0}".format(after-before))
return x
return new_function
def time_all_class_methods(Cls):
class NewCls:
def __init__(self,*args,**kwargs):
self.oInstance = Cls(*args,**kwargs)
def __getattribute__(self,s):
try:
x = super(NewCls,self).__getattribute__(s)
except AttributeError:
pass
else:
return x
x = self.oInstance.__getattribute__(s)
if type(x) == type(self.__init__):
return time_this(x)
else:
return x
return NewCls
@time_all_class_methods
class Foo:
def a(self):
print("entering a")
import time
time.sleep(3)
print("exiting a")
oF = Foo()
oF.a()
# out:
decorating
starting timer
entering a
exiting a
Elapsed Time = 0:00:03.006767
總結(jié)
在此篇教程中,我們給大家展示了一些Python裝飾器使用的技巧-我們介紹了怎么樣把參數(shù)傳遞給裝飾器,怎樣裝飾類。但是這僅僅是冰山一角。除了本文介紹的之外,還有其他好多裝飾器的使用方法,我們甚至可以使用裝飾器裝飾裝飾器(如果你有機(jī)會(huì)使用到它,這可能是一個(gè)做全面檢查的好方法)。Python有一些內(nèi)置的裝飾器,比如:staticmethod,classmethod
閱讀完本文還需要學(xué)習(xí)什么呢?通常是沒(méi)有比我在文章中展示的裝飾器更復(fù)雜的了,如果你有興趣學(xué)習(xí)更多關(guān)于改變類功能的方法,我建議您閱讀下繼承和OOP設(shè)計(jì)原則?;蛘吣憧梢栽囋囬喿x一下元類。
以上就是深入了解Python裝飾器的高級(jí)用法的詳細(xì)內(nèi)容,更多關(guān)于Python裝飾器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何在Python函數(shù)執(zhí)行前后增加額外的行為
有的時(shí)候會(huì)需要在函數(shù)前后添點(diǎn)額外的功能(比如過(guò)濾、計(jì)時(shí)等)時(shí),以前總是首先想到裝飾器。最近學(xué)習(xí)了Python的上下文管理器,所以本文就給大家介紹了如何在Python函數(shù)執(zhí)行前后增加額外的行為,有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-10-10
Django-celery-beat動(dòng)態(tài)添加周期性任務(wù)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Django-celery-beat動(dòng)態(tài)添加周期性任務(wù)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Python?socket之TCP通信及下載文件的實(shí)現(xiàn)
本文主要介紹了Python?socket之TCP通信及下載文件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
淺談python3.6的tkinter運(yùn)行問(wèn)題
今天小編就為大家分享一篇淺談python3.6的tkinter運(yùn)行問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
如何使用django-treebeard實(shí)現(xiàn)樹(shù)類型存儲(chǔ)與編輯
這篇文章主要介紹了使用django-treebeard實(shí)現(xiàn)樹(shù)類型存儲(chǔ)與編輯的宣相關(guān)操作代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08
python安裝cxOracle避坑總結(jié)不要直接pip install
這篇文章主要為大家介紹了python安裝cx_Oracle是遇到的一些問(wèn)題的解決辦法的總結(jié),來(lái)幫大家避避坑有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步2021-10-10
Django1.7+python 2.78+pycharm配置mysql數(shù)據(jù)庫(kù)教程
原本感覺(jué)在Django1.7+python 2.78+pycharm環(huán)境下配置mysql數(shù)據(jù)庫(kù)是件很容易的事情,結(jié)果具體操作的時(shí)候才發(fā)現(xiàn),問(wèn)題還是挺多的,這里記錄一下最終的配置結(jié)果,給需要的小伙伴參考下吧2014-11-11
Python打包后的exe還原成.py的實(shí)現(xiàn)步驟
本文主要介紹了Python打包后的exe還原成.py的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
python實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)感知器算法
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)感知器算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12

