裝飾器是可呼叫的物件,其引數是另乙個函式(被裝飾的函式)。
裝飾器可能會處理被裝飾的函式,然後把它返回,或者將其替換成另乙個函式或可呼叫物件。
➊deco返回inner函式物件#裝飾器通常把函式替換成另乙個函式
>>
>
defdeco
(func):.
..definner()
:...
('running inner()').
..return inner #➊ ..
.>>
> @deco ..
.def
target()
:#➋ ..
('running target()').
..>>
> target(
)#➌
running inner(
)>>
> target #➍
<
locals
>
.inner at 0x10063b598
>
➋使用deco裝飾target
➌ 呼叫被裝飾的target,實際執行的是inner
➍審查物件,發現target現在是inner的引用。
嚴格來說,裝飾器只是語法糖。如前所示,裝飾器可以像常規的可呼叫物件那樣呼叫,其引數是另乙個函式。有時,這樣做更方便,尤其是做元程式設計(在執行時改變程式的行為)時。
綜上,裝飾器的一大特性是,能把被裝飾的函式替換成其他函式。第二個特性是,裝飾器在載入模組時立即執行。
裝飾器的乙個關鍵特性是,它們在被裝飾的函式定義之後立即執行。這通常是在匯入時(即python載入模組時)。
➊ registry儲存被@register裝飾的函式引用。registry =
#➊ def
register
(func)
:#➋
('running register(%s)'
% func)
#➌ #➍
return func #➎
@register #➏
deff1()
('running f1()')
@register
deff2()
('running f2()'
)deff3(
):#➐ print
('running f3()'
)def
main()
:#➑
('running main()'
('registry ->'
, registry)
f1() f2(
) f3(
)if __name__==
'__main__'
: main(
)#➒
➋ register的引數是乙個函式。
➌ 為了演示,顯示被裝飾的函式。
➍ 把func存入registry。
➎ 返回func:必須返回函式;這裡返回的函式與通過引數傳入的一樣。
➏ f1和f2被@register裝飾。
➐ f3沒有裝飾。
➑ main顯示registry,然後呼叫f1()、f2()和f3()。
➒ 只有把registration.py當作指令碼執行時才呼叫main()。
執行上述程式,輸出如下
register在模組中其他函式之前執行(兩次)。呼叫register時,傳給它的引數是被裝飾的函式,例如。running register(
>
)running register(
>
)running main(
)registry -
>
[>
,>
]running f1(
)running f2(
)running f3(
)
registry中有兩個被裝飾函式的引用:f1和f2。這兩個函式,以及f3,只在main明確呼叫它們時才執行。
檢視register的值,輸出如下>>
>
import registration
running register(
>
) running register(
>
)
由此可知:函式裝飾器在匯入模組時立即執行,而被裝飾的函式只在明確呼叫時執行。這突出了python程式設計師所說的匯入時和執行時之間的區別。>>
> registration.registry
[>
,>
]
使用註冊裝飾器可以改進之前的電商**折扣示例。
回顧一下,之前示例的主要問題是,定義體中有函式的名稱,但是best_promo用來判斷哪個折扣幅度最大的promos列表中也有函式名稱。這種重複是個問題,因為新增策略函式後可能會忘記把它新增到promos列表中,導致best_promo忽略新策略,而且不報錯,為系統引入了不易察覺的缺陷。示例7-3使用註冊裝飾器解決了這個問題。
➊ promos列表起初是空的。#promos列表中的值使用promotion裝飾器填充
promos =
#➊ def
promotion
(promo_func)
:#➋
return promo_func
@promotion #➌
deffidelity
(order)
:"""為積分為1000或以上的顧客提供5%折扣"""
return order.total()*
.05if order.customer.fidelity >=
1000
else0
@promotion
defbulk_item
(order)
:"""單個商品為20個或以上時提供10%折扣"""
discount =
0for item in order.cart:
if item.quantity >=
20:
discount += item.total()*
.1return discount
@promotion
deflarge_order
(order)
:"""訂單中的不同商品達到10個或以上時提供7%折扣"""
distinct_items =
iflen
(distinct_items)
>=10:
return order.total()*
.07return
0def
best_promo
(order)
:#➍
"""選擇可用的最佳折扣
"""return
max(promo(order)
for promo in promos)
➋ promotion把promo_func新增到promos列表中,然後原封不動地將其返回。
➌ 被@promotion裝飾的函式都會新增到promos列表中。
➍ best_promos無需修改,因為它依賴promos列表
與之前給出的方案相比,這個方案有幾個優點。
• **策略函式無需使用特殊的名稱(即不用以_promo結尾)。
• @promotion裝飾器突出了被裝飾的函式的作用,還便於臨時禁用某個**策略:只需把裝飾器注釋掉。
• **折扣策略可以在其他模組中定義,在系統中的任何地方都行,只要使用@promotion裝飾即可。
多數裝飾器會修改被裝飾的函式。通常,它們會定義乙個內部函式,然後將其返回,替換被裝飾的函式。使用內部函式的**幾乎都要靠閉包才能正確運作。
閉包函式和裝飾器
目錄 二 閉包函式的應用 裝飾器閉包 閉是封閉 函式內部巢狀函式 包是包含,閉包是指該內部函式對外部作用域而非全域性作用域的變數的引用。為函式傳參的方式一 使用引數的形式def func x print x func 1 為函式傳參的方式二 包給函式def outter x x 2 def inne...
閉包函式和裝飾器
目錄裝飾器 閉包即函式內部函式對外部作用域而非全域性作用域的引用,說白了就是將函式內部的變數拿到全域性來使用,還不會修改區域性變數的值 def outter x 5 def inner return x return inner f outter x f 1 print f print x 5 6使...
閉包函式和裝飾器
閉包函式 作用域關係在函式定義階段時就已經固定死了,與呼叫位置無關 即 在任意位置呼叫函式都需要跑到定義函式時尋找作用域關係 def f1 x 1 def inner print x x最後還是等於1,因為只看定義階段,return inner func f1 def f2 x 111111 fun...