日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

[python 进阶] 第7章 函数装饰器和闭包

發(fā)布時(shí)間:2023/12/10 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [python 进阶] 第7章 函数装饰器和闭包 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 7.1 裝飾器基礎(chǔ)知識(shí)
    • 7.2 Python何時(shí)執(zhí)行裝飾器
    • 7.3 使用裝飾器改進(jìn)“策略”
    • 7.4 變量作用域(global)
    • 備注 -比較字節(jié)碼(暫略)
    • 7.5 閉包
    • 7.6 nonlocal聲明
    • global和nonlocal的區(qū)別
    • 7.7 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的裝飾器
    • 7.8 標(biāo)準(zhǔn)庫(kù)中的裝飾器
      • 7.8.1 使用functools.lru_cache做備忘
      • 補(bǔ)充 @functools.lru_cache()可以配置參數(shù)
      • 7.8.2 單分派泛函數(shù)( @functools.singledispatch)
    • 7.9 疊放裝飾器
    • 7.10 參數(shù)化裝飾器
      • 7.10.1 一個(gè)參數(shù)化的注冊(cè)裝飾器
      • 7.10.2 參數(shù)化clock裝飾器

函數(shù)裝飾器用于在源碼中“標(biāo)記”函數(shù),以某種方式增強(qiáng)函數(shù)的行為。這是一項(xiàng)強(qiáng)大的功能,但是若想掌握,必須理解閉包。
nonlocal 是新近出現(xiàn)的保留關(guān)鍵字,在 Python 3.0 中引入。
除了在裝飾器中有用處之外,閉包還是 回調(diào)式異步編程函數(shù)式編程風(fēng)格的基礎(chǔ)。
本章的最終目標(biāo)是解釋清楚函數(shù)裝飾器的工作原理,包括最簡(jiǎn)單的 注冊(cè)裝飾器和較復(fù)雜的 參數(shù)化裝飾器。但是,在實(shí)現(xiàn)這一目標(biāo)之前,我們要討論下述話(huà)題:

  • Python 如何計(jì)算裝飾器句法
  • Python 如何判斷變量是不是局部的
  • 閉包存在的原因和工作原理
  • nonlocal 能解決什么問(wèn)題
    掌握這些基礎(chǔ)知識(shí)后,我們可以進(jìn)一步探討裝飾器:
  • 實(shí)現(xiàn)行為良好的裝飾器
  • 標(biāo)準(zhǔn)庫(kù)中有用的裝飾器
  • 實(shí)現(xiàn)一個(gè)參數(shù)化裝飾器

7.1 裝飾器基礎(chǔ)知識(shí)

裝飾器是可調(diào)用的對(duì)象,其參數(shù)是另一個(gè)函數(shù)(被裝飾的函數(shù))。 裝飾器可能會(huì)處理被裝飾的函數(shù),然后把它返回,或者將其替換成另一個(gè)函數(shù)或可調(diào)用對(duì)象。
假如有個(gè)名為 decorate 的裝飾器:

@decorate def target(): print('running target()')

上述代碼的效果與下述寫(xiě)法一樣:

def target(): print('running target()') target = decorate(target)

兩種寫(xiě)法的最終結(jié)果一樣:上述兩個(gè)代碼片段執(zhí)行完畢后得到的 target 不一定是原來(lái)那
個(gè) target 函數(shù),而是 decorate(target) 返回的函數(shù)。

為了確認(rèn)被裝飾的函數(shù)會(huì)被替換,請(qǐng)看示例 7-1 中的控制臺(tái)會(huì)話(huà)。
示例 7-1 裝飾器通常把函數(shù)替換成另一個(gè)函數(shù)

>>> def deco(func): ... def inner(): ... print('running inner()') ... return inner ... >>> @deco ... def target(): ... print('running target()') ... >>> target() running inner() >>> target <function deco.<locals>.inner at 0x10063b598>

嚴(yán)格來(lái)說(shuō),裝飾器只是語(yǔ)法糖。如前所示,裝飾器可以像常規(guī)的可調(diào)用對(duì)象那樣調(diào)用,其
參數(shù)是另一個(gè)函數(shù)。有時(shí),這樣做更方便,尤其是做元編程(在運(yùn)行時(shí)改變程序的行
為)時(shí)。
綜上,裝飾器的一大特性是,能把被裝飾的函數(shù)替換成其他函數(shù)。第二個(gè)特性是,裝飾器
在加載模塊時(shí)立即執(zhí)行。

