美文网首页Python点滴Pythoner集中营程序员
[译] Python装饰器Part II:装饰器参数

[译] Python装饰器Part II:装饰器参数

作者: TypingQuietly | 来源:发表于2015-07-05 19:17 被阅读1375次

这是Python装饰器讲解的第二部分,上一篇:Python装饰器Part I:装饰器简介

回顾:不带参数的装饰器

Python装饰器Part I:装饰器简介中,我演示了怎么样使用无参数的装饰器,主要是使用类式装饰器,因为这样更容易理解。
如果我们创建了一个不带参数的装饰器,被装饰的方法会传递给装饰器的构造器,然后在被装饰的函数被调用的时候,装饰器的__call__()方法就会执行。

class decoratorWithoutArguments(object):

    def __init__(self, f):
        """
        If there are no decorator arguments, the function
        to be decorated is passed to the constructor.
        """
        print "Inside __init__()"
        self.f = f

    def __call__(self, *args):
        """
        The __call__ method is not called until the
        decorated function is called.
        """
        print "Inside __call__()"
        self.f(*args)
        print "After self.f(*args)"

@decoratorWithoutArguments
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4

print "After decoration"

print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "After first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "After second sayHello() call"

任何传递给被装饰方法的参数都将传递给__call__(),输出日志是:

Inside __init__()
After decoration
Preparing to call sayHello()
Inside __call__()
sayHello arguments: say hello argument list
After self.f(*args)
After first sayHello() call
Inside __call__()
sayHello arguments: a different set of arguments
After self.f(*args)
After second sayHello() call

需要注意的是,在“装饰”阶段,只有__init__()会被调用;同时只有在被装饰方法被调用的时候,__call__()才会被调用。

带参数的装饰器

现在我们把上面的那个例子简单的改动一下,看看在添加装饰器参数的情况下会发生什么情况:

class decoratorWithArguments(object):

    def __init__(self, arg1, arg2, arg3):
        """
        If there are decorator arguments, the function
        to be decorated is not passed to the constructor!
        """
        print "Inside __init__()"
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, f):
        """
        If there are decorator arguments, __call__() is only called
        once, as part of the decoration process! You can only give
        it a single argument, which is the function object.
        """
        print "Inside __call__()"
        def wrapped_f(*args):
            print "Inside wrapped_f()"
            print "Decorator arguments:", self.arg1, self.arg2, self.arg3
            f(*args)
            print "After f(*args)"
        return wrapped_f

@decoratorWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4

print "After decoration"

print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"

从输出结果来看,运行的效果发生了明显的变化:

Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

现在,在“装饰”阶段,构造器和__call__()都会被依次调用,__call__()也只接受一个函数对象类型的参数,而且必须返回一个装饰方法去替换原有的方法,__call__()只会在“装饰”阶段被调用一次,接着返回的装饰方法会被实际用在调用过程中。
尽管这个行为很合理,构造器现在被用来捕捉装饰器的参数,而且__call__()不能再被当做装饰方法,相反要利用它来完成装饰的过程。尽管如此,第一次见到这种与不带参数的装饰器迥然不同的行为还是会让人大吃一惊,而且它们的编程范式也有很大的不同。

带参数的函数式装饰器

最后,让我们看一下更复杂的函数式装饰器,在这里你不得不一次完成所有的事情:

def decoratorFunctionWithArguments(arg1, arg2, arg3):
    def wrap(f):
        print "Inside wrap()"
        def wrapped_f(*args):
            print "Inside wrapped_f()"
            print "Decorator arguments:", arg1, arg2, arg3
            f(*args)
            print "After f(*args)"
        return wrapped_f
    return wrap

@decoratorFunctionWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4

print "After decoration"

print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"

输出结果:

Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

函数式装饰器的返回值必须是一个函数,能包装原有被包装函数。也就是说,Python会在装饰发生的时候拿到并且调用这个返回的函数结果,然后传递给被装饰的函数,这就是为什么我们在装饰器的实现里嵌套定义了三层的函数,最里层的那个函数是新的替换函数。
因为闭包的特性, wrapped_f()在不需要像在类式装饰器例子中一样显示存储arg1, arg2, arg3这些值的情况下,就能够访问这些参数。不过,这恰巧是我觉得“显式比隐式更好”的例子。尽管函数式装饰器可能更加精简一点,但类式装饰器会更加容易理解并因此更容易被修改和维护。

原文地址:Python Decorators II: Decorator Arguments

相关文章

  • [译] Python装饰器Part II:装饰器参数

    这是Python装饰器讲解的第二部分,上一篇:Python装饰器Part I:装饰器简介 回顾:不带参数的装饰器 ...

  • Python中的装饰器

    Python中的装饰器 不带参数的装饰器 带参数的装饰器 类装饰器 functools.wraps 使用装饰器极大...

  • 透析Python装饰器--透过现象看本质

    》眼花缭乱 Python的装饰器(也称语法糖)大致分为这几类: 无参数装饰器 有参数装饰器 装饰类的装饰器 无参数...

  • 透析Python装饰器-------------透过现象看本质

    》眼花缭乱 Python的装饰器(也称语法糖)大致分为这几类: 无参数装饰器 有参数装饰器 装饰类的装饰器 无参数...

  • [译] Python装饰器Part I:装饰器简介

    假以时日,我相信装饰器一定会成为Python这门编程语言一个更加强大的功能。到目前为止,我觉得到我所看到的有关介绍...

  • 装饰器

    """@装饰器- 普通装饰器- 带参数的装饰器- 通用装饰器- 装饰器装饰类- 内置装饰器- 缓存装饰器- 类实现...

  • typescript 五种装饰器

    装饰器类型 装饰器的类型有:类装饰器、访问器装饰器、属性装饰器、方法装饰器、参数装饰器,但是没有函数装饰器(fun...

  • TypeScript: 类的装饰器(三)

    带参数的类的装饰器 学习 python 的同学应该知道,python 中也有装饰器,而且 python 中的众多框...

  • python 装饰器

    python 装饰器 描述 python 中一切皆对象,函数也可以当作参数传递 装饰器就是接受一个函数作为参数,添...

  • python装饰器

    装饰器简述 要理解装饰器需要知道Python高阶函数和python闭包,Python高阶函数可以接受函数作为参数,...

网友评论

  • 头上有灰机:请问,当使用__call__来做装饰器时,在哪里并且如何使用functools.wraps?
  • df70772b5e50:为什么要将 不带参数 和 带参数 时 __call__ 的调用时机和行为不一致呢?
    1e97c730aedc:@安静的疯子 这就可以比作类的实例,这个装饰器先实例出这个类,然后因为这个对象里有__call__方法,所以@类装饰器(参数)会执行__init__和__call__。我是这么理解的。
    TypingQuietly:@安静的疯子 对比的话,“直观感受”上稍稍有点不一样。
  • 好吃的野菜:不觉明励

本文标题:[译] Python装饰器Part II:装饰器参数

本文链接:https://www.haomeiwen.com/subject/cpyyqttx.html