Python中用Decorator来简化元编程的教程

935次阅读  |  发布于5年以前

少劳多得

Decorator 与 Python 之前引入的元编程抽象有着某些共同之处:即使没有这些技术,您也一样可以实现它们所提供的功能。正如 Michele Simionato 和我在 可爱的 Python 专栏的早期文章 中指出的那样,即使在 Python 1.5 中,也可以实现 Python 类的创建,而不需要使用 "元类" 挂钩。

Decorator 根本上的平庸与之非常类似。Decorator 所实现的功能就是修改紧接 Decorator 之后定义的函数和方法。这总是可能的,但这种功能主要是由 Python 2.2 中引入的 classmethod() 和 staticmethod() 内置函数驱动的。在旧式风格中,您可以调用 classmethod(),如下所示:
清单 1. 典型的 "旧式" classmethod


    class C:
      def foo(cls, y):
        print "classmethod", cls, y
      foo = classmethod(foo)

虽然 classmethod() 是内置函数,但并无独特之处;您也可以使用自己的方法转换函数。例如:
清单 2. 典型的 "旧式" 方法的转换


    def enhanced(meth):
      def new(self, y):
        print "I am enhanced"
        return meth(self, y)
      return new
    class C:
      def bar(self, x):
        print "some method says:", x
      bar = enhanced(bar)

decorator 所做的一切就是使您避免重复使用方法名,并且将 decorator 放在方法定义中第一处提及其名称的地方。例如:
清单 3. 典型的 "旧式" classmethod


    class C:
      @classmethod
      def foo(cls, y):
        print "classmethod", cls, y
      @enhanced
      def bar(self, x):
        print "some method says:", x

decorator 也可以用于正则函数,采用的是与类中的方法相同的方式。令人惊奇的是,这一切是如此简单(严格来说,甚至有些不必要),只需要对语法进行简单修改,所有东西就可以工作得更好,并且使得程序的论证更加轻松。通过在方法定义的函数之前列出多个 decorator,即可将 decorator 链接在一起;良好的判断可以有助于防止将过多 decorator 链接在一起,不过有时候将几个 decorator 链接在一起是有意义的:
清单 4. 链接 decorator


    @synchronized
    @logging
    def myfunc(arg1, arg2, ...):
      # ...do something
    # decorators are equivalent to ending with:
    #  myfunc = synchronized(logging(myfunc))
    # Nested in that declaration order

Decorator 只是一个语法糖,如果您过于急切,那么它就会使您搬起石头砸了自己的脚。decorator 其实就是一个至少具有一个参数的函数 ―― 程序员要负责确保 decorator 的返回内容仍然是一个有意义的函数或方法,并且实现了原函数为使连接有用而做的事情。例如,下面就是 decorator 两个不正确的用法:
清单 5. 没有返回函数的错误 decorator


    >>> def spamdef(fn):
    ...   print "spam, spam, spam"
    ...
    >>> @spamdef
    ... def useful(a, b):
    ...   print a**2 + b**2
    ...
    spam, spam, spam
    >>> useful(3, 4)
    Traceback (most recent call last):
     File "<stdin>", line 1, in ?
    TypeError: 'NoneType' object is not callable

decorator 可能会返回一个函数,但这个函数与未修饰的函数之间不存在有意义的关联:
清单 6. 忽略传入函数的 decorator


    >>> def spamrun(fn):
    ...   def sayspam(*args):
    ...     print "spam, spam, spam"
    ...   return sayspam
    ...
    >>> @spamrun
    ... def useful(a, b):
    ...   print a**2 + b**2
    ...
    >>> useful(3,4)
    spam, spam, spam

最后,一个表现更良好的 decorator 可以在某些方面增强或修改未修饰函数的操作:
清单 7. 修改未修饰函数行为的 decorator


    >>> def addspam(fn):
    ...   def new(*args):
    ...     print "spam, spam, spam"
    ...     return fn(*args)
    ...   return new
    ...
    >>> @addspam
    ... def useful(a, b):
    ...   print a**2 + b**2
    ...
    >>> useful(3,4)
    spam, spam, spam
    25

您可能会质疑,useful() 到底有多么有用?addspam() 真的是那样出色的增强 吗?但这种机制至少符合您通常能在有用的 decorator 中看到的那种模式。

高级抽象简介

根据我的经验,元类应用最多的场合就是在类实例化之后对类中的方法进行修改。decorator 目前并不允许您修改类实例化本身,但是它们可以修改依附于类的方法。这并不能让您在实例化过程中动态添加或删除方法或类属性,但是它让这些方法可以在运行时根据环境的条件来变更其行为。现在从技术上来说,decorator 是在运行 class 语句时应用的,对于顶级类来说,它更接近于 "编译时" 而非 "运行时"。但是安排 decorator 的运行时决策与创建类工厂一样简单。例如:
清单 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

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8