Python裝飾器的高級用法(翻譯)
原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function
介紹
我寫這篇文章的主要目的是介紹裝飾器的高級用法宣决。如果你對裝飾器知之甚少蚁孔,或者對本文講到的知識點易混淆。我建議你復習下裝飾器基礎教程贞盯。
本教程的目標是介紹裝飾器的一些有趣的用法镀赌。特別是怎樣在類中使用裝飾器氯哮,怎樣給裝飾器傳遞額外的參數。
裝飾器 vs 裝飾器模式
Decorator模式是一個面向對象的設計模式商佛,它允許動態(tài)地往現有的對象添加行為喉钢。當你裝飾了一個對象,在某種程度上良姆,你是在獨立于同一個類的其他實例的基礎上擴展其功能肠虽。
Python裝飾器不是裝飾器模式的實現,它在函數玛追、方法定義的時候添加功能税课,而不是在運行的時候添加。Decorator設計模式本身可以在Python中實現豹缀,因為Python是動態(tài)編程語言伯复,所以沒有必要這樣做慨代。
一個基礎的裝飾器
這是裝飾器的最簡單例子邢笙,在繼續(xù)往下面閱讀之前請確保理解此段代碼。如果你需要更多關于此代碼的解釋侍匙,請復習下基礎裝飾器教程氮惯。
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
帶參數的裝飾器
有時候帶參數的裝飾器會非常有用,這種技術經常用在函數注冊中想暗。在web框架Pyramid中經常有用到妇汗,例如:
@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
return {'project': 'hello decorators'}
比方說,我們有一個用戶可以登錄并且可以和用戶交互的GUI應用程序说莫。用戶和GUI界面的交互觸發(fā)事件杨箭,導致Python函數執(zhí)行。假設有許多使用該圖形界面的用戶储狭,他們各自的權限級別差異很大互婿,不同的功能執(zhí)行需要不同的權限捣郊。比如,考慮以下功能:
# 假設這些函數是存在的
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'] """
# 在這些函數中我們需要實現權限檢查
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 """
一種實現這些權限檢查的方式是實現多個裝飾器慈参,比如:
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 """
但是呛牲,這太可怕了。這需要大量的復制粘貼驮配,每個裝飾器需要一個不同的名字娘扩,如果有任何關于權限檢查的改變,每個裝飾器都需要修改壮锻。就沒有一個裝飾器把以上三個裝飾器的工作都干了的嗎琐旁?
為了解決此問題,我們需要一個返回裝飾器的函數:
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 """
嘗試一下調用delete_user
猜绣,new name
和premium_checkpoint
然后看看發(fā)生了什么旋膳。
premium_checkpoint
和delete_user
產生了一個“permission denied”
的異常,new_game
執(zhí)行正常途事。
下面是帶參數裝飾的一般形式验懊,和例子的使用:
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()
等價于:
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()
類裝飾器
裝飾器不僅可以修飾函數,還可以對類進行裝飾尸变。比如說义图,我們有一個類,該類含有許多重要的方法召烂,我們需要記錄每一個方法執(zhí)行的時間碱工。我們可以使用上述的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
此方法可以運行正常。但是在該類中存在許多多余的代碼奏夫,如果我們想建立更多的類方法并且遺忘了裝飾其中的一個方法怕篷,如果我們不想裝飾該類中的方法了,會發(fā)生什么樣的情況呢酗昼?這可能會存在出現認為錯誤的空間廊谓,如果寫成這樣會更有好:
@time_all_class_methods
class ImportantStuff:
def do_stuff_1(self):
pass
def do_stuff_2(self):
pass
def do_stuff_3(self):
pass
等價于:
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
是怎么工作的呢?
首先麻削,我們需要采用一個類作為參數蒸痹,然后返回一個類,我們也要知道返回的類的功能應該和原始類ImportantStuff
功能一樣呛哟。也就是說叠荠,我們仍然希望做重要的事情,我們希望記錄下每個步驟發(fā)生的時間扫责。我們寫成這樣:
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
總結
在此篇教程中榛鼎,我們給大家展示了一些Python裝飾器使用的技巧-我們介紹了怎么樣把參數傳遞給裝飾器,怎樣裝飾類。但是這僅僅是冰山一角者娱。除了本文介紹的之外蜘渣,還有其他好多裝飾器的使用方法,我們甚至可以使用裝飾器裝飾裝飾器(如果你有機會使用到它肺然,這可能是一個做全面檢查的好方法)蔫缸。Python有一些內置的裝飾器,比如:staticmethod
际起,classmethod
閱讀完本文還需要學習什么呢拾碌?通常是沒有比我在文章中展示的裝飾器更復雜的了,如果你有興趣學習更多關于改變類功能的方法街望,我建議您閱讀下繼承和OOP設計原則校翔。或者你可以試試閱讀一下元類灾前。