`
yzjklove
  • 浏览: 63757 次
  • 性别: Icon_minigender_1
  • 来自: 广东省惠州市
社区版块
存档分类
最新评论

可爱的 Python: Decorator 简化元编程

阅读更多
Python 使元编程成为可能,不过每个版本的 Python 都有一些细微的区别(并且不是完全兼容),这使我们实现元编程的道路变得更加崎岖。一类函数对象的使用由来已久,同样还有一些技术用于探索和实现魔术般的属性。在版本 2.2 中,Python 增加了一种很有帮助的定制元类机制,但是其代价就是令用户绞尽脑汁。最近,在 2.4 版本中,Python 增加了 “decorator” ,这是适于执行大部分元编程的最新方式 —— 也是到目前为止对用户最友好的方式。
少劳多得

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

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



@arg_sayer() 绕了很多弯路,但只获得非常有限的结果,不过对于它所阐明的几方面来说,这是值得的:

Foo.say() 方法对于不同的实例有不同的行为。在这个例子中,不同之处只是一个数据值,可以轻松地通过其他方式改变这个值;不过原则上来说,decorator 可以根据运行时的决策来彻底重写这个方法。


本例中未修饰的 Foo.say() 方法是一个简单的占位符,其整个行为都是由 decorator 决定的。然而,在其他情况下,decorator 可能会将未修饰的方法与一些新功能相结合。


正如我们已经看到的一样,Foo.say() 的修改是通过 FooMaker() 类工厂在运行时严格确定的。可能更加典型的情况是在顶级定义类中使用 decorator,这些类只依赖于编译时可用的条件(这通常就足够了)。


decorator 都是参数化的。或者更确切地说,arg_sayer() 本身根本就不是一个真正的 decorator;arg_sayer() 所返回的 函数 —— what_sayer() 就是一个使用了闭包来封装其数据的 decorator 函数。参数化的 decorator 较为常见,但是它们将所需的函数嵌套为三层。





回页首




迈进元类领域

正如上一节中介绍的一样,decorator 并不能完全取代元类挂钩,因为它们只修改了方法,而未添加或删除方法。实际上,这样说并不完全正确。作为一个 Python 函数,decorator 完全可以实现其他 Python 代码所实现的任何功能。通过修饰一个类的 .__new__() 方法(甚至是其占位符版本),您实际上可以更改附加到该类的方法。尽管尚未在现实中看到这种模式,不过我认为它有着某种必然性,甚至可以作为 _metaclass_ 指派的一项改进:


清单 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'



在 change_methods() decorator 示例中,我们添加并删除了几个固定的方法,不过这是毫无意义的。在更现实的情况中,应使用上一节中提到的几个模式。例如,参数化的 decorator 可以接受一个能表示要添加或删除的方法的数据结构;或者由数据库查询之类的某些环境特性做出这一决策。这种对附加方法的操作也可以像之前一样打包到一个函数工厂中,这将使最终决策延迟到运行时。这些新兴技术也许比 _metaclass_ 指派更加万能。例如,您可以调用一个增强了的 change_methods(),如下所示:


清单 10. 增强的 change_methods()
       
class Foo(object):
    @change_methods(add=(foo, bar, baz), remove=(fliz, flam))
    def __new__(): pass







回页首




修改调用模型

您将看到,有关 decorator 的最典型的例子可能是使一个函数或方法来实现 “其他功能”,同时完成其基本工作。例如,在诸如 Python Cookbook Web 站点(请参见 参考资料 中的链接)之类的地方,您可以看到 decorator 添加了诸如跟踪、日志记录、存储/缓存、线程锁定以及输出重定向之类的功能。与这些修改相关(但实质略有区别)的是修饰 “之前” 和 “之后”。对于修饰之前/之后来说,一种有趣的可能性就是检查传递给函数的参数和函数返回值的类型。如果这些类型并非如我们预期的一样,那么这种 type_check() decorator 就可能会触发一个异常,或者采取一些纠正操作。

与这种 decorator 前/后类似的情况,我想到了 R 编程语言和 NumPy 特有的函数的 “elementwise” 应用。在这些语言中,数学函数通常应用于元素序列中的每个元素,但也会应用于单个数字。

当然,map() 函数、列表内涵(list-comprehension)和最近的生成器内涵(generator-comprehension 都可以让您实现 elementwise 应用。但是这需要较小的工作区来获得类似于 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



创建一个可以 “增强” 普通数值函数的 decorator 并不困难:


清单 12. 将函数转换成 elementwise 函数
       
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() 函数并不困难;毕竟 decorator 只需占据几行。但是作为对面向方面编程的一种认可,这个例子让我们可以分离 那些在不同层次上运作的关注事项。我们可以编写各种数值计算函数,希望它们都可转换成 elementwise 调用模型,而不用考虑参数类型测试和返回值类型强制转换的细节。

对于那些对单个事物或事物序列(此时要保留序列类型)进行操作的函数来说,elementwise() decorator 均可同样出色地发挥作用。作为一个练习,您可尝试去解决如何允许相同的修饰后调用来接受和返回迭代器(提示:如果您只是想迭代一次完整的 elementwise 计算,那么当且仅当传入的是一个迭代对象时,才能这样简化一些。)

您将碰到的大多数优秀的 decorator 都在很大程度上采用了这种组合正交关注的范例。传统的面向对象编程,尤其是在诸如 Python 之类允许多重继承的语言中,都会试图使用一个继承层次结构来模块化关注事项。然而,这仅会从一个祖先那里获取一些方法,而从其他祖先那里获取其他方法,因此需要采用一种概念,使关注事项比在面向方面的思想中更加分散。要充分利用生成器,就要考虑一些与混搭方法不同的问题:可以处于方法本身的 “核心” 之外的关注事项为依据,使各 方法以不同方式工作。






回页首




修饰 decorator

在结束本文之前,我想为您介绍一种确实非常出色的 Python 模块,名为 decorator,它是由与我合著过一些图书的 Michele Simionato 编写的。该模块使 decorator 的开发变得更加美妙。decorator 模块的主要组件具有某种自反式的优雅,它是一个称为 decorator() 的 decorator。与未修饰的函数相比,使用 @decorator 修饰过的函数可以通过一种更简单的方式编写。(相关资料请参看 参考资料)。

Michele 已经为自己的模块编写了很好的文档,因此这里不再赘述;不过我非常乐意介绍一下它所解决的基本问题。decorator 模块有两大主要优势。一方面,它使您可以编写出嵌套层次更少的 decorator,如果没有这个模块,您就只能使用更多层次(“平面优于嵌套”);但更加有趣的是这样一个事实:它使得修饰过的函数可以真正地与其在元数据中未修饰的版本相匹配,这是我的例子中没有做到的。例如,回想一下我们上面使用过的简单 “跟踪” decorator addspam():


清单 13. 一个简单的 decorator 是如何造成元数据崩溃的
       
>>> 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)



尽管这个修饰过的函数的确完成 了自己增强过的工作,但若进一步了解,就会发现这并不是完全正确的,尤其是对于那些关心这种细节的代码分析工具或 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 更加有利,同时,其保留行为的元数据的也更出色了。当然,阅读 Michele 开发这个模块所使用的全部资料会使您回到大脑混沌的世界,我们将这留给 Simionato 博士一样的宇宙学家好了。

分享到:
评论

相关推荐

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

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

    Python:Python高级特性:装饰器与迭代器

    ### Python高级特性详解:装饰器与迭代器 #### 一、装饰器 ##### 1.1 装饰器的概念 装饰器是Python语言中一项非常强大的特性,它允许开发者在不修改...掌握这些高级特性对于深入理解和运用Python编程语言至关重要。

    C#面向对象设计模式纵横谈(10):Decorator 装饰模式(结构型模式) (Level 300)

    在这个“C#面向对象设计模式纵横谈”系列的第十篇中,我们将深入探讨Decorator模式。 首先,让我们了解装饰模式的基本组件: 1. **Component(组件)**:这是被装饰的对象的接口。它可以是抽象类或接口,定义了...

    python-base.py: 千行代码入门Python python-visual.py: 15张图入门Matplotlib

    python_base.py: 千行代码入门Python python_visual.py: 15张图入门Matplotlib python_visual_animation.py: 使用Matplotlib画...python_decorator.py: Python进阶: 通过实例详解装饰器(附代码) python_datetime.p

    decorator python(decorator-3.4.0.tar.gz).rar

    装饰器在Python编程语言中是一种强大的工具,它允许程序员在不修改原有代码的基础上,增加或扩展函数的功能。这个"decorator python"模块是版本3.4.0的实现,其核心概念是通过函数来包装(即装饰)其他函数,以增强...

    C#面向对象设计模式纵横谈(10):Decorator 装饰模式(结构型模式)

    在面向对象编程中,设计模式是一种在特定情况下解决问题的标准化方法。其中,装饰模式(Decorator Pattern)作为结构型模式之一,在不改变现有对象的情况下为其添加新的功能。本文档通过一个具体的示例——游戏中的...

    Python高级编程和异步IO并发编程

    在Python编程领域,掌握高级特性以及网络编程与并发模型是至关重要的。本资源"Python高级编程和异步IO并发编程"旨在深入探讨这些主题,帮助开发者提升技能,以实现更高效、更强大的程序设计。 首先,让我们从面向...

    python-decorator-3.4.0-3.el7.noarch.rpm

    离线安装包,亲测可用

    LearnPython-master.zip

    python_base.py: 千行代码入门Python python_visual.py: 15张图入门Matplotlib python_visual_animation.py: 使用Matplotlib画...python_decorator.py: Python进阶: 通过实例详解装饰器(附代码) python_datetime.p

    Python高级编程

    "Python高级编程"涵盖了Python语言的深入理解和高级用法,这包括但不限于元编程、装饰器、生成器、协程、多线程与多进程、高级数据结构、错误处理、模块化编程、性能优化以及Python在大型项目中的应用。 1. **元...

    最新Python高级编程.最新版.pdf

    1. **元编程**:Python支持元编程,即在运行时动态地创建或修改类和函数。元类(metaclass)是创建类的对象,可以用来定制类的行为。装饰器(decorator)是一种特殊类型的函数,用于修改或增强其他函数的功能。 2. ...

    python-decorator-3.0.1-3.1.el6.noarch.rpm

    python-decorator-3.0.1-3.1.el6.noarch

    Decorator:Decorator是一个Android库,可帮助在RecyclerViews中创建可组合的边距和分隔线

    安装implementation ' io.cabriole:decorator:1.3.0 '用可用的最新版本替换xxx如何使用只需创建以下示例中提供的装饰之一,然后使用以下命令将它们添加到RecyclerView中: recyclerView.addItemDecoration...

    Python库 | drf_nested_decorator-0.3-py2-none-any.whl

    资源分类:Python库 所属语言:Python 资源全名:drf_nested_decorator-0.3-py2-none-any.whl 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    python实现Decorator模式实例代码

    本文研究的主要是python实现Decorator模式,具体介绍如下。 一般来说,装饰器是一个函数,接受一个函数(或者类)作为参数,返回值也是也是一个函数(或者类)。首先来看一个简单的例子: # -*- coding: utf-8 -*- ...

    python decorator==4.4.2

    Python装饰器是Python编程语言中的一个强大特性,它允许我们修改或增强函数、类以及其他对象的行为,而无需改变它们的源代码。在Odoo这样的框架中,装饰器被广泛用于实现如权限控制、日志记录、性能分析等功能。在...

    Python-关于Python中进行函数式编程的Awesome东西的列表

    - `decorator`库提供了一种更高级的装饰器实现方式,支持元编程和堆叠装饰器。 通过学习和应用这些概念和工具,开发者可以编写出更简洁、清晰且易于测试的Python代码,提高代码的复用性和可维护性。函数式编程思想...

    Python编程实战:运用设计模式、并发和程序库创建高质量程序

    在Python中,我们可以应用诸如单例模式(Singleton)、工厂模式(Factory)、装饰器模式(Decorator)等来增强代码的可读性和可维护性。例如,装饰器模式允许我们在不修改原有函数代码的情况下,添加额外的功能或...

    Python+核心编程2.rar

    3. **面向对象编程**:Python支持面向对象编程,包括类的定义、继承、封装、多态等概念,以及如何创建和使用对象。 4. **异常处理**:Python中的try/except/finally语句用于处理程序运行时可能出现的错误和异常,以...

Global site tag (gtag.js) - Google Analytics