亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Python裝飾器以及高級用法

 

介紹

首先我要承認(rèn),裝飾器非常難!你在本教程中看到的一些代碼將會有一些復(fù)雜。大多數(shù)人在學(xué)習(xí)Python時都跟裝飾器做過斗爭,所以如果這對你來說很奇怪,不要感到沮喪,因?yàn)橥瑯拥拇蠖鄶?shù)人都可以克服這種苦難。在本教程中,我將逐步介紹了解裝飾器的過程。首先我假設(shè)你已經(jīng)可以編寫基本函數(shù)和基本類。如果你不能做這些事,那么我建議你在回到這里之前先學(xué)習(xí)如何去做到編寫基本函數(shù)和基本類(除非你迷路了,在這種情況下你可以原諒)。

用例:計(jì)時函數(shù)執(zhí)行

假設(shè)我們正在執(zhí)行一段代碼,執(zhí)行時間比我們想的還要長一些。這段代碼由一堆函數(shù)調(diào)用組成,我們確信這些調(diào)用中至少有一個調(diào)用構(gòu)成了我們代碼中的瓶頸。我們?nèi)绾握业狡款i?現(xiàn)在有一個解決方案,就是我們現(xiàn)在要關(guān)注的解決方案,就是對函數(shù)執(zhí)行進(jìn)行計(jì)時。

讓我們從一個簡單的例子開始。我們只有一個函數(shù)需要計(jì)時,func_a

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()

一種方法是將時鐘代碼放在每個函數(shù)調(diào)用周圍。所以就像這樣:

func_a(current_stuff)

看起來會更像這樣:

before = datetime.datetime.now()
func_a(current_stuff)
after = datetime.datetime.now()
print ("Elapsed Time = {0}".format(after-before))

這樣就可以了。但是如果我們有多次調(diào)用func_a并且我們想要為所有這些計(jì)時會發(fā)生什么呢?我們可以用計(jì)時代碼包圍func_a的每個調(diào)用,但是這樣做也有不好的效果。它只準(zhǔn)備編寫一次計(jì)時代碼。因此,我們將其放在函數(shù)定義中,而不是將其放在函數(shù)之外。

def func_a(stuff):
 before = datetime.datetime.now()
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))

這種方法的好處是:

  1. 我們將代碼放在一個地方,所以如果我們想要更改它(例如,如果我們想將經(jīng)過的時間存儲在數(shù)據(jù)庫或日志中)那么我們只需要在一個地方而不是每一個函數(shù)調(diào)用中更改它
  2. 我們不需要記住每次調(diào)用func_a都要寫四行代碼而不是一行,這是非常好的

好的,但是只需要計(jì)算一個函數(shù)的時間是不現(xiàn)實(shí)的。如果你需要對一件事進(jìn)行計(jì)時,你很有可能需要至少對兩件事進(jìn)行計(jì)時。所以我們會選擇三個。

def func_a(stuff):
 before = datetime.datetime.now()
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))
def func_b(stuff):
 before = datetime.datetime.now()
 do_important_things_4()
 do_important_things_5()
 do_important_things_6()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))
def func_c(stuff):
 before = datetime.datetime.now()
 do_important_things_7()
 do_important_things_8()
 do_important_things_9()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))

這看起來很糟糕。如果我們想要對8個函數(shù)進(jìn)行計(jì)時的時候怎么辦?然后我們決定將計(jì)時的信息存儲在日志文件中。然后我們決定建立一個更好的數(shù)據(jù)庫。我們這里需要的是將一種相同的代碼合并到func_a,func_b和func_c中的方法,這種方法不會讓我們到處復(fù)制粘貼代碼。

一個簡單的繞道:返回函數(shù)的函數(shù)

Python是一種非常特殊的語言,因?yàn)?strong>函數(shù)是第一類對象。這意味著一旦函數(shù)在作用域中被定義,它就可以傳遞給函數(shù),賦值給變量,甚至從函數(shù)返回。這個簡單的事實(shí)是使python裝飾器成為可能的原因。查看下面的代碼,看看你是否可以猜出標(biāo)記為A,B,C和D的行會發(fā)生什么。

def get_function():
 print ("inside get_function") 
 def returned_function(): 
 print("inside returned_function") 
 return 1
 print("outside returned_function")
 return returned_function
returned_function() # A 
x = get_function() # B 
x # C 
x() # D

A