7.2 Python何時(shí)執(zhí)行裝飾器

裝飾器的一個(gè)關(guān)鍵特性是,它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。這通常是在導(dǎo)入時(shí)
(即 Python 加載模塊時(shí))

registry = [] def register(func):print('running register(%s)' % func)registry.append(func)return func @register def f1():print('running f1()') @register def f2():print('running f2()') def f3():print('running f3()') def main():print('running main()')print('registry ->', registry) # 發(fā)現(xiàn)registry這個(gè)數(shù)組并不是空f(shuō)1()f2()f3() if __name__=='__main__':main()

輸出后是什么樣子呢?

running register(<function f1 at 0x7ff079e400d0>) running register(<function f2 at 0x7ff06c9e37b8>) running main() registry -> [<function f1 at 0x7ff079e400d0>, <function f2 at 0x7ff06c9e37b8>] running f1() running f2() running f3()

注意:在調(diào)用f1()和f2()時(shí),輸出的是 runnint f1()和running f2()。
上面的例子主要是強(qiáng)調(diào):函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。這突出了 Python 程序員所說(shuō)的導(dǎo)入時(shí)和運(yùn)行時(shí)之間的區(qū)別。
考慮到裝飾器在真實(shí)代碼中的常用方式,示例 7-2 有兩個(gè)不尋常的地方。裝飾器函數(shù)與被裝飾的函數(shù)在同一個(gè)模塊中定義。實(shí)際情況是,裝飾器通常在一個(gè)模塊中定義,然后應(yīng)用到其他模塊中的函數(shù)上。
register 裝飾器返回的函數(shù)與通過(guò)參數(shù)傳入的相同。實(shí)際上,大多數(shù)裝飾器會(huì)在內(nèi)部定義一個(gè)函數(shù),然后將其返回。

雖然上示例中的 register 裝飾器原封不動(dòng)地返回被裝飾的函數(shù),但是這種技術(shù)并非沒(méi)有用處。很多 Python Web 框架使用這樣的裝飾器把函數(shù)添加到某種中央注冊(cè)處,例如把URL模式映射到生成 HTTP 響應(yīng)的函數(shù)上的注冊(cè)處。這種注冊(cè)裝飾器可能會(huì)也可能不會(huì)修改被裝飾的函數(shù)。

7.3 使用裝飾器改進(jìn)“策略”

