PEP原文 : www.python.org/dev/peps/pe…html
PEP標題: Decorators for Functions and Methodsjava
PEP做者: Kevin D. Smith, Jim J. Jewett, Skip Montanaro, Anthony Baxterpython
建立日期: 2003-06-05git
合入版本: 2.4程序員
譯者 :豌豆花下貓(Python貓 公衆號做者)github
PEP翻譯計劃 :https://github.com/chinesehuazhou/peps-cn編程
本文檔旨在描述裝飾器語法和作出決定的過程。它既不試圖涵蓋大量潛在的替代語法,也不試圖詳盡列出每種形式的全部優勢和缺點。安全
當前用於轉換函數和方法的方式(例如,將它們聲明爲類或靜態方法)很笨拙,而且可能致使難以理解的代碼。在理想的狀況下,這些轉換應該在代碼中做聲明的位置進行。本 PEP 引入了對函數或方法聲明做轉換的新語法。app
當前對函數或方法做變換的方式會把實際的變換置於函數體以後。對於大型函數,這會將函數行爲的關鍵組成部分與其他的函數外部接口的定義分開。例如:編程語言
def foo(self):
perform method operation
foo = classmethod(foo)複製代碼
對於較長的方法,這變得不太可讀。在概念上只是聲明一個函數,使用其名稱三遍就很不 pythonic。此問題的解決方案是將方法的轉換移到方法自己的聲明附近。新語法的意圖是替換
def foo(cls):
pass
foo = synchronized(lock)(foo)
foo = classmethod(foo)複製代碼
成爲一種將裝飾符放置在函數的聲明中的寫法:
@classmethod
@synchronized(lock)
def foo(cls):
pass複製代碼
以這種方式來修改類也是可能的,儘管好處不能當即體現。幾乎能夠確定,使用類裝飾器能夠完成的任何事情均可以使用元類來完成,可是使用元類很是晦澀,因此就有吸引力找到一種對類進行簡單修改的更簡便的方法。對於 Python 2.4 來講,僅添加了函數/方法裝飾器。
PEP 3129 (譯註:譯文在此) 提議從 Python 2.6 開始添加類裝飾器。
自 2.2 版本以來,Python 中提供了兩個裝飾器(classmethod() 和 staticmethod() )。大約從那時起,就已經假設最終會在語言中添加對它們的一些語法支持。既然有了此假設,人們可能想知道爲何還會很難達成共識。
在 comp.lang.python 和 python-dev 郵件列表中,關於如何最好地實現函數裝飾器的討論,時不時就會展開。沒有一個明確的爭辯理由,可是以下問題看起來分歧最大。
人們廣泛贊成,裝飾器語法對於當前而言是可取的。Guido 在第十屆Python大會 [3] 的 DevDay 主題演講中提到了[對裝飾器的語法支持](http://www.python.org/doc/essays/ppt/python10/py10keynote.pdf)[2],儘管[他後來講](https://mail.python.org/pipermail/python-dev/2002-February/020017.html)[5],這只是他「半開玩笑」提議的幾種擴展之一。會議結束後不久,Michael Hudson 在 python-dev 上[提出了主題](https://mail.python.org/pipermail/python-dev/2002-February/020005.html)[4],將最初的括號語法歸因於[Gareth McCaughan ](http://groups.google.com/groups%3Fhl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26selm%3Dslrna40k88.2h9o.Gareth.McCaughan%40g.local)[6] 先前在 comp.lang.python 上的提議。
類裝飾器彷佛是顯而易見的下一步,由於類定義和函數定義在語法上類似,可是 Guido 仍然有疑慮,類裝飾器幾乎確定不會在 Python 2.4 中出現。
從 2002 年 2 月到 2004 年 7 月,python-dev 裏的討論一直此起彼伏。數百篇回帖,人們提出了許多可能的語法變體。Guido 列了一份提案清單,帶到 EuroPython 2004 [7] 上討論。以後,他決定使用[Java風格的](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)[10] @decorator 語法,該語法在 2.4a2 中首次出現。
Barry Warsaw 將其命名爲「pie-decorator」語法,以記念 Pie-thon Parrot 比賽(譯註:這是當年的一件逸事,Parrot 虛擬機與 CPython 虛擬機比賽性能優劣),該事件與裝飾器語法幾乎同時發生,並且 @ 看起來有點像餡餅。Guido 在 Python-dev 上概述了他的要點[8],其中包括 [這篇文章](https://mail.python.org/pipermail/python-dev/2004-August/046672.html)[9],談論了一些(許多)被否決的內容。
對於將此特性命名爲「decorator」,有不少人抱怨。主要問題是該名稱與GoF書 中的用法不一致[11]。名稱「 decorator」可能更可能是用在編譯器領域中——一個語法樹被遍歷和註解。頗有可能會出現一個更好的名稱。
新的語法應該:
安德魯·庫奇林(Andrew Kuchling)在他的博客[14]中連接了許多有關動機和用例的討論。特別值得注意的是[Jim Huginin 的用例列表](https://mail.python.org/pipermail/python-dev/2004-April/044132.html)[15]。
當前在 Python 2.4a2 中實現的函數裝飾器的語法爲:
@dec2
@dec1
def func(arg1, arg2, ...):
pass複製代碼
這等效於:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))複製代碼
但沒有對變量 func 的過渡性賦值。裝飾器靠近函數的聲明。@ 符號清楚地代表這裏正在發生新的事情。
應用順序[16](從下到上)的基本原理是,它與函數用途的通常順序相匹配。在數學中,組合函數 (g o f)(x) 會轉換爲 g(f(x))。在 Python 中,"@g @f def foo()" 轉換爲 foo = g(f(foo))。
裝飾器語句是被約束的——任意的表達式都不能用。Guido 出於直覺[17],更喜歡這種方式。
當前語法還容許裝飾器在聲明時,能夠調用一個返回裝飾器的函數:
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass複製代碼
這等效於:
func = decomaker(argA, argB, ...)(func)複製代碼
使用返回裝飾器的函數的基本原理是,@ 符號後的部分能夠被視爲表達式(儘管句法上被限爲一個函數),而後該表達式返回的任何內容將被調用。參見聲明參數[16]。
大量的[18]不一樣語法被提了出來——與其嘗試令這些語法單獨起做用,更值得將它們分爲多個領域討論。試圖單獨討論[每種可能的語法](http://ucsu.colorado.edu/~bethard/py/decorators-output.py)[19]將是一種瘋狂的舉動,而且會產生一個徹底不明智的 PEP。
第一個語法點是裝飾器的位置。對於如下示例,咱們使用了 2.4a2 中的 @ 語法。
def 語句以前的裝飾器是第一種選擇,而且在 2.4a2 中就使用了它:
@classmethod
def foo(arg1,arg2):
pass
@accepts(int,int)
@returns(float)
def bar(low,high):
pass複製代碼
有許多人對該位置提出了反對意見——最主要的反對意見是,這是 Python 中第一個真正的前一行代碼會對下一行產生影響的狀況。2.4a3 中可用的語法要求每行一個裝飾器(在 a2 中,能夠在同一行上指定多個裝飾器),最後在 2.4 的最終版本中,每行只保留一個裝飾器。
人們還抱怨說,當使用多個裝飾器時,語法很快會變得笨重。可是,有人指出,在單個函數上使用大量裝飾器的可能性很小,所以這並非一個大問題。
這種形式的一些優勢是裝飾器位於方法的主體以外——顯然,它們是在定義函數時執行的。
另外一個好處是,寫在函數定義的前面,適合在不知道代碼內容時,就改變代碼的語義,也就是說,你知道如何正確地解釋代碼的語義,若是該語法沒有出如今函數定義以前,你須要回看並改變初始的理解。
Guido 決定他更喜歡[20]在「def」的前面行裏放置裝飾器,由於長長的參數列表就意味着裝飾器最好被「隱藏」起來 。
第二種形式是把裝飾器放在 def 與函數名稱之間,或者在函數名稱與參數列表之間:
def @classmethod foo(arg1,arg2):
pass
def @accepts(int,int),@returns(float) bar(low,high):
pass
def foo @classmethod (arg1,arg2):
pass
def bar @accepts(int,int),@returns(float) (low,high):
pass複製代碼
對該形式有兩個異議。第一,它很容易破壞源代碼的「可擴展性」——你沒法再經過搜索「def foo(」來找到函數的定義;第二,更嚴重的是,在使用多個裝飾器的狀況下,語法將會很是笨拙。
接下來的一種形式,它有必定數量的堅決支持者,就是把裝飾器放在"def"行的參數列表與末尾的「:」號之間:
def foo(arg1,arg2) @classmethod:
pass
def bar(low,high) @accepts(int,int),@returns(float):
pass複製代碼
Guido 將反對這種形式的論點(其中許多也適用於之前的形式)總結 [13]爲:
下一種形式是將裝飾器語法放在方法體的開頭,與當前文檔字符串(doctring)的所在位置相同:
def foo(arg1,arg2):
@classmethod
pass
def bar(low,high):
@accepts(int,int)
@returns(float)
pass複製代碼
對此形式的主要反對意見是,它須要「窺視」方法體才能肯定裝飾器。另外,即便裝飾器代碼在方法體內,但它並非在運行方法時執行。Guido 認爲 docstring 並不構成一個很好的反例,甚至「docstring」裝飾器頗有可能有助於將 docstring 移到函數體以外。
最後一種形式是用一個代碼塊將方法的代碼嵌套起來。在此示例中,咱們將使用「decorate」關鍵字,由於 @ 語法毫無心義。
decorate:
classmethod
def foo(arg1,arg2):
pass
decorate:
accepts(int,int)
returns(float)
def bar(low,high):
pass複製代碼
這種形式將致使被裝飾方法和非裝飾方法的縮進不一致。此外,被裝飾的方法體將從第三層縮進開始。
@classmethod
def foo(arg1,arg2):
pass
@accepts(int,int)
@returns(float)
def bar(low,high):
pass複製代碼
反對這種語法的主要意見是 Python 中當前未使用過 @ 符號(IPython 和 Leo 均使用了@符號),而且 @ 符號沒有意義。另外一個反對意見是,這會將當前未使用的字符(從有限的集合中)「浪費」在不被認爲是主要用途的事物上。
|classmethod
def foo(arg1,arg2):
pass
|accepts(int,int)
|returns(float)
def bar(low,high):
pass複製代碼
這是 @decorator 語法的一個變體——它的優勢是不會破壞 IPython 和 Leo。與 @ 語法相比,它的主要缺點是 | 符號看起來像大寫字母 I 和小寫字母 l。
[classmethod]
def foo(arg1,arg2):
pass
[accepts(int,int), returns(float)]
def bar(low,high):
pass複製代碼
對列表語法的主要反對意見是它當前是有意義的(當在方法以前使用時)。並且也沒有任何跡象代表該表達式是個裝飾器。
<classmethod>
def foo(arg1,arg2):
pass
<accepts(int,int), returns(float)>
def bar(low,high):
pass複製代碼
這些替代寫法都沒有太大的吸引力。涉及其它括號的寫法僅用於使裝飾器構造得不像是個列表。它們沒有作到任何使解析變得更容易的事情。'<...>'寫法存在解析問題,由於'<'和'>'已經解析爲未配對。它們還引發了進一步的解析歧義,由於右尖括號(>)多是一個大於號,而不是裝飾器的閉合符。
該寫法提議不用新的語法來實現——它提議用一個可自省的魔術函數來控制其後的函數。Jp Calderone 和 Philip Eby 都提供了此功能的實現。Guido 堅定反對這一點——不用新的語法,這樣的函數的魔力會極其高:
經過 sys.settraceback 使用具備「遠距動做」(action-at-a-distance)功能的函數,可能會適合一種潛在的功能,該功能沒法經過其它任何不更改語言的方式實現,可是對於裝飾器而言,狀況並不是如此。此處廣泛持有的觀點是,須要添加裝飾器做爲一種語法功能,以免 2.2 和 2.3 中使用的後綴表示法帶來的問題。裝飾器被認定爲一項重要的新語言功能,其設計須要具備前瞻性,而不是受到 2.3 版中能夠實現的東西所約束。
這個想法是來自 comp.lang.python 的共識(有關更多信息,請參見下面的社區共識。)Robert Brewer 撰寫了詳細的J2 提案[21]文檔,概述了支持這種形式的論點。此形式的最初問題有:
幾天後,Guido 出於兩個主要理由拒絕了該提案[22]。首先:
... 縮進塊的句法形式強烈暗示了其內容應爲語句序列,但實際上它卻不是——只有表達式是容許的,而且這些表達式存在隱式的「收集中」狀態,直到它們能夠被應用在隨後的函數定義爲止。...
其次:
... 關鍵字開始於塊的開頭,會引發不少關注。對於「 if」、「 while」、「 for」、「 try」、「 def」和「 class」,這是正確的。可是,「 using」關鍵字(或其它位置的關鍵字)不值得引發這種關注。重點應該放在裝飾器或裝飾器套件上,由於它們是隨後的函數定義的重要裝飾符。...
請讀者閱讀完整的回覆[22]。
Wiki 頁面[23]上還有許多其它變體和提議。
Java 中有一些時間最初使用 @ 做爲Javadoc 註釋[24]中的標記,後來在 Java 1.5 中用做[註解](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)[10],這與 Python 的裝飾器類似。@ 之前沒有在 Python 中用做標記的事實也意味着,很顯然早期版本的 Python 不可能解析此類代碼,從而可能致使細微的語義錯誤。這也意味着,什麼是裝飾器和什麼不是裝飾器,這種不肯定性被移除了。也就是說,@ 仍然是一個至關隨意的選擇。有些人建議使用 | 代替。
對於使用相似列表的語法(不管出如今何處)來指定裝飾器,一些替代方法被提了出來:[| ... |],* [...] * 和 <...>。
Guido 徵集一名志願者來實現他所偏好的語法,Mark Russell 響應並向 SF 提交了補丁[25]。這個新語法在 2.4a2 中可用。
@dec2
@dec1
def func(arg1, arg2, ...):
pass複製代碼
這等效於:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))複製代碼
儘管沒有在中間建立名爲 func 的變量。
在 2.4a2 中實現的版本容許在一行上包含多個 @decorator 子句。在 2.4a3 版中,此規定已嚴格限制爲每行只容許一個裝飾器。
Michael Hudson 的一個實現了「list-after-def」語法的 早期補丁[26] 還繼續活躍着。
在發佈 2.4a2 以後,Guido 表示,若是社區能夠達成社區共識、提供一份體面的提案和實現方案,他將對社區提案進行從新審覈,以迴應社區的反應。在出現了驚人數量的帖子以後,Python Wiki [18]收集了大量的替代方案,社區共識出現了(見下)。Guido [隨後拒絕了](https://mail.python.org/pipermail/python-dev/2004-September/048518.html)此方案[22],但補充說:
在 Python 2.4a3(將於本週四發佈)中,一切還保存在 CVS 中。對於 2.4b1,我將考慮將 @ 更改成其它單個字符,儘管我認爲 @ 具備與 Java 相似功能所使用的相同字符的優勢。有人認爲這並不徹底相同,由於 Java 中的 @ 用於不更改語義的屬性。可是 Python 的動態特性使它的語法元素永遠不會與其它語言中的相似構造具備徹底相同的含義,而且確定存在明顯的重疊。關於對第三方工具的影響:IPython 的做者認爲不會有太大影響;Leo 的做者說 Leo 將倖免於難(儘管這將使他和他的使用者有一些過渡性的痛苦)。我實際上以爲選擇一個在 Python 語法中其它地方已經使用過的字符,可能會使外部工具更難以適應,由於在這種狀況下解析將變得更加微妙。但坦率地說,我尚未決定,因此這裏有些擺動的空間。我如今不想再考慮其它的語法選擇:必須在某個時候中止,每一個人都有話說,但演出必須繼續。
本節記錄了被否決的 J2 語法,爲了歷史的完整性而將其包括在內。
在 comp.lang.python 上出現的共識是要提議 J2 語法(「J2」是在 PythonDecorators Wiki 頁面上的叫法):在 def 語句以前,做爲前綴的新關鍵字using
及裝飾器代碼塊。例如:
using:
classmethod
synchronized(lock)
def func(cls):
pass複製代碼
該語法的主要論點來自「可讀性計數」(readability counts)學說。簡而言之,它們是:
using
關鍵字和其代碼塊將單塊的 def 語句轉換成多塊的複合結構,相似於 try/finally 和其它。 羅伯特·布魯爾(Robert Brewer)爲此形式撰寫了詳細的提案[21],邁克爾·斯帕克斯(Michael Sparks)製做了[補丁](https://bugs.python.org/issue1013835)[27]。
如前所述,Guido 否決了此形式,並在給 python-dev 和 comp.lang.python 的消息[22]中概述了它的問題。
在 comp.lang.python 和 python-dev 郵件列表裏的許多討論,都集中在裝飾器的使用上,認爲它是一種比 staticmethod() 和 classmethod() 內置函數更簡潔的方法。固然其能力要比那個強大得多。本節介紹了一些使用示例。
def onexit(f):
import atexit
atexit.register(f)
return f
@onexit
def func():
...複製代碼
請注意,此示例可能不適合實際使用,僅用於演示目的。
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...複製代碼
def attrs(**kwds):
def decorate(f):
for k in kwds:
setattr(f, k, kwds[k])
return f
return decorate
@attrs(versionadded="2.2",
author="Guido van Rossum")
def mymethod(f):
...複製代碼
def accepts(*types):
def check_accepts(f):
assert len(types) == f.func_code.co_argcount
def new_f(*args, **kwds):
for (a, t) in zip(args, types):
assert isinstance(a, t), \
"arg %r does not match %s" % (a,t)
return f(*args, **kwds)
new_f.func_name = f.func_name
return new_f
return check_accepts
def returns(rtype):
def check_returns(f):
def new_f(*args, **kwds):
result = f(*args, **kwds)
assert isinstance(result, rtype), \
"return value %r does not match %s" % (result,rtype)
return result
new_f.func_name = f.func_name
return new_f
return check_returns
@accepts(int, (int,float))
@returns((int,float))
def func(arg1, arg2):
return arg1 * arg2複製代碼
def provides(*interfaces):
"""
An actual, working, implementation of provides for
the current implementation of PyProtocols. Not
particularly important for the PEP text.
"""
def provides(typ):
declareImplementation(typ, instancesProvide=interfaces)
return typ
return provides
class IBar(Interface):
"""Declare something about IBar here"""
@provides(IBar)
class Foo(object):
"""Implement something here..."""複製代碼
固然,儘管沒有語法上的支持,但全部這些示例現在都是可能的。
PEP 3129 [#PEP-3129]提議從 Python 2.6 開始添加類裝飾器。
[1] PEP 3129, "Class Decorators", Winter http://www.python.org/dev/peps/pep-3129
[2] http://www.python.org/doc/essays/ppt/python10/py10keynote.pdf
[3] http://www.python.org/workshops/2002-02/
[4] https://mail.python.org/pipermail/python-dev/2002-February/020005.html
[5] https://mail.python.org/pipermail/python-dev/2002-February/020017.html
[6] http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=slrna40k88.2h9o.Gareth.McCaughan%40g.local
[7] http://www.python.org/doc/essays/ppt/euro2004/euro2004.pdf
[8] https://mail.python.org/pipermail/python-dev/2004-August/author.html
[9] https://mail.python.org/pipermail/python-dev/2004-August/046672.html
[10] (1, 2) http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
[11] http://patterndigest.com/patterns/Decorator.html
[12] http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=mailman.1010809396.32158.python-list%40python.org
[13] (1, 2) https://mail.python.org/pipermail/python-dev/2004-August/047112.html
[14] http://www.amk.ca/diary/archives/cat_python.html#003255
[15] https://mail.python.org/pipermail/python-dev/2004-April/044132.html
[16] (1, 2) https://mail.python.org/pipermail/python-dev/2004-September/048874.html
[17] https://mail.python.org/pipermail/python-dev/2004-August/046711.html
[18] (1, 2) http://www.python.org/moin/PythonDecorators
[19] http://ucsu.colorado.edu/~bethard/py/decorators-output.py
[20] https://mail.python.org/pipermail/python-dev/2004-March/043756.html
[21] (1, 2) http://www.aminus.org/rbre/python/pydec.html
[22] (1, 2, 3, 4) https://mail.python.org/pipermail/python-dev/2004-September/048518.html
[23] https://wiki.python.org/moin/PythonDecoratorProposals
[24] http://java.sun.com/j2se/javadoc/writingdoccomments/
[25] https://bugs.python.org/issue979728
[26] http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar-3.diff
[27] https://bugs.python.org/issue1013835
[28] http://peak.telecommunity.com/PyProtocols.html
[29] https://mail.python.org/pipermail/python-dev/2004-March/thread.html
本文檔已經放置在公共領域。源文檔:github.com/python/peps…
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。