嚴格來講,裝飾器只是語法糖。如前所示,裝飾器能夠像常規的可調用
對象那樣調用,其參數是另外一個函數。有時,這樣作更方便,尤爲是作
元編程(在運行時改變程序的行爲)時。html
它們在被裝飾的函數定義以後當即運行。這一般是在導入時(即 Python 加載模塊時)python
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 看成腳本運行獲得的輸出以下:編程
$ python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3()
>>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)
此時查看 registry 的值,獲得的輸出以下:緩存
>>> registration.registry [<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]
裝飾器函數與被裝飾的函數在同一個模塊中定義。實際狀況是,裝
飾器一般在一個模塊中定義,而後應用到其餘模塊中的函數上。閉包
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): """單個商品爲20個或以上時提供10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount @promotion def large_order(order): """訂單中的不一樣商品達到10個或以上時提供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)
神奇的例子app
>>> 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
利用global就能夠啦ide
>>> b = 6 >>> def f3(a): ... global b ... print(a) ... print(b) ... b = 9 ... >>> f3(3) 3 6
人們有時會把閉包和匿名函數弄混。
這是有歷史緣由的:在函數內部定義函數不常見,直到開始使用匿名函數纔會這樣作,模塊化
假若有個名爲 avg 的函數,它的做用是計算不斷增長的系列值的均值;函數
初學者可能會這樣性能
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
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager
在 averager 函數中,series 是自由變量(free variable)。這是一個
技術術語,指未在本地做用域中綁定的變量.
>>> avg.__code__.co_varnames ('new_value', 'total') >>> avg.__code__.co_freevars ('series',)
>>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x107a44f78: list object at 0x107a91a48>,) >>> avg.__closure__[0].cell_contents [10, 11, 12]
計算移動平均值的高階函數,不保存全部歷史值,但有
缺陷
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
count += 1 語句的做用其實與 count = count + 1 同樣。
所以,咱們在 averager 的定義體中爲 count 賦值了,這會把 count 變成局部變量。
示例 7-9 沒遇到這個問題,由於咱們沒有給 series 賦值,咱們只是調
用 series.append,並把它傳給 sum 和 len。也就是說,咱們利用了
列表是可變的對象這一事實。可是對數字、字符串、元組等不可變類型來講,只能讀取,不能更新。
若是嘗試從新綁定,例如 count = count + 1,其實會隱式建立局部
變量 count。這樣,count 就不是自由變量了,所以不會保存在閉包
中。
爲了解決這個問題,Python 3 引入了 nonlocal 聲明。它的做用是把變
量標記爲自由變量,即便在函數中
爲變量賦予新值了,也會變成自由變
量。若是爲 nonlocal 聲明的變量賦予新值,閉包中保存的綁定會更
新。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total / count return averager
基本上,這種處理方式是把內部函數須要修改
的變量(如 count 和 total)存儲爲可變對象(如字典或簡單的
實例)的元素或屬性,而且把那個對象綁定給一個自由變量。
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) ## 這裏的函數對象變成了從clocked print(factorial.__name__)
常)返回被裝飾的函數本該返回的值,同時還會作些額外操做。
上面實現的 clock 裝飾器有幾個缺點:不支持關鍵字參數,並且遮蓋了被裝飾函
數的 name 和 doc 屬性。使用 functools.wraps 裝飾器把相關的屬性從 func 複製到 clocked 中。此外,這個新版還能正確處理關鍵字參數。
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
functools.lru_cache 是很是實用的裝飾器,它實現了備忘(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))
浪費時間的地方很明顯:fibonacci(1) 調用了 8 次,fibonacci(2) 調用了 5 次……可是,若是增長兩行代碼,使用 lru_cache,性能會顯著改善,
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))
❶ 注意,必須像常規函數那樣調用 lru_cache。這一行中
有一對括
號:@functools.lru_cache()。這麼作的緣由是,lru_cache 能夠接受配置參數,稍
後說明。
分開。
個函數綁在一塊兒組成一個泛函數
from functools import singledispatch from collections import abc import numbers import html @singledispatch def htmlize(obj): content = html.escape(repr(obj)) return '<pre>{}</pre>'.format(content) @htmlize.register(str) def _(text): content = html.escape(text).replace('\n', '<br>\n') return '<p>{0}</p>'.format(content) @htmlize.register(numbers.Integral) def _(n): return '<pre>{0} (0x{0:x})</pre>'.format(n) @htmlize.register(tuple) @htmlize.register(abc.MutableSequence) def _(seq): inner = '</li>\n<li>'.join(htmlize(item) for item in seq) return '<ul>\n<li>' + inner + '</li>\n</ul>' print(htmlize({1, 2, 3})) print(htmlize(abs)) print(htmlize('Heimlich & Co.\n- a game')) print(htmlize(42)) ##這個強啊!!! print(htmlize(['alpha', 66, {3, 2, 1}]))
❷ 各個專門函數使用 @«base_function».register(«type») 裝飾。
❸ 專門函數的名稱可有可無;_ 是個不錯的選擇,簡單明瞭。
爲每一個須要特殊處理的類型註冊一個函數。numbers.Integral 是 int 的虛擬超類。
❺ 能夠疊放多個 register 裝飾器,讓同一個函數支持不一樣類型。
只要可能,註冊的專門函數應該處理抽象基類(如 numbers.Integral 和
abc.MutableSequence),不要處理具體實現(如 int 和 list)。
這樣,代碼支持的兼容類型更普遍。例如,Python 擴展能夠子類化 numbers.Integral,使用固定的位數實
現 int 類型。
@d1 @d2 def f(): print('f')
等同於
def f(): print('f') f = d1(d2(f))
爲了便於啓用或禁用 register 執行的函數註冊功能,咱們爲它提供一個可選的 active
參數,設爲 False 時,不註冊被裝飾的函數。
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)
參數化裝飾器一般會把被裝飾的函數替換掉,並且結構上須要多一層嵌套。
import time DEFAULT_FMT = '花費時間:[{elapsed:0.5f}s] 程序名:{name} 參數:({args}) -> 結果:{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}: 花費時間:{elapsed}s') # def snooze(seconds): # time.sleep(seconds) ## 第三種狀況 @clock('程序名:{name} 參數:({args}) 花費時間:dt={elapsed:0.3f}s') def snooze(seconds): time.sleep(seconds) snooze(0.123)
clock 是參數化裝飾器工廠函數
❷ decorate 是真正的裝飾器。
❸ clocked 包裝被裝飾的函數。
❹ _result 是被裝飾的函數返回的真正結果
def runnoob(arg:'int'): z = 1 print(arg + 1) # 返回字典類型的局部變量。 print('==='*30) print(locals()) # 返回字典類型的所有變量。 print('=' * 50) print(globals()) num = 8 runnoob(num)
量標記爲自由變量