美文网首页
Python 装饰器语法详解

Python 装饰器语法详解

作者: ButteredCat | 来源:发表于2017-08-22 20:12 被阅读0次

    Python 装饰器(decorator)是一种语法糖(Syntactic sugar),

    @d
    def foo():
        pass
    

    以上代码和以下代码是等价的:

    foo = d(foo)
    

    进一步说,

    @d(param)
    def foo():
        pass
    

    和下面代码是等价的

    foo = (d(param))(foo)
    

    他们的实质都是传入一个函数对象,进行“装饰”后返回装饰好的另一个函数对象。

    返回函数对象的函数

    Python 中的函数是一个对象(object),它可以像任何其他对象一样被创建、引用、传递和销毁。引用函数对象的方法是使用它的函数名,如 foo 引用的是一个函数对象,而 foo() 则是其返回值了。

    得益于函数在 Python 中一等公民的地位,你可以在一个函数中返回另一个函数,接受函数作为参数或返回函数的函数,称为高阶函数(higher-order function)。

    看下面的例子:

    def f(a):
        def g(b):
            def h(c):
                print(a, b, c)
            return h
        return g
    
    In [2]: f(1)(2)(3)
    1 2 3
    
    In [3]: g = f(4)
    
    In [4]: g(5)(6)
    4 5 6
    

    这是在函数式编程中被称为柯里化(currying)的东西,被柯里化后的高阶函数 f 其实是这样执行的:

    ((f(1))(2))(3)
    

    首先执行的 f(1),返回其内部定义的函数对象 g,接着执行 g(2),返回了 g 内部定义的对象 h,以此类推。

    不带参数的装饰器函数

    最简单的情形就是不带参数的装饰器函数。
    给函数 foo 使用装饰器 d,等价于

    foo = d(foo)
    

    为了定义装饰器 d,必须使其接受一个函数对象参数,并返回一个函数对象,而要返回一个函数对象,就需要在 d 内部新定义一个函数对象。这个内部的函数对象有什么特点呢?它接受 foo 的参数,运行 foo 的代码,并返回 foo 的返回值,当然还做了装饰器需要做的额外工作。

    按照以上需求,装饰器 d 的代码就可以写出了。

    def d(f):
        def wrapper(*args, **kwargs):
            # do something
            res = f(*args, **kwargs)
            # do something
            return res
        
        return wrapper
    

    d(foo) 返回了函数对象 wrapper,而变量 foo 指向了它。foo 不再是原来的那个 foo,装饰器赋予了它额外的功能。

    如果需要的话,d 其实是可以这样被调用的:

    In [35]: def foo(n):
        ...:     return n + 1
        ...:
    
    In [36]: d(foo)(2)
    Out[36]: 3
    

    是不是就是上面柯里化的形式?

    另一种装饰器就更容易理解了:

    def d(f):
        f.__some_attr__ = 'attr'
        return f
    

    根本就没有重新定义一个函数对象,改了 f 的某个属性就把它扔出来了。

    带参数的装饰器函数

    带参数的装饰器函数这样使用:

    @d(param)
    def foo(n):
        return n + 1
    

    它等价于

    foo = d(param)(foo)
    

    为此,首先需要定义一个接受装饰器参数 param 的函数,接着再定义接受 foo 为参数的函数,最后是接受函数 foo 本身参数为参数的函数,除了最内层函数返回 foo() 运行结果外,其他函数都返回一个函数对象。

    def d(param):
        def outter(f):
            def inner(*args, **kwargs):
                # do something
                res = f(*args, **kwargs)
                # do something
                return res
            return inner
        return outter
    

    你当然也可以这样调用 d:

    d(param)(foo)(3)
    

    callable

    如果一个类的实例 instance 具有 __call__ 方法,那么这个实例就是一个 callable,instance() 就相当于 instance.__call__()。

    不带参数的装饰器类

    仍然沿用以上对装饰器语法糖的解释,对函数 foo 使用装饰器类 D,等价于

    foo = D(foo)
    

    foo 对象应该在 D.__init__() 中被传入,得到一个 callable,而 foo 在 D.__call__() 中被执行。所以一个不带参数的装饰器类应该写成这样:

    class D(object):
        def __init__(self, f):
            self.f = f
            
        def __call__(self, *args, **kwargs):
            # do something
            res = self.f(*args, **kwargs)
            # do something
            return res
    

    带参数的装饰器类

    使用带参数的装饰器类,等价于

    foo = D(params)(foo)
    

    可以知道,装饰器的参数 params 在 D.__init__() 中被传入,而 D.__call__() 接受函数对象 foo, 返回装饰后的函数对象。因而装饰器类是这样的:

    class D(object):
        def __init__(self, param):
            self.param = param
            
        def __call__(self, f):
            def wrapper(*args, **kwargs):
                # do something
                res = f(*args, **kwargs)
                # do something
                return res
            
            return wrapper
    

    总结

    本文提供了将装饰器转化成高阶函数表达的一种范式,根据这一范式,可以直接推导出四种装饰器(带参/不带参、函数/类)的写法。

    相关文章

      网友评论

          本文标题:Python 装饰器语法详解

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