使用注冊(cè)裝飾器可以改進(jìn)之前的第六章中的電商促銷(xiāo)折扣示例。
回顧一下,示例 6-6 的主要問(wèn)題是,定義體中有函數(shù)的名稱(chēng),但是 best_promo 用來(lái)判斷哪個(gè)折扣幅度最大的 promos 列表中也有函數(shù)名稱(chēng)。這種重復(fù)是個(gè)問(wèn)題,因?yàn)樾略霾呗院瘮?shù)后可能會(huì)忘記把它添加到 promos 列表中,導(dǎo)致 best_promo 忽略新策略,而且不報(bào)錯(cuò),為系統(tǒng)引入了不易察覺(jué)的缺陷。以下這個(gè)例子使用注冊(cè)裝飾器解決了這個(gè)問(wèn)題。

  • promos 列表中的值使用 promotion 裝飾器

    from collections import namedtupleCustomer = namedtuple('Customer', 'name fidelity')class LineItem:def __init__(self, product, quantity, price):self.product = productself.quantity = quantityself.price = pricedef total(self):return self.price * self.quantitypromos =[]def promotion(promo_func):promos.append(promo_func)return promo_func@promotiondef fidelity(order):"""為積分為1000或者以上的顧客提供5%折扣"""return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0@promotiondef bulk_item(order):"""單個(gè)商品為20個(gè)或者以上時(shí)提供10%折扣"""discount = 0for item in order.cart:if item.quantity >= 20:discount += item.total() * 0.01return discount@promotiondef large_order(order):"""訂單中的不同商品達(dá)到10個(gè)以上時(shí)提供7%折扣"""distinct_items = {item.product for item in order.cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0def best_promo(order):"""選擇可用的最佳折扣"""return max(promo(order) for promo in promos)class Order: #上下文def __init__(self, customer, cart, promotion=None):self.customer = customerself.cart = list(cart)self.promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self.cart)return self.__totaldef due(self):if self.promotion is None:discount = 0else:discount = self.promotion(self)return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())if __name__ == '__main__':joe = Customer('John Doe', 0)ann = Customer('Ann Smith', 1000)cart = [LineItem('banana', 4, 0.5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]print(Order(joe, cart, fidelity)) # <Order total: 42.00 due: 42.00>print(Order(ann, cart, fidelity)) #<Order total: 42.00 due: 39.90>

與 6.1 節(jié)給出的方案相比,這個(gè)方案有幾個(gè)優(yōu)點(diǎn)。

  • 促銷(xiāo)策略函數(shù)無(wú)需使用特殊的名稱(chēng)(即不用以 _promo 結(jié)尾)。
  • @promotion 裝飾器突出了被裝飾的函數(shù)的作用,還便于臨時(shí)禁用某個(gè)促銷(xiāo)策略:只需把裝飾器注釋掉。
  • 促銷(xiāo)折扣策略可以在其他模塊中定義,在系統(tǒng)中的任何地方都行,只要使用@promotion 裝飾即可。

不過(guò),多數(shù)裝飾器會(huì)修改被裝飾的函數(shù)。通常,它們會(huì)定義一個(gè)內(nèi)部函數(shù),然后將其返
回,替換被裝飾的函數(shù)。使用內(nèi)部函數(shù)的代碼幾乎都要靠閉包才能正確運(yùn)作。

7.4 變量作用域(global)

在示例 7-4 中,我們定義并測(cè)試了一個(gè)函數(shù),它讀取兩個(gè)變量的值:一個(gè)是局部變量 a,
是函數(shù)的參數(shù);另一個(gè)是變量 b,這個(gè)函數(shù)沒(méi)有定義它。

示例 7-4 一個(gè)函數(shù),讀取一個(gè)局部變量和一個(gè)全局變量

>>> def f1(a): ... print(a) ... print(b) ... >>> f1(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f1 NameError: global name 'b' is not defined

出現(xiàn)錯(cuò)誤并不奇怪。 在示例 7-4 中,如果先給全局變量 b 賦值,然后再調(diào)用 f,那就不
會(huì)出錯(cuò)。

示例 7-5 b 是局部變量,因?yàn)樵诤瘮?shù)的定義體中給它賦值了

>>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... b = 9 ... >>> f2(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f2 UnboundLocalError: local variable 'b' referenced before assignment

注意,首先輸出了 3,這表明 print(a) 語(yǔ)句執(zhí)行了。但是第二個(gè)語(yǔ)句 print(b) 執(zhí)行不了。一開(kāi)始我很吃驚,我覺(jué)得會(huì)打印 6,因?yàn)橛袀€(gè)全局變量 b,而且是在 print(b) 之后為局部變量 b 賦值的。
可事實(shí)是,Python 編譯函數(shù)的定義體時(shí),它判斷 b 是局部變量,因?yàn)樵诤瘮?shù)中給它賦值了。生成的字節(jié)碼證實(shí)了這種判斷,Python 會(huì)嘗試從本地環(huán)境獲取 b。后面調(diào)用 f2(3)時(shí), f2 的定義體會(huì)獲取并打印局部變量 a 的值,但是嘗試獲取局部變量 b 的值時(shí),發(fā)現(xiàn)b 沒(méi)有綁定值。
這不是缺陷,而是設(shè)計(jì)選擇:Python 不要求聲明變量,但是假定在函數(shù)定義體中賦值的變量是局部變量。這比 JavaScript 的行為好多了,JavaScript 也不要求聲明變量,但是如果忘記把變量聲明為局部變量(使用 var),可能會(huì)在不知情的情況下獲取全局變量。
如果在函數(shù)中賦值時(shí)想讓解釋器把 b 當(dāng)成全局變量,要使用 global 聲明:

