摘要:函數(shù)裝飾器和閉包嚴(yán)格來說,裝飾器只是語(yǔ)法糖。何時(shí)執(zhí)行裝飾器它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。裝飾器突出了被裝飾的函數(shù)的作用,還便于臨時(shí)禁用某個(gè)促銷策略只需把裝飾器注釋掉。
函數(shù)裝飾器和閉包
嚴(yán)格來說,裝飾器只是語(yǔ)法糖。如前所示,裝飾器可以像常規(guī)的可調(diào)用
對(duì)象那樣調(diào)用,其參數(shù)是另一個(gè)函數(shù)。有時(shí),這樣做更方便,尤其是做
元編程(在運(yùn)行時(shí)改變程序的行為)時(shí)。
它們?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) f1() f2() f3() if __name__=="__main__": main()
把 registration.py 當(dāng)作腳本運(yùn)行得到的輸出如下:
$ python3 registration.py running register(如果導(dǎo)入 registration.py 模塊(不作為腳本運(yùn)行),輸出如下:) running register( ) running main() registry -> [ , ] running f1() running f2() running f3()
>>> import registration running register() running register( )
此時(shí)查看 registry 的值,得到的輸出如下:
>>> registration.registry [裝飾器在真實(shí)代碼中的常用方式, ]
裝飾器函數(shù)與被裝飾的函數(shù)在同一個(gè)模塊中定義。實(shí)際情況是,裝
飾器通常在一個(gè)模塊中定義,然后應(yīng)用到其他模塊中的函數(shù)上。
promos = [] def promotion(promo_func): promos.append(promo_func) return @promotion def fidelity(order): """為積分為1000或以上的顧客提供5%折扣""" return order.total() * .05 if order.customer.fidelity >= 1000 else 0 @promotion def bulk_item(order): """單個(gè)商品為20個(gè)或以上時(shí)提供10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount @promotion def 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() * .07 return 0 def best_promo(order): """選擇可用的最佳折扣""" return max(promo(order) for promo in promos)
promotion 把 promo_func 添加到 promos 列表中,然后原封不動(dòng)地將其返回。
被 @promotion 裝飾的函數(shù)都會(huì)添加到 promos 列表中。
與 6.1 節(jié)給出的方案相比,這個(gè)方案有幾個(gè)優(yōu)點(diǎn)。促銷策略函數(shù)無需使用特殊的名稱(即不用以 _promo 結(jié)尾)。
@promotion 裝飾器突出了被裝飾的函數(shù)的作用,還便于臨時(shí)禁用
某個(gè)促銷策略:只需把裝飾器注釋掉。
促銷折扣策略可以在其他模塊中定義,在系統(tǒng)中的任何地方都行,只要使用 @promotion 裝飾即可。
變量作用域規(guī)則神奇的例子
>>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... b = 9 ... >>> f2(3) 3 Traceback (most recent call last): File "", line 1, in File " ", line 3, in f2 UnboundLocalError: local variable "b" referenced before assignment
可事實(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 沒有綁定值。
為什么會(huì)這樣這不是缺陷,而是設(shè)計(jì)選擇:Python 不要求聲明變量,但是假定在函數(shù)定義體中賦值的變量是局部變量。
這比 JavaScript 的行為好多了,JavaScript 也不要求聲明變量,但是如果忘記把變量聲明為局部變量使用 var),可能會(huì)在不知情的情況下獲取全局變量。
利用global就可以啦
>>> b = 6 >>> def f3(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 6閉包
人們有時(shí)會(huì)把閉包和匿名函數(shù)弄混。
這是有歷史原因的:在函數(shù)內(nèi)部定義函數(shù)不常見,直到開始使用匿名函數(shù)才會(huì)這樣做,
函數(shù)是不是匿名的沒有關(guān)系,關(guān)鍵是它能訪問定義體之外定義的非全局變量。
只有涉及嵌套函數(shù)時(shí)才有閉包問題。因此,很多人是同時(shí)知道這兩個(gè)概念的。
案例假如有個(gè)名為 avg 的函數(shù),它的作用是計(jì)算不斷增加的系列值的均值;
初學(xué)者可能會(huì)這樣
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() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0函數(shù)式實(shí)現(xiàn),使用高階函數(shù) make_averager。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager重要概念
在 averager 函數(shù)中,series 是自由變量(free variable)。這是一個(gè)
技術(shù)術(shù)語(yǔ),指未在本地作用域中綁定的變量.
>>> avg.__code__.co_varnames ("new_value", "total") >>> avg.__code__.co_freevars ("series",)
series 的綁定在返回的 avg 函數(shù)的 closure 屬性中。
avg.__closure__ 中的各個(gè)元素對(duì)應(yīng)于avg.__code__.co_freevars 中的一個(gè)名稱。
這些元素是 cell 對(duì)象,有個(gè) cell_contents 屬性,保存著真正的值。
>>> avg.__code__.co_freevars ("series",) >>> avg.__closure__ (小總結(jié),) >>> avg.__closure__[0].cell_contents [10, 11, 12] |
綜上,閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定,
這樣調(diào)用函數(shù)時(shí),雖然定義作用域不可用了,但是仍能使用那些綁定。
注意,只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中的外部變量。
nonlocal聲明計(jì)算移動(dòng)平均值的高階函數(shù),不保存所有歷史值,但有
缺陷
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
問題是,當(dāng) count 是數(shù)字或任何不可變類型時(shí)
count += 1 語(yǔ)句的作用其實(shí)與 count = count + 1 一樣。注意
因此,我們?cè)?averager 的定義體中為 count 賦值了,這會(huì)把 count 變成局部變量。
示例 7-9 沒遇到這個(gè)問題,因?yàn)槲覀儧]有給 series 賦值,我們只是調(diào)
用 series.append,并把它傳給 sum 和 len。也就是說,我們利用了
列表是可變的對(duì)象這一事實(shí)。但是對(duì)數(shù)字、字符串、元組等不可變類型來說,只能讀取,不能更新。
如果嘗試重新綁定,例如 count = count + 1,其實(shí)會(huì)隱式創(chuàng)建局部
變量 count。這樣,count 就不是自由變量了,因此不會(huì)保存在閉包
中。
為了解決這個(gè)問題,Python 3 引入了 nonlocal 聲明。它的作用是把變
量標(biāo)記為自由變量,即使在函數(shù)中
為變量賦予新值了,也會(huì)變成自由變
量。如果為 nonlocal 聲明的變量賦予新值,閉包中保存的綁定會(huì)更
新。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total / count return averager對(duì)付沒有 nonlocal 的 Python 2
基本上,這種處理方式是把內(nèi)部函數(shù)需要修改實(shí)現(xiàn)一個(gè)簡(jiǎn)單的裝飾器
的變量(如 count 和 total)存儲(chǔ)為可變對(duì)象(如字典或簡(jiǎn)單的
實(shí)例)的元素或?qū)傩裕⑶野涯莻€(gè)對(duì)象綁定給一個(gè)自由變量。
import time def clock(func): def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ args_str = "".join(repr(arg) for arg in args) print("[%0.8fs] %s(%s) -> %r" % (elapsed, name, args_str, result)) return result return 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) snooze(0.123) print("*" * 40) factorial(6) ## 這里的函數(shù)對(duì)象變成了從clocked print(factorial.__name__)這是裝飾器的典型行為:把被裝飾的函數(shù)替換成新函數(shù),二者接受相同的參數(shù),而且(通
常)返回被裝飾的函數(shù)本該返回的值,同時(shí)還會(huì)做些額外操作。
上面實(shí)現(xiàn)的 clock 裝飾器有幾個(gè)缺點(diǎn):不支持關(guān)鍵字參數(shù),而且遮蓋了被裝飾函
數(shù)的 name 和 doc 屬性。使用 functools.wraps 裝飾器把相關(guān)的屬性從 func 復(fù)制到 clocked 中。此外,這個(gè)新版還能正確處理關(guān)鍵字參數(shù)。
import time import functools def clock(func): @functools.wraps(func) ###這里 保留__name__ 和 __doc__ 屬性 def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(", ".join(repr(arg) for arg in args)) if kwargs: pairs = ["%s=%r" % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(", ".join(pairs)) arg_str = ", ".join(arg_lst) print("[%0.8fs] %s(%s) -> %r " % (elapsed, name, arg_str, result)) return result return clocked標(biāo)準(zhǔn)庫(kù)中的裝飾器
functools.lru_cache 是非常實(shí)用的裝飾器,它實(shí)現(xiàn)了備忘(memoization)功能。
就是更加利用緩存干活
import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(", ".join(repr(arg) for arg in args)) if kwargs: pairs = ["%s=%r" % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(", ".join(pairs)) arg_str = ", ".join(arg_lst) print("[%0.8fs] %s(%s) -> %r " % (elapsed, name, arg_str, result)) return result return clocked @clock def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == "__main__": print(fibonacci(6))
浪費(fèi)時(shí)間的地方很明顯:fibonacci(1) 調(diào)用了 8 次,fibonacci(2) 調(diào)用了 5 次……但是,如果增加兩行代碼,使用 lru_cache,性能會(huì)顯著改善,
import time import functools def clock(func): @functools.wraps(func) def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] if args: arg_lst.append(", ".join(repr(arg) for arg in args)) if kwargs: pairs = ["%s=%r" % (k, w) for k, w in sorted(kwargs.items())] arg_lst.append(", ".join(pairs)) arg_str = ", ".join(arg_lst) print("[%0.8fs] %s(%s) -> %r " % (elapsed, name, arg_str, result)) return result return clocked @functools.lru_cache() # @clock # def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == "__main__": print(fibonacci(6))
? 注意,必須像常規(guī)函數(shù)那樣調(diào)用 lru_cache。這一行中lru_cache 可以使用兩個(gè)可選的參數(shù)來配置。
有一對(duì)括
號(hào):@functools.lru_cache()。這么做的原因是,lru_cache 可以接受配置參數(shù),稍
后說明。
maxsize 參數(shù)指定存儲(chǔ)多少個(gè)調(diào)用的結(jié)果。
緩存滿了之后,舊的結(jié)果會(huì)被扔掉,騰出空間。
為了得到最佳性能,maxsize 應(yīng)該設(shè)為 2 的冪。typed 參數(shù)如果設(shè)為 True,把不同
參數(shù)類型得到的結(jié)果分開保存,即把通常認(rèn)為相等的浮點(diǎn)數(shù)和整數(shù)參數(shù)(如 1 和 1.0)區(qū)
分開。
functools.singledispatch 裝飾器 讓Python強(qiáng)行支持重載方法個(gè)函數(shù)綁在一起組成一個(gè)泛函數(shù)
from functools import singledispatch from collections import abc import numbers import html @singledispatch def htmlize(obj): content = html.escape(repr(obj)) return "{}".format(content) @htmlize.register(str) def _(text): content = html.escape(text).replace(" ", "
") return "{0}
".format(content) @htmlize.register(numbers.Integral) def _(n): return "{0} (0x{0:x})".format(n) @htmlize.register(tuple) @htmlize.register(abc.MutableSequence) def _(seq): inner = "
? 各個(gè)專門函數(shù)使用 @?base_function?.register(?type?) 裝飾。
? 專門函數(shù)的名稱無關(guān)緊要;_ 是個(gè)不錯(cuò)的選擇,簡(jiǎn)單明了。
為每個(gè)需要特殊處理的類型注冊(cè)一個(gè)函數(shù)。numbers.Integral 是 int 的虛擬超類。
? 可以疊放多個(gè) register 裝飾器,讓同一個(gè)函數(shù)支持不同類型。
注意:只要可能,注冊(cè)的專門函數(shù)應(yīng)該處理抽象基類(如 numbers.Integral 和
abc.MutableSequence),不要處理具體實(shí)現(xiàn)(如 int 和 list)。
這樣,代碼支持的兼容類型更廣泛。例如,Python 擴(kuò)展可以子類化 numbers.Integral,使用固定的位數(shù)實(shí)
現(xiàn) int 類型。
@singledispatch 不是為了把 Java 的那種方法重載帶入 Python。在一個(gè)類中 為同一個(gè)方法定義多個(gè)重載變體,
@singledispath 的優(yōu)點(diǎn)是支持模塊化擴(kuò)展:各個(gè)模塊可以為它支持的各個(gè)類型注冊(cè)一個(gè)專門函數(shù)。
疊放裝飾器@d1 @d2 def f(): print("f")
等同于
def f(): print("f") f = d1(d2(f))一個(gè)參數(shù)化的注冊(cè)裝飾器
為了便于啟用或禁用 register 執(zhí)行的函數(shù)注冊(cè)功能,我們?yōu)樗峁┮粋€(gè)可選的 active
參數(shù),設(shè)為 False 時(shí),不注冊(cè)被裝飾的函數(shù)。
registry = set() 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 return decorate @register(active=False) def f1(): print("running f1()") @register() def f2(): print("running f2()") def f3(): print("running f3()") if __name__ =="__main__": print(registry)
參數(shù)化裝飾器通常會(huì)把被裝飾的函數(shù)替換掉,而且結(jié)構(gòu)上需要多一層嵌套。參數(shù)化clock裝飾器
import time DEFAULT_FMT = "花費(fèi)時(shí)間:[{elapsed:0.5f}s] 程序名:{name} 參數(shù):({args}) -> 結(jié)果:{result}" def clock(fmt=DEFAULT_FMT): def decorate(func): def clocked(*_args): t0 = time.time() _result = func(*_args) ### locals() 局部變量 elapsed = time.time() - t0 name = func.__name__ args = ", ".join(repr(arg) for arg in _args) result = repr(_result) # 這里不知道他為什么這么能用 print(fmt.format(**locals())) return _result return clocked return decorate if __name__ == "__main__": # ## 第一種情況 # @clock() # def snooze(seconds): # time.sleep(seconds) ## 第二種情況 # @clock("程序名:{name}: 花費(fèi)時(shí)間:{elapsed}s") # def snooze(seconds): # time.sleep(seconds) ## 第三種情況 @clock("程序名:{name} 參數(shù):({args}) 花費(fèi)時(shí)間:dt={elapsed:0.3f}s") def snooze(seconds): time.sleep(seconds) snooze(0.123)
clock 是參數(shù)化裝飾器工廠函數(shù)
? decorate 是真正的裝飾器。
? clocked 包裝被裝飾的函數(shù)。
? _result 是被裝飾的函數(shù)返回的真正結(jié)果
def runnoob(arg:"int"): z = 1 print(arg + 1) # 返回字典類型的局部變量。 print("==="*30) print(locals()) # 返回字典類型的全部變量。 print("=" * 50) print(globals()) num = 8 runnoob(num)小總結(jié)
嚴(yán)格來說,裝飾器只是語(yǔ)法糖。
它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。這通常是在導(dǎo)入時(shí)(即 Python 加載模塊時(shí))
裝飾器改進(jìn)了策略模式
閉包閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定
Python 3 引入了 nonlocal 聲明。它的作用是把變
量標(biāo)記為自由變量
functools.wraps 裝飾器把相關(guān)的屬性從 func 復(fù)制到 clocked 中。此外,這個(gè)新版還能正確處理關(guān)鍵字參數(shù)
functools.lru_cache 是非常實(shí)用的裝飾器,它實(shí)現(xiàn)了備忘(memoization)功能。就是更加利用緩存干活 functools.singledispatch 裝飾器 讓Python強(qiáng)行支持重載方法 locals() globals()*locals
** locals()
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/41655.html
摘要:初步認(rèn)識(shí)裝飾器函數(shù)裝飾器用于在源代碼中標(biāo)記函數(shù),以某種方式增強(qiáng)函數(shù)的行為。函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。只有涉及嵌套函數(shù)時(shí)才有閉包問題。如果想保留函數(shù)原本的屬性,可以使用標(biāo)準(zhǔn)庫(kù)中的裝飾器。 《流暢的Python》筆記本篇將從最簡(jiǎn)單的裝飾器開始,逐漸深入到閉包的概念,然后實(shí)現(xiàn)參數(shù)化裝飾器,最后介紹標(biāo)準(zhǔn)庫(kù)中常用的裝飾器。 1. 初步認(rèn)識(shí)裝飾器 函數(shù)裝飾...
摘要:可迭代的對(duì)象迭代器和生成器理念迭代是數(shù)據(jù)處理的基石。可迭代的對(duì)象與迭代器的對(duì)比從可迭代的對(duì)象中獲取迭代器標(biāo)準(zhǔn)的迭代器接口有兩個(gè)方法。此外,也沒有辦法還原迭代器。最終,函數(shù)的定義體返回時(shí),外層的生成器對(duì)象會(huì)拋出異常這一點(diǎn)與迭代器協(xié)議一致。 可迭代的對(duì)象、迭代器和生成器 理念 迭代是數(shù)據(jù)處理的基石。掃描內(nèi)存中放不下的數(shù)據(jù)集時(shí),我們要找到一種惰性獲取數(shù)據(jù)項(xiàng)的方式,即按需一次獲取一個(gè)數(shù)據(jù)項(xiàng)。這...
摘要:變量查找規(guī)則在中一個(gè)變量的查找順序是局部環(huán)境,閉包,全局,內(nèi)建閉包引用了自由變量的函數(shù)。閉包的作用閉包的最大特點(diǎn)是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù),此時(shí)即便生成閉包的環(huán)境父函數(shù)已經(jīng)釋放,閉包仍然存在。 導(dǎo)語(yǔ):本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之函數(shù)篇的重點(diǎn)知識(shí)及個(gè)人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、掌握裝飾器的本質(zhì)、功...
摘要:第六章抽象本章會(huì)介紹如何將語(yǔ)句組織成函數(shù)。關(guān)鍵字參數(shù)和默認(rèn)值目前為止,我們使用的參數(shù)都是位置參數(shù),因?yàn)樗鼈兊奈恢煤苤匾?,事?shí)上比它們的名字更重要。參數(shù)前的星號(hào)將所有值放置在同一個(gè)元祖中。函數(shù)內(nèi)的變量被稱為局部變量。 第六章:抽象 本章會(huì)介紹如何將語(yǔ)句組織成函數(shù)。還會(huì)詳細(xì)介紹參數(shù)(parameter)和作用域(scope)的概念,以及遞歸的概念及其在程序中的用途。 懶惰即美德 斐波那契數(shù)...
閱讀 2984·2023-04-26 00:23
閱讀 3399·2021-09-13 10:28
閱讀 2178·2021-08-31 14:18
閱讀 2884·2019-08-30 15:54
閱讀 1939·2019-08-30 15:43
閱讀 1276·2019-08-29 16:56
閱讀 2800·2019-08-29 14:16
閱讀 2053·2019-08-28 17:51