热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于python:函数和方法的装饰器

内容正告正告正告摘要动机为什么这很难?背景对于“Decorator”名称设计指标以后语法语法的抉择装璜器地位语法模式为什么是@?以后实现与历史社区共识例子(不

内容

  • 正告正告正告
  • 摘要
  • 动机
    为什么这很难?
  • 背景
  • 对于“Decorator”名称
  • 设计指标
  • 以后语法
  • 语法的抉择
    装璜器地位
    语法模式
    为什么是@?
  • 以后实现与历史
    社区共识
  • 例子
  • (不再是)未决问题
  • 参考资料
  • 版权

正告正告正告
本文档旨在形容装璜器语法和做出决定的过程。它既不试图涵盖大量潜在的代替语法,也不试图详尽列出每种模式的所有长处和毛病。

摘要
以后用于转换函数和办法的形式(例如,将它们申明为类或静态方法)很蠢笨,并且可能导致难以了解的代码。在现实的状况下,这些转换应该在代码中作申明的地位进行。本 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 邮件列表中,对于如何最好地实现函数装璜器的探讨,时不时就会开展。没有一个明确的辩论理由,然而如下问题看起来一致最大。

  • 对于“用意的申明”搁置何处的一致。简直所有人都批准,在函数定义的开端装璜/转换函数不是最佳的。除此之外,仿佛没有明确的共识将这些信息放在何处。
  • 语法束缚。Python 是一种语法简略的语言,除开“捣鬼”(无论从表面上还是思考到语言解析器),对能够实现和不能实现的事件都有相当严格的束缚。没有显著的办法来组织这些信息,以便刚接触该概念的人们会想:“哦,是的,我晓得你在做什么。” 看起来最好的方法就是避免新用户对语法的含意造成谬误的心智模型。
  • 总体上不相熟该概念。对于那些相熟代数(或者只是根本算术)或至多应用过其它编程语言的人来说,Python 的大部分内容都是合乎直觉的。但在 Python 中遇到装璜器概念之前,很少有人会接触到这个概念。没有一个很强的先验模因(preexisting meme)能蕴含这个概念。
  • 语法上的探讨所取得的关注,大体上超过了所有其它货色所取得的关注。读者能够看到的三元运算符探讨,与PEP 308相干,也是这样的例子。

背景
人们广泛批准,装璜器语法对于以后而言是可取的。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”可能更多是用在编译器畛域中——一个语法树被遍历和注解。很有可能会呈现一个更好的名称。

设计指标
新的语法应该:

  • 实用于任意包装器(wrapper),包含用户定义的可调用对象以及现有的内置类型classmethod() 和 staticmethod() 。此要求还意味着装璜器语法必须反对将参数传递给 wrapper 的构造函数
  • 每个定义需反对多重包装器
  • 过程应清晰可见;至多应该显著到令新用户在编写代码时能够平安地疏忽它
  • 成为一种“……一旦解释就容易记住的”语法
  • 不会使未来的扩大变艰难
  • 易于输出;应用了它的程序应该冀望常常应用它
  • 不会对疾速浏览代码造成艰难。搜寻所有定义、特定定义或函数的入参应该要容易
  • 不应使辅助反对工具,如语言敏感的编辑器和其它“ 玩具解析器工具 ”[12] ,变得复杂化
  • 容许未来的编译器针对装璜器进行优化。Python 的 JIT 编译器有心愿在未来成为事实,这就要求装璜器的语法要先于函数的定义
  • 从以后暗藏的函数开端,移到最后面[13]

安德鲁·库奇林(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)学说。简而言之,它们是:

  • 一个套件比多个 @ 行更好。using 关键字和其代码块将单块的 def 语句转换成多块的复合构造,相似于 try/finally 和其它。
  • 对于标识符(token),关键字比标点符号更好。关键字与标识符的现有用法相符。不须要新的标识符类别。关键字将 Python 装璜器与 Java 注解和 .Net 属性辨别开,它们不言而喻并非同类。
    罗伯特·布鲁尔(Robert Brewer)为此模式撰写了具体的提案[21],迈克尔·斯帕克斯(Michael Sparks)制作了补丁[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():
    ...

请留神,此示例可能不适宜理论应用,仅用于演示目标。

用单例实例定义一个类。请留神,一旦类隐没,进取的程序员须要更有创造力能力创立更多的实例。(出自 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” 即可收费获取,每日干货分享


推荐阅读
author-avatar
jny2272191
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有