這一行給出了一個NameError并聲明returned_function不存在。但我們只是定義了它,對吧?你在這里需要知道的是,它是在get_function的范圍內(nèi)定義的。也就是說,在get_function里面定義了它。它不是在get_function之外。如果這讓你感到困惑,那么你可以嘗試使用該locals()函數(shù),并閱讀Python的范圍。

B

這行代碼打印出以下內(nèi)容:

inside get_function
outside returned_function

此時Python不執(zhí)行returned_function的任何內(nèi)容。

C

這一行輸出:

<function returned_function at 0x7fdc4463f5f0>

也就是說,get_function()返回的值x本身就是一個函數(shù)。

嘗試再次運(yùn)行B和C行。請注意,每次重復(fù)此過程時,返回的returned_function地址都是不同。每次調(diào)用get_function都會生成新的returned function。

d

因?yàn)閤是函數(shù),所以就可以調(diào)用它。調(diào)用x就是調(diào)用returned_function的一個實(shí)例。這里輸出的是:

inside returned_function
1

也就是說,它打印字符串,并返回值1。

回到時間問題

你現(xiàn)在仍然在看么?如此我們有了新的知識,那么我們?nèi)绾谓鉀Q我們的老問題?我建議我們創(chuàng)建一個函數(shù),讓我們調(diào)用它并稱為time_this,它將接收另一個函數(shù)作為參數(shù),并將參數(shù)函數(shù)封裝在某些計(jì)時代碼中。有點(diǎn)像:

def time_this(original_function): # 1
 def new_function(*args,**kwargs): # 2
 before = datetime.datetime.now() # 3
 x = original_function(*args,**kwargs) # 4
 after = datetime.datetime.now() # 5
 print("Elapsed Time = {0}".format(after-before)) # 6
 return x # 7
 return new_function() # 8

我承認(rèn)它有點(diǎn)瘋狂,所以讓我們一行一行的看下去:

1這只是time_this的原型。time_this是一個函數(shù)就像任何其他函數(shù)一樣,并且只有一個參數(shù)。 2我們在內(nèi)部定義一個函數(shù)time_this。每當(dāng)time_this執(zhí)行時它都會創(chuàng)建一個新函數(shù)。 3計(jì)時代碼,就像之前一樣。 4我們調(diào)用原始函數(shù)并保留結(jié)果以供日后使用。 5,6剩余的計(jì)時代碼。 7new_function必須像原始函數(shù)一樣運(yùn)行,因此返回存儲的結(jié)果。 8返回在time_this中創(chuàng)建的函數(shù)。

現(xiàn)在我們要確保我們的函數(shù)是計(jì)時的:

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
func_a = time_this(func_a) # <---------
def func_b(stuff):
 do_important_things_4()
 do_important_things_5()
 do_important_things_6()
func_b = time_this(func_b) # <---------
def func_c(stuff):
 do_important_things_7()
 do_important_things_8()
 do_important_things_9()
func_c = time_this(func_c) # <---------

看看func_a,當(dāng)我們執(zhí)行時func_a = time_this(func_a)我們用time_this返回的函數(shù)替換func_a。所以我們用一個函數(shù)替換func_A該函數(shù)執(zhí)行一些計(jì)時操作(上面的第3行),將func a的結(jié)果存儲在一個名為x的變量中(第4行),執(zhí)行更多的計(jì)時操作(第5行和第6行),然后返回func_a返回的內(nèi)容。換句話說func_a,仍然以相同的方式調(diào)用并返回相同的東西,它也只是被計(jì)時了。是不是感覺很整潔?

介紹裝飾器

我們所做的工作很好,而且非常棒,但是很難看,非常難讀懂。所以Python可愛的作者給了我們一種不同的,更漂亮的寫作方式:

@time_this
def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()

完全等同于:

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
func_a = time_this(func_a)

這通常被稱為語法糖。@沒有什么神奇的。這只是一個已達(dá)成一致的慣例。沿著這條路上的某個地方?jīng)Q定了。

總結(jié)

裝飾器只是一個返回函數(shù)的函數(shù)。如果這些東西看起來非常的 - 那么請確保以下主題對你有意義然后再回到本教程:

  • Python函數(shù)
  • 范圍
  • Python作為第一類對象(甚至可以查找lambda函數(shù),它可能使它更容易理解)。

