<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org1fbb994">1. 什么是閉包</a></li>
<li><a href="#org6e28685">2. 裝飾器</a></li>
<li><a href="#orga345de2">3. 無參裝飾器</a></li>
<li><a href="#org184871c">4. 有參裝飾器</a></li>
<li><a href="#org8215eae">5. 位置參數(shù)和關鍵字參數(shù)都可以的裝飾器</a></li>
<li><a href="#org7ddc069">6. return 之后的對象和傳入對象的關系以及形式</a></li>
<li><a href="#org87145ad">7. 類裝飾器</a></li>
</ul>
</div>
</div>
<a id="org1fbb994"></a>
什么是閉包
解釋 1:在函數(shù)內部定義一個函數(shù),這個函數(shù)使用到外部函數(shù)
閉包(closure)是一種引用了外部變量的函數(shù)對象,無論該變量所處的作用域是否還存在于內存中。
舉例來說,函數(shù) generatepowerfunc 返回了另一個函數(shù):
def generate_power_func(n):
print "id(n): %X" % id(n)
def nth_power(x):
return x**n
print "id(nth_power): %X" % id(nth_power)
return nth_power
函數(shù) nthpower 就是一個閉包,它可以訪問定義在 generatepowerfunc 函數(shù)中的變量 n,i
顯而易見如果缺少變量 n,函數(shù) nthpower 將是一個不能執(zhí)行的沒有閉合的函數(shù),
這個不完整的函數(shù) nthpower 需要變量 n 來讓它變成一個完整的函數(shù)對象,
這種函數(shù)就是閉包,換句話說是變量 n 封閉了函數(shù) nthpower。
<a id="org6e28685"></a>
裝飾器
https://wiki.python.org/moin/PythonDecorators
<a id="orga345de2"></a>
無參裝飾器
# -*- coding: utf-8 -*-
def func_cache(func):
cache = {}
def inner_deco(*args):
if args in cache:
print('func {} is already cached with arguments {}'.format(
func.__name__, args))
return cache[args]
else:
print('func {} is not cached with arguments {}'.format(
func.__name__, args))
res = func(*args)
cache[args] = res
return res
return inner_deco
@func_cache
def add_two_number(a, b):
return a + b
if __name__ == "__main__":
print('1. add_two_number(1, 2)')
add_two_number(1, 2)
print('2. add_two_number(2, 3)')
add_two_number(2, 3)
print('3. add_two_number(1, 2)')
add_two_number(1, 2)
<a id="org184871c"></a>
有參裝飾器
目前我們實現(xiàn)的函數(shù)緩存裝飾器,會緩存所有遇到的函數(shù)返回值。
我們希望能夠對緩存數(shù)量上限做一個限制,從而在內存消耗和運行效率上取得折中。
但是同時,對于不同的函數(shù),我們希望做到緩存上限不同,例如對于運行一次比較耗時的函數(shù),我們希望緩存上限大一些;
反之,則小一些。這時,需要用到帶參數(shù)的 Decorator。
# -*- coding: utf-8 -*-
from functools import wraps
import random
def outer_deco(size=10):
def func_cache(func):
cache = {}
@wraps(func)
def inner_deco(*args, **kwargs):
key = (args, frozenset(kwargs.items()))
if key not in cache:
print('func {} is not cached with arguments {} {}'.format(
func.__name__, args, kwargs))
res = func(*args, **kwargs)
if len(cache) >= size:
lucky_key = random.choice(list(cache.keys()))
print('func {} cache pop {}'.format(
func.__name__, lucky_key))
cache.pop(lucky_key, None)
cache[key] = res
return cache[key]
return inner_deco
return func_cache
@outer_deco(size=3)
def add_two_number(a, b):
return a + b
@outer_deco()
def product_two_number(a, b):
return a * b
if __name__ == "__main__":
print('add_two_number func name is {}'.format(add_two_number.__name__))
print('1. add_two_number(1, 2)')
add_two_number(1, 2)
print('2. add_two_number(2, 3)')
add_two_number(2, 3)
print('3. add_two_number(1, b=2)')
add_two_number(1, b=2)
print('4. add_two_number(1, 2)')
add_two_number(1, 2)
print('5. product_two_number(1, 2)')
product_two_number(1, 2)
print('6. add_two_number(1, 3)')
add_two_number(1, 3)
<a id="org8215eae"></a>
位置參數(shù)和關鍵字參數(shù)都可以的裝飾器
http://blog.guoyb.com/2016/04/19/python-decorator/?hmsr=toutiao.io
<a id="org7ddc069"></a>
return 之后的對象和傳入對象的關系以及形式
https://www.ibm.com/developerworks/cn/linux/l-cpdecor.html
高級抽象簡介
根據(jù)我的經(jīng)驗,元類應用最多的場合就是在類實例化之后對類中的方法進行修改。decorator 目前并不允許您修改類實例化本身,但是它們可以修改依附于類的方法。這并不能讓您在實例化過程中動態(tài)添加或刪除方法或類屬性,但是它讓這些方法可以在運行時根據(jù)環(huán)境的條件來變更其行為?,F(xiàn)在從技術上來說,decorator 是在運行 class 語句時應用的,對于頂級類來說,它更接近于 “編譯時” 而非 “運行時”。但是安排 decorator 的運行時決策與創(chuàng)建類工廠一樣簡單。例如:
清單 8. 健壯但卻深度嵌套的 decorator
def arg_sayer(what):
def what_sayer(meth):
def new(self, *args, **kws):
print what
return meth(self, *args, **kws)
return new
return what_sayer
def FooMaker(word):
class Foo(object):
@arg_sayer(word)
def say(self): pass
return Foo()
foo1 = FooMaker('this')
foo2 = FooMaker('that')
print type(foo1),; foo1.say() # prints: <class '__main__.Foo'> this
print type(foo2),; foo2.say() # prints: <class '__main__.Foo'> that
@argsayer() 繞了很多彎路,但只獲得非常有限的結果,不過對于它所闡明的幾方面來說,這是值得的:
Foo.say() 方法對于不同的實例有不同的行為。在這個例子中,不同之處只是一個數(shù)據(jù)值,可以輕松地通過其他方式改變這個值;不過原則上來說,decorator 可以根據(jù)運行時的決策來徹底重寫這個方法。
本例中未修飾的 Foo.say() 方法是一個簡單的占位符,其整個行為都是由 decorator 決定的。然而,在其他情況下,decorator 可能會將未修飾的方法與一些新功能相結合。
正如我們已經(jīng)看到的一樣,F(xiàn)oo.say() 的修改是通過 FooMaker() 類工廠在運行時嚴格確定的。可能更加典型的情況是在頂級定義類中使用 decorator,這些類只依賴于編譯時可用的條件(這通常就足夠了)。
decorator 都是參數(shù)化的?;蛘吒_切地說,argsayer() 本身根本就不是一個真正的 decorator;argsayer()所返回的 函數(shù) —— whatsayer() 就是一個使用了閉包來封裝其數(shù)據(jù)的 decorator 函數(shù)。參數(shù)化的 decorator 較為常見,但是它們將所需的函數(shù)嵌套為三層。
邁進元類領域
正如上一節(jié)中介紹的一樣,decorator 并不能完全取代元類掛鉤,因為它們只修改了方法,而未添加或刪除方法。
實際上,這樣說并不完全正確。作為一個 Python 函數(shù),decorator 完全可以實現(xiàn)其他 Python 代碼所實現(xiàn)的任何功能。
通過修飾一個類的 ._new_() 方法(甚至是其占位符版本),您實際上可以更改附加到該類的方法。
盡管尚未在現(xiàn)實中看到這種模式,不過我認為它有著某種必然性,甚至可以作為 <span class="underline">metaclass</span> 指派的一項改進:
清單 9. 添加和刪除方法的 decorator
def flaz(self): return 'flaz' # Silly utility method
def flam(self): return 'flam' # Another silly method
def change_methods(new):
"Warning: Only decorate the __new__() method with this decorator"
if new.__name__ != '__new__':
return new # Return an unchanged method
def __new__(cls, *args, **kws):
cls.flaz = flaz
cls.flam = flam
if hasattr(cls, 'say'): del cls.say
return super(cls.__class__, cls).__new__(cls, *args, **kws)
return __new__
class Foo(object):
@change_methods
def __new__(): pass
def say(self): print "Hi me:", self
foo = Foo()
print foo.flaz() # prints: flaz
foo.say() # AttributeError: 'Foo' object has no attribute 'say'
在 changemethods() decorator 示例中,我們添加并刪除了幾個固定的方法,不過這是毫無意義的。
在更現(xiàn)實的情況中,應使用上一節(jié)中提到的幾個模式。例如,參數(shù)化的 decorator 可以接受一個能表示要添加或刪除的方法的數(shù)據(jù)結構;或者由數(shù)據(jù)庫查詢之類的某些環(huán)境特性做出這一決策。
這種對附加方法的操作也可以像之前一樣打包到一個函數(shù)工廠中,這將使最終決策延遲到運行時。
這些新興技術也許比 <span class="underline">metaclass</span> 指派更加萬能。
例如,您可以調用一個增強了的 changemethods(),如下所示:
清單 10. 增強的 changemethods()
class Foo(object):
@change_methods(add=(foo, bar, baz), remove=(fliz, flam))
def __new__(): pass
回頁首
修改調用模型
您將看到,有關 decorator 的最典型的例子可能是使一個函數(shù)或方法來實現(xiàn) “其他功能”,同時完成其基本工作。
例如,在諸如 Python Cookbook Web 站點(請參見 參考資料 中的鏈接)之類的地方,您可以看到 decorator 添加了諸如跟蹤、日志記錄、存儲/緩存、線程鎖定以及輸出重定向之類的功能。
與這些修改相關(但實質略有區(qū)別)的是修飾 “之前” 和 “之后”。對于修飾之前/之后來說,一種有趣的可能性就是檢查傳遞給函數(shù)的參數(shù)和函數(shù)返回值的類型。
如果這些類型并非如我們預期的一樣,那么這種 typecheck() decorator 就可能會觸發(fā)一個異常,或者采取一些糾正操作。
與這種 decorator 前/后類似的情況,我想到了 R 編程語言和 NumPy 特有的函數(shù)的 “elementwise” 應用。
在這些語言中,數(shù)學函數(shù)通常應用于元素序列中的每個元素,但也會應用于單個數(shù)字。
當然,map() 函數(shù)、列表內涵(list-comprehension)和最近的生成器內涵(generator-comprehension 都可以讓您實現(xiàn) elementwise 應用。
但是這需要較小的工作區(qū)來獲得類似于 R 語言的行為:map() 所返回的序列類型通常是一個列表;如果您傳遞的是單個元素而不是一個序列,那么調用將失敗。例如:
清單 11. map() 調用失敗
>>> from math import sqrt
>>> map(sqrt, (4, 16, 25))
[2.0, 4.0, 5.0]
>>> map(sqrt, 144)
TypeError: argument 2 to map() must support iteration
創(chuàng)建一個可以 “增強” 普通數(shù)值函數(shù)的 decorator 并不困難:
清單 12. 將函數(shù)轉換成 elementwise 函數(shù)
def elementwise(fn):
def newfn(arg):
if hasattr(arg,'__getitem__'): # is a Sequence
return type(arg)(map(fn, arg))
else:
return fn(arg)
return newfn
@elementwise
def compute(x):
return x**3 - 1
print compute(5) # prints: 124
print compute([1,2,3]) # prints: [0, 7, 26]
print compute((1,2,3)) # prints: (0, 7, 26)
當然,簡單地編寫一個具有不同返回類型的 compute() 函數(shù)并不困難;畢竟 decorator 只需占據(jù)幾行。但是作為對面向方面編程的一種認可,
這個例子讓我們可以分離 那些在不同層次上運作的關注事項。
我們可以編寫各種數(shù)值計算函數(shù),希望它們都可轉換成 elementwise 調用模型,而不用考慮參數(shù)類型測試和返回值類型強制轉換的細節(jié)。
對于那些對單個事物或事物序列(此時要保留序列類型)進行操作的函數(shù)來說,elementwise() decorator 均可同樣出色地發(fā)揮作用。
作為一個練習,您可嘗試去解決如何允許相同的修飾后調用來接受和返回迭代器
(提示:如果您只是想迭代一次完整的 elementwise 計算,那么當且僅當傳入的是一個迭代對象時,才能這樣簡化一些。)
您將碰到的大多數(shù)優(yōu)秀的 decorator 都在很大程度上采用了這種組合正交關注的范例。
傳統(tǒng)的面向對象編程,尤其是在諸如 Python 之類允許多重繼承的語言中,都會試圖使用一個繼承層次結構來模塊化關注事項。
然而,這僅會從一個祖先那里獲取一些方法,而從其他祖先那里獲取其他方法,因此需要采用一種概念,使關注事項比在面向方面的思想中更加分散。
要充分利用生成器,就要考慮一些與混搭方法不同的問題:可以處于方法本身的 “核心” 之外的關注事項為依據(jù),使各 方法以不同方式工作。
修飾 decorator
在結束本文之前,我想為您介紹一種確實非常出色的 Python 模塊,名為 decorator,它是由與我合著過一些圖書的 Michele Simionato 編寫的。
該模塊使 decorator 的開發(fā)變得更加美妙。decorator 模塊的主要組件具有某種自反式的優(yōu)雅,它是一個稱為 decorator() 的 decorator。
與未修飾的函數(shù)相比,使用 @decorator 修飾過的函數(shù)可以通過一種更簡單的方式編寫。(相關資料請參看 參考資料)。
Michele 已經(jīng)為自己的模塊編寫了很好的文檔,因此這里不再贅述;不過我非常樂意介紹一下它所解決的基本問題。decorator 模塊有兩大主要優(yōu)勢。
一方面,它使您可以編寫出嵌套層次更少的 decorator,如果沒有這個模塊,您就只能使用更多層次(“平面優(yōu)于嵌套”);
但更加有趣的是這樣一個事實:它使得修飾過的函數(shù)可以真正地與其在元數(shù)據(jù)中未修飾的版本相匹配,這是我的例子中沒有做到的。
例如,回想一下我們上面使用過的簡單 “跟蹤” decorator addspam():
清單 13. 一個簡單的 decorator 是如何造成元數(shù)據(jù)崩潰的
>>> def useful(a, b): return a**2 + b**2
>>> useful.__name__
'useful'
>>> from inspect import getargspec
>>> getargspec(useful)
(['a', 'b'], None, None, None)
>>> @addspam
... def useful(a, b): return a**2 + b**2
>>> useful.__name__
'new'
>>> getargspec(useful)
([], 'args', None, None)
盡管這個修飾過的函數(shù)的確完成 了自己增強過的工作,但若進一步了解,就會發(fā)現(xiàn)這并不是完全正確的,尤其是對于那些關心這種細節(jié)的代碼分析工具或 IDE 來說更是如此。
使用 decorator,我們就可以改進這些問題:
清單 14. decorator 更聰明的用法
>>> from decorator import decorator
>>> @decorator
... def addspam(f, *args, **kws):
... print "spam, spam, spam"
... return f(*args, **kws)
>>> @addspam
... def useful(a, b): return a**2 + b**2
>>> useful.__name__
'useful'
>>> getargspec(useful)
(['a', 'b'], None, None, None)
這對于編寫 decorator 更加有利,同時,其保留行為的元數(shù)據(jù)的也更出色了。
當然,閱讀 Michele 開發(fā)這個模塊所使用的全部資料會使您回到大腦混沌的世界,我們將這留給 Simionato 博士一樣的宇宙學家好了。
<a id="org87145ad"></a>