>>> b = 6 >>> def f3(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 6 >>> b 9 >>> f3(3) 3 9 >>> b = 30 >>> b 30 >>>

了解 Python 的變量作用域之后,下一節(jié)可以討論閉包了。如果好奇示例 7-4 和示例 7-5 中的兩個(gè)函數(shù)生成的字節(jié)碼有什么區(qū)別,可以下面的備注:

備注 -比較字節(jié)碼(暫略)

7.5 閉包

在博客圈,人們有時(shí)會(huì)把閉包和匿名函數(shù)弄混。這是有歷史原因的:在函數(shù)內(nèi)部定義函數(shù)不常見(jiàn),直到開(kāi)始使用匿名函數(shù)才會(huì)這樣做。而且,只有涉及嵌套函數(shù)時(shí)才有閉包問(wèn)題。
因此,很多人是同時(shí)知道這兩個(gè)概念的。
其實(shí),閉包指延伸了作用域的函數(shù),其中包含函數(shù)定義體中引用、但是不在定義體中定義的非全局變量。函數(shù)是不是匿名的沒(méi)有關(guān)系,關(guān)鍵是它能訪問(wèn)定義體之外定義的非全局變量。
這個(gè)概念難以掌握,最好通過(guò)示例理解。
假如有個(gè)名為 avg 的函數(shù),它的作用是計(jì)算不斷增加的系列值的均值;例如,整個(gè)歷史中某個(gè)商品的平均收盤(pán)價(jià)。每天都會(huì)增加新價(jià)格,因此平均值要考慮至目前為止所有的價(jià)格。

示例 7-8 average_oo.py:計(jì)算移動(dòng)平均值

class Averager():def __init__(self):self.series=[]def __call__(self, new_value):self.series.append(new_value)total = sum(self.series)return total/len(self.series) avg=Averager() print(avg(10)) #10.0 print(avg(11)) #10.5 print(avg(12)) #11.0

Averager 的實(shí)例是可調(diào)用對(duì)象

示例 7-9 是函數(shù)式實(shí)現(xiàn),使用高階函數(shù) make_averager。

示例 7-9 average.py:計(jì)算移動(dòng)平均值的高階函數(shù)

def make_averager():series = []def averager(new_value):series.append(new_value)total = sum(series)return total/len(series) return averager

調(diào)用 make_averager 時(shí),返回一個(gè) averager 函數(shù)對(duì)象。每次調(diào)用 averager 時(shí),它會(huì)
把參數(shù)添加到系列值中,然后計(jì)算當(dāng)前平均值,如示例 7-10 所示。
示例 7-10 測(cè)試示例 7-9

>>> avg = make_averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0

注意,這兩個(gè)示例有共通之處:調(diào)用 Averager() 或 make_averager() 得到一個(gè)可調(diào)用對(duì)象 avg,它會(huì)更新歷史值,然后計(jì)算當(dāng)前均值。在示例 7-8 中,avg 是 Averager 的實(shí)例;在示例 7-9 中是內(nèi)部函數(shù) averager。不管怎樣,我們都只需調(diào)用 avg(n),把 n 放入系列值中,然后重新計(jì)算均值。
Averager 類(lèi)的實(shí)例 avg 在哪里存儲(chǔ)歷史值很明顯:self.series 實(shí)例屬性。但是第二個(gè)示例中的 avg 函數(shù)在哪里尋找 series 呢?
注意,series 是 make_averager 函數(shù)的局部變量,因?yàn)槟莻€(gè)函數(shù)的定義體中初始化了series:series = []。可是,調(diào)用 avg(10) 時(shí),make_averager 函數(shù)已經(jīng)返回了,而它的本地作用域也一去不復(fù)返了。
在 averager 函數(shù)中,series 是自由變量(free variable)。這是一個(gè)技術(shù)術(shù)語(yǔ),指未在本地作用域中綁定的變量。


averager 的閉包延伸到那個(gè)函數(shù)的作用域之外,包含自由變量 series 的綁定。
綜上,閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定,這樣調(diào)用函數(shù)時(shí),雖然定義作用域不可用了,但是仍能使用那些綁定。
注意,只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中的外部變量。

7.6 nonlocal聲明

前面實(shí)現(xiàn) make_averager 函數(shù)的方法效率不高。在示例 7-9 中,我們把所有值存儲(chǔ)在歷史數(shù)列中,然后在每次調(diào)用 averager 時(shí)使用 sum 求和。更好的實(shí)現(xiàn)方式是,只存儲(chǔ)目前的總值和元素個(gè)數(shù),然后使用這兩個(gè)數(shù)計(jì)算均值。
示例 7-13 中的實(shí)現(xiàn)有缺陷,只是為了闡明觀點(diǎn)。你能看出缺陷在哪兒?jiǎn)?#xff1f;