另一方面,如果你對更多的話題感興趣的話,你可能會發(fā)現(xiàn):

  • 如裝飾類:

python @add_class_functionality class MyClass: ...

  • 具有更多參數(shù)的裝飾器, 例如:

python @requires_permission(name="edit") def save_changes(stuff): ...

下面就是我要介紹的高級裝飾器的主題。

裝飾器的高級用法

介紹

下面這些旨在介紹裝飾器的一些更有趣的用法。具體來說,如何在類上使用裝飾器,以及如何將額外的參數(shù)傳遞給裝飾器函數(shù)。

裝飾者與裝飾者模式

裝飾器模式是一種面向?qū)ο蟮脑O(shè)計(jì)模式,其允許動態(tài)地將行為添加到現(xiàn)有的對象當(dāng)中。當(dāng)你裝飾對象時,你將以獨(dú)立于同類的其他實(shí)例方式擴(kuò)展它的功能。

Python裝飾器不是裝飾器模式的實(shí)現(xiàn)。Python裝飾器在定義時向函數(shù)和方法添加功能,它們不用于在運(yùn)行時添加功能。裝飾器模式本身可以在Python中實(shí)現(xiàn),但由于Python是Duck-teped的,因此這是一件非常簡單的事情。

一個基本的裝飾

這是裝飾器可以做的一個非常基本的例子。我只是把它作為一個參考點(diǎn)。在繼續(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 = {0}".format(after-before)) 
 return x 
 return new_function 
@time_this
def func_a(stuff):
 import time
 time.sleep(3)
func_a(1)

接受參數(shù)的裝飾器

有時,除了裝飾的函數(shù)之外,裝飾器還可以使用參數(shù)。這種技術(shù)經(jīng)常用于函數(shù)注冊等事情。一個著名的例子是Pyramid Web應(yīng)用程序框架中的視圖配置。例如:

@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
 return {'project': 'hello decorators'}

假設(shè)我們有一個應(yīng)用程序,用戶可以登錄并與一個漂亮的gui(圖形用戶界面)進(jìn)行交互。用戶與gui的交互觸發(fā)事件,而這些事件導(dǎo)致Python函數(shù)被執(zhí)行。讓我們假設(shè)有很多用戶使用這個應(yīng)用程序,并且他們有許多不同的權(quán)限級別。執(zhí)行不同的功能需要不同的權(quán)限類型。例如,考慮以下功能:

#這些功能是存在的
def current_user_id():
 """
 此函數(shù)返回當(dāng)前登錄的用戶ID,如果沒有經(jīng)過身份驗(yàn)證,則返回None 
 """
def get_permissions(iUserId):
 """
 返回給定用戶的權(quán)限字符串列表,例如 ['logged_in','administrator','premium_member']
 """
#我們需要對這些函數(shù)進(jìn)行權(quán)限檢查
def delete_user(iUserId):
 """
 刪除具有給定ID的用戶,只有管理員權(quán)限才能訪問此函數(shù)
 """
def new_game():
 """
 任何已登錄的用戶都可以啟動一個新游戲
 """
def premium_checkpoint():
 """
 保存游戲進(jìn)程,只允許高級成員訪問
 """

實(shí)現(xiàn)這些權(quán)限的一種方法是創(chuàng)建多個裝飾器,例如:

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):
 """
 刪除具有給定Id的用戶,只有具有管理員權(quán)限的用戶才能訪問此函數(shù)
 """
@requires_logged_in 
def new_game():
 """
 任何已登錄的用戶都可以啟動一個新游戲
 """
@requires_premium_member
def premium_checkpoint():
 """
 保存游戲進(jìn)程,只允許高級成員訪問
 """

但這太可怕了。它需要大量的復(fù)制粘貼,并且每個裝飾器需要不同的名稱,如果對權(quán)限的檢查方式進(jìn)行了任何更改,則必須更新每個裝飾器。有一個裝飾器可以完成這三個工作不是很好嗎?

為此,我們需要一個返回裝飾器的函數(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): #這樣裝飾器就不會拋出NameError
 return ['logged_in',]
def current_user_id(): #名稱錯誤也是如此
 return 1
#現(xiàn)在我們可以進(jìn)行裝飾了 
@requires_permission('administrator')
def delete_user(iUserId):
 """
 刪除具有給定Id的用戶,只有具有管理員權(quán)限的用戶才能訪問此函數(shù)
 """
