内容
正告正告正告
本文档旨在形容装璜器语法和做出决定的过程。它既不试图涵盖大量潜在的代替语法,也不试图详尽列出每种模式的所有长处和毛病。
摘要
以后用于转换函数和办法的形式(例如,将它们申明为类或静态方法)很蠢笨,并且可能导致难以了解的代码。在现实的状况下,这些转换应该在代码中作申明的地位进行。本 PEP 引入了对函数或办法申明作转换的新语法。
动机
以后对函数或办法作变换的形式会把理论的变换置于函数体之后。对于大型函数,这会将函数行为的要害组成部分与其余的函数内部接口的定义离开。例如:
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 主题演讲中提到了对装璜器的语法反对[2],只管他起初说[5],这只是他“半开玩笑”提议的几种扩大之一。会议完结后不久,Michael Hudson 在 python-dev 上提出了主题[4],将最后的括号语法归因于Gareth McCaughan [6] 先前在 comp.lang.python 上的提议。
类装璜器仿佛是不言而喻的下一步,因为类定义和函数定义在语法上类似,然而 Guido 依然有疑虑,类装璜器简直必定不会在 Python 2.4 中呈现。
从 2002 年 2 月到 2004 年 7 月,python-dev 里的探讨始终此起彼伏。数百篇回帖,人们提出了许多可能的语法变体。Guido 列了一份提案清单,带到 EuroPython 2004 [7] 上探讨。之后,他决定应用Java格调的[10] @decorator 语法,该语法在 2.4a2 中首次呈现。
Barry Warsaw 将其命名为“pie-decorator”语法,以留念 Pie-thon Parrot 较量(译注:这是当年的一件逸事,Parrot 虚拟机与 CPython 虚拟机较量性能优劣),该事件与装璜器语法简直同时产生,而且 @ 看起来有点像馅饼。Guido 在 Python-dev 上概述了他的要点[8],其中包含 这篇文章[9],议论了一些(许多)被否决的内容。
对于“Decorator”名称
对于将此个性命名为“decorator”,有很多人埋怨。次要问题是该名称与GoF书 中的用法不统一[11]。名称“ decorator”可能更多是用在编译器畛域中——一个语法树被遍历和注解。很有可能会呈现一个更好的名称。
设计指标
新的语法应该:
安德鲁·库奇林(Andrew Kuchling)在他的博客[14]中链接了许多无关动机和用例的探讨。特地值得注意的是Jim Huginin 的用例列表[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]不同语法被提了进去——与其尝试令这些语法独自起作用,更值得将它们分为多个畛域探讨。试图独自探讨每种可能的语法[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
这种模式将导致被装璜办法和非装璜办法的缩进不统一。此外,被装璜的办法体将从第三层缩进开始。
语法模式
@decorator:
@classmethod
def foo(arg1,arg2):
pass
@accepts(int,int)
@returns(float)
def bar(low,high):
pass
拥护这种语法的次要意见是 Python 中以后未应用过 @ 符号(IPython 和 Leo 均应用了@符号),并且 @ 符号没有意义。另一个拥护意见是,这会将以后未应用的字符(从无限的汇合中)“节约”在不被认为是主要用途的事物上。
| decorator:
|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
对列表语法的次要拥护意见是它以后是有意义的(当在办法之前应用时)。而且也没有任何迹象表明该表达式是个装璜器。
应用其它括号(<…>,[[…]],…)的列表语法:
def foo(arg1,arg2):
pass
def bar(low,high):
pass
这些代替写法都没有太大的吸引力。波及其它括号的写法仅用于使装璜器结构得不像是个列表。它们没有做到任何使解析变得更容易的事件。'<…>&#8217;写法存在解析问题,因为'<&#8216;和&#8217;>&#8217;曾经解析为未配对。它们还引起了进一步的解析歧义,因为右尖括号(>)可能是一个大于号,而不是装璜器的闭合符。
decorate()
该写法提议不必新的语法来实现——它提议用一个可自省的魔术函数来管制其后的函数。Jp Calderone 和 Philip Eby 都提供了此性能的实现。Guido 坚定拥护这一点——不必新的语法,这样的函数的魔力会极其高:
通过 sys.settraceback 应用具备“远距动作”(action-at-a-distance)性能的函数,可能会适宜一种潜在的性能,该性能无奈通过其它任何不更改语言的形式实现,然而对于装璜器而言,状况并非如此。此处广泛持有的观点是,须要增加装璜器作为一种语法性能,以防止 2.2 和 2.3 中应用的后缀表示法带来的问题。装璜器被认定为一项重要的新语言性能,其设计须要具备前瞻性,而不是受到 2.3 版中能够实现的货色所束缚。
新关键字(和代码块)
这个想法是来自 comp.lang.python 的共识(无关更多信息,请参见上面的社区共识。)Robert Brewer 撰写了具体的J2 提案[21]文档,概述了反对这种模式的论点。此模式的最后问题有:
- 它须要一个新关键字,因而还须要一个"from __future__ import decorators"的语句。
- 关键字的抉择仍有争议。然而,"using"已成为该共识的抉择,并被用于提案和实现中。
- 关键字/代码块模式会产生相似于一般代码块的内容,但并不是。尝试在此块中应用语句将导致语法错误,这可能会使用户感到困惑。
几天后,Guido 出于两个次要理由回绝了该提案[22]。首先:
… 缩进块的句法模式强烈暗示了其内容应为语句序列,但实际上它却不是——只有表达式是容许的,并且这些表达式存在隐式的“收集中”状态,直到它们能够被利用在随后的函数定义为止。…
其次:
… 关键字开始于块的结尾,会引起很多关注。对于“ if”、“ while”、“ for”、“ try”、“ def”和“ class”,这是正确的。然而,“ using”关键字(或其它地位的关键字)不值得引起这种关注。重点应该放在装璜器或装璜器套件上,因为它们是随后的函数定义的重要装璜符。…
其它模式
Wiki 页面[23]上还有许多其它变体和提议。
为什么是@?
Java 中有一些工夫最后应用 @ 作为Javadoc 正文[24]中的标记,起初在 Java 1.5 中用作注解[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 随后回绝了此计划[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)学说。简而言之,它们是:
如前所述,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():
...
请留神,此示例可能不适宜理论应用,仅用于演示目标。
用单例实例定义一个类。请留神,一旦类隐没,进取的程序员须要更有创造力能力创立更多的实例。(出自 python-dev 上的 Shane Hathaway )
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
向一个函数增加属性。(基于 Anders Munch 在 python-dev 上公布的示例)
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):
...
限定函数参数和返回类型。请留神,这会将 func_name 属性从旧函数复制到新函数。func_name 在 Python 2.4a3 中是可写的:
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
申明一个类实现特定的一个(一组)接口。摘自 Bob Ippolito 在 python-dev 上发表的文章,基于其在PyProtocols [28]的教训根底上。
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..."""
当然,只管没有语法上的反对,但所有这些示例现在都是可能的。
(不再是)未决问题
尚不确定类装璜器是否会在未来集成到 Python 中。Guido 表白了对这一概念持狐疑态度,但不同的人在 python-dev 里提出了一些无力的论据[29](搜寻 PEP 318 &#8212; 发帖草案)。类装璜器在 Python 2.4 中是极不可能的。
PEP 3129 [#PEP-3129]提议从 Python 2.6 开始增加类装璜器。
@ 字符的抉择将在 Python 2.4b1 之前从新查看。(最初,@ 字符被保留。)
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈 ,发送 “J” 即可收费获取,每日干货分享