示例 7-13 計(jì)算移動(dòng)平均值的高階函數(shù),不保存所有歷史值,但有缺陷 def make_averager(): count = 0 total = 0def averager(new_value):count += 1total += new_valuereturn total / count return averager

Python 3 引入了 nonlocal 聲明。它的作用是把變量標(biāo)記為自由變量,即使在函數(shù)中為變量賦予新值了,也會(huì)變成自由變量。如果為 nonlocal 聲明的變量賦予新值,閉包中保存的綁定會(huì)更新。

示例 7-14 計(jì)算移動(dòng)平均值,不保存所有歷史(使用 nonlocal 修正) def make_averager(): count = 0 total = 0def averager(new_value):nonlocal count, totalcount += 1total += new_valuereturn total / count return averager

global和nonlocal的區(qū)別

global 表示將變量聲明為全局變量
nonlocal 表示將變量聲明為外層變量(外層函數(shù)的局部變量,而且不能是全局變量)。

  • global關(guān)鍵字用來(lái)在函數(shù)或其他局部作用域中使用全局變量。但是如果不修改全局變量也可以不使用global關(guān)鍵字。
  • 聲明全局變量,如果在局部要對(duì)全局變量修改,需要在局部也要先聲明該全局變量。

    gcount = 0def global_test():global gcountgcount +=1print (gcount)global_test()
  • 3.在局部如果不聲明全局變量,并且不修改全局變量。則可以正常使用全局變量:

    gcount = 0def global_test():print(gcount)global_test()
  • nonlocal關(guān)鍵字用來(lái)在函數(shù)或其他作用域中使用外層(非全局)變量

    def make_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter def make_counter_test(): mc = make_counter() print(mc())print(mc())print(mc())make_counter_test()

7.7 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的裝飾器

定義了一個(gè)裝飾器,它會(huì)在每次調(diào)用被裝飾的函數(shù)時(shí)計(jì)時(shí),然后把經(jīng)過(guò)的時(shí)間、傳入的參數(shù)和調(diào)用的結(jié)果打印出來(lái)。
import time

def clock(func):def clocked(*args):t0=time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@clock def snooze(seconds):time.sleep(seconds) @clock def factorial(n):return 1 if n < 2 else n*factorial(n-1) if __name__ == '__main__':print('*' * 40, 'Calling snooze(.123)')snooze(.123)print('*' * 40, 'Calling factorial(6)')print('6! =', factorial(6))

這是裝飾器的典型行為:把被裝飾的函數(shù)替換成新函數(shù),二者接受相同的參數(shù),而且(通常)返回被裝飾的函數(shù)本該返回的值,同時(shí)還會(huì)做些額外操作。

將上述例子改進(jìn)一下:

7.8 標(biāo)準(zhǔn)庫(kù)中的裝飾器

Python 內(nèi)置了三個(gè)用于裝飾方法的函數(shù):property、classmethod 和
staticmethod。另一個(gè)常見(jiàn)的裝飾器是 functools.wraps,它的作用是協(xié)助構(gòu)建行為良好的裝飾器。我
們?cè)谑纠?7-17 中用過(guò)。標(biāo)準(zhǔn)庫(kù)中最值得關(guān)注的兩個(gè)裝飾器是 lru_cache 和全新的
singledispatch(Python 3.4 新增)。這兩個(gè)裝飾器都在 functools 模塊中定義。

7.8.1 使用functools.lru_cache做備忘

示例 7-18 生成第 n 個(gè)斐波納契數(shù),遞歸方式非常

import timedef clock(func):def clocked(*args):print(*args)t0 = time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@clock def fibonacci(n):return n if n < 2 else fibonacci(n-2)+fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

示例 7-19 使用緩存實(shí)現(xiàn),速度

import time import functools def clock(func):def clocked(*args):print(*args)t0 = time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@functools.lru_cache() #注意,必須像常規(guī)函數(shù)那樣調(diào)用 lru_cache。這一行中有一對(duì)括號(hào):@functools.lru_cache()。這么做的原因是,lru_cache 可以接受配置參數(shù) @clock def fibonacci(n):return n if n < 2 else fibonacci(n-2)+fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