@requires_permission('logged_in')
def new_game():
 """
 任何已登錄的用戶都可以啟動一個新游戲
 """
@requires_permission('premium_member')
def premium_checkpoint():
 """
 保存游戲進(jìn)程,只允許高級成員訪問
 """

嘗試調(diào)用delete_user,new_game和premium_checkpoint看看會發(fā)生什么。

premium_checkpoint和delete_user都在消息“權(quán)限被拒絕”的情況下引發(fā)異常,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()

這相當(dāng)于:

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ù)進(jìn)行操作,它們也可以對類進(jìn)行操作。比方說,我們有一個類可以做很多非常重要的事情,我們想要把它所做的一切都進(jìn)行計(jì)時。然后我們可以使用time_this像以前一樣使用裝飾器:

class ImportantStuff(object):
 @time_this
 def do_stuff_1(self):
 ...
 @time_this
 def do_stuff_2(self):
 ...
 @time_this
 def do_stuff_3(self):
 ...

這樣就可以了。但是這個類中還有一些額外的代碼行。如果我們寫一些更多的類方法并忘記裝飾它們中的一個呢?如果我們決定不再為進(jìn)行計(jì)時怎么辦?這里肯定存在人為錯誤的空間。這樣編寫它會好得多:

@time_all_class_methods
class ImportantStuff:
 def do_stuff_1(self):
 ...
 def do_stuff_2(self):
 ...
 def do_stuff_3(self):
 ...

如你所知,該代碼相當(dāng)于:

class ImportantStuff:
 def do_stuff_1(self):
 ...
 def do_stuff_2(self):
 ...
 def do_stuff_3(self):
 ...
ImportantStuff = time_all_class_methods(ImportantStuff)

那么time_all_class_methods是如何工作的? 首先,我們知道它需要將一個類作為參數(shù),并返回一個類。我們也知道返回類的函數(shù)應(yīng)該與原始ImportantStuff類的函數(shù)相同。也就是說,我們?nèi)匀幌M胍瓿芍匾氖虑椋覀冃枰M(jìn)行計(jì)時。以下是我們將如何做到這一點(diǎn):

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(object):
 def __init__(self,*args,**kwargs):
 self.oInstance = Cls(*args,**kwargs)
 def __getattribute__(self,s):
 """
 每當(dāng)訪問NewCls對象的任何屬性時,都會調(diào)用這個函數(shù)。這個函數(shù)首先嘗試
 從NewCls獲取屬性。如果失敗,則嘗試從self獲取屬性。oInstance(一個
 修飾類的實(shí)例)。如果它設(shè)法從self獲取屬性。oInstance,
 屬性是一個實(shí)例方法,然后應(yīng)用' time_this '。
 """
 try: 
 x = super(NewCls,self).__getattribute__(s)
 except AttributeError: 
 pass
 else:
 return x
 x = self.oInstance.__getattribute__(s)
 if type(x) == type(self.__init__): # 這是一個實(shí)例方法
 return time_this(x) # 這等價于用time_this修飾方法
 else:
 return x
 return NewCls
#現(xiàn)在讓我們做一個虛擬類來測試它:
@time_all_class_methods
class Foo(object):
 def a(self):
 print ("entering a")
 import time
 time.sleep(3)
 print ("exiting a")
oF = Foo()
oF.a()

結(jié)論

在裝飾器的高級用法中,我向你展示了使用Python裝飾器的一些技巧 - 我已經(jīng)向你展示了如何將參數(shù)傳遞給裝飾器,以及如何裝飾類。但這仍然只是冰山的一角。在各種奇怪的情況下,有大量的方法用于裝飾器。你甚至可以裝飾你的裝飾器(但如果你到達(dá)那一點(diǎn),那么做一個全面的檢查可能是個好主意)。Python同時內(nèi)置了一些值得了解的裝飾器,例如裝飾器staticmethod及classmethod。

接下來要怎么做?除了我在這篇文章中向你展示的內(nèi)容外,通常不需要對裝飾器執(zhí)行任何更復(fù)雜的操作。如果你對更改類功能的更多方法感興趣,那么我建議閱讀有關(guān)繼承和一般OO設(shè)計(jì)原則的數(shù)據(jù)。或者,如果你真的想學(xué)會他們,那么請閱讀元類(但同樣,處理這些東西幾乎不需要)。

分享到:
標(biāo)簽:裝飾 Python
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定