補(bǔ)充 @functools.lru_cache()可以配置參數(shù)

7.8.2 單分派泛函數(shù)( @functools.singledispatch)

7.9 疊放裝飾器

示例 7-19 演示了疊放裝飾器的方式:@lru_cache 應(yīng)用到 @clock 裝飾 fibonacci 得到的結(jié)果上。在示例 7-21 中,模塊中最后一個(gè)函數(shù)應(yīng)用了兩個(gè) @htmlize.register 裝飾器。
把 @d1 和 @d2 兩個(gè)裝飾器按順序應(yīng)用到 f 函數(shù)上,作用相當(dāng)于 f = d1(d2(f))。
也就是說(shuō),下述代碼:

@d1 @d2 def f():print('f')

等同于:

def f():print('f')f = d1(d2(f))

除了疊放裝飾器之外,本章還用到了幾個(gè)接受參數(shù)的裝飾器,

7.10 參數(shù)化裝飾器

解析源碼中的裝飾器時(shí),Python 把被裝飾的函數(shù)作為第一個(gè)參數(shù)傳給裝飾器函數(shù)。那怎么讓裝飾器接受其他參數(shù)呢?答案是:創(chuàng)建一個(gè)裝飾器工廠函數(shù),把參數(shù)傳給它,返回一個(gè)裝飾器,然后再把它應(yīng)用到要裝飾的函數(shù)上。

registry = [] def register(func):print('running register(%s)' % func)registry.append(func)return func @register def f1():print('running f1()') print('running main') print('registry ->',registry) f1()

7.10.1 一個(gè)參數(shù)化的注冊(cè)裝飾器

為了便于啟用或禁用 register 執(zhí)行的函數(shù)注冊(cè)功能,我們?yōu)樗峁┮粋€(gè)可選的 active參數(shù),設(shè)為 False 時(shí),不注冊(cè)被裝飾的函數(shù)。實(shí)現(xiàn)方式參見(jiàn)下面這個(gè)例子。從概念上看,這個(gè)新的 register 函數(shù)不是裝飾器,而是裝飾器工廠函數(shù)。調(diào)用它會(huì)返回真正的裝飾器,這才是應(yīng)用到目標(biāo)函數(shù)上的裝飾器。

registry = [] def register(active=True):def decorate(func):print('running register(active=%s)->decorate(%s)'%(active, func))if active:registry.add(func)else:registry.discard(func)return func #decorate 是裝飾器,必須返回一個(gè)函數(shù)return decorate # register 是裝飾器工廠函數(shù),因此返回 decorate @register(active=True) def f1():print('running f1()') @register() #即使不傳入?yún)?shù),register 也必須作為函數(shù)調(diào)用(@register()),即要返回真正的裝飾器 decorate。 def f2():print('running f2()') def f3():print('running f3()')

如果不使用 @ 句法,那就要像常規(guī)函數(shù)那樣使用 register;若想把 f 添加到 registry中,則裝飾 f 函數(shù)的句法是 register()(f);不想添加(或把它刪除)的話(huà),句法是register(active=False)(f)。

參數(shù)化裝飾器的原理相當(dāng)復(fù)雜,我們剛剛討論的那個(gè)比大多數(shù)都簡(jiǎn)單。參數(shù)化裝飾器通常會(huì)把被裝飾的函數(shù)替換掉,而且結(jié)構(gòu)上需要多一層嵌套。

7.10.2 參數(shù)化clock裝飾器

本節(jié)再次探討 clock 裝飾器,為它添加一個(gè)功能:讓用戶(hù)傳入一個(gè)格式字符串,控制被裝飾函數(shù)的輸出。
import time
DEFAULT_FMT=’[{elapsed:0.8f}s] {name}({args})-> {result}’

def clock(fmt=DEFAULT_FMT):def decorate(func):def clocked(*_args):t0 = time.time()_result= func(*_args)elapsed=time.time-t0name = func._name__args = ','.join(repr(arg) for arg in _args)result = repr(_result)print(fmt.format(**locals()))return _resultreturn clockedreturn decorate if __name__ == '__main__':@clockdef snooze(seconds):time.sleep(seconds)for i in range(3):snooze(.123)

總結(jié)

以上是生活随笔為你收集整理的[python 进阶] 第7章 函数装饰器和闭包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。