Python装饰器

Python装饰器

   一个生动的比喻是,被装饰函数是画作,而装饰器就是外面的画框。装饰器为被装饰函数提供额外的功能和行为;可以提取多个函数的共同部分作为装饰器,从而使代码更加清晰。

   学习装饰器要逐步解决的问题:

  1. 如何定义,使用装饰器?装饰器的执行顺序如何?
  2. 装饰器的接口是怎么样的?被装饰函数及其参数是如何传递的?
  3. 装饰器的高级用法:参数化装饰器和类装饰器

装饰器的基本知识

  理解装饰器要首先理解闭包。首先什么是闭包,所谓闭包,就是那些可以使用非全局变量、非自身的变量(称为自由变量)的函数,或者说延展了自身作用域的函数。只有涉及嵌套定义的函数,才会涉及到闭包。例如一个函数1内部定义了函数2,函数2可以使用函数1的变量,那么函数2和它所引用的自由变量就构成了一个闭包。典型例子:

# series为了保存历史值,这是很烂的写法
def make_averager():
    series = []
    def average(n):
        series.append(n)
        return sum(series) / len(series)

# 改进的写法
def make_averager():
    total = 0
    sum = 0
    def average(n):
        nonlocal total, sum
        total +=1
        sum += n
        return sum / total
           
View Code

  仔细一看,这跟装饰器很像。

  装饰器是闭包的应用。装饰器的参数是被修饰的函数,在执行完额外工作后,要返回被装饰函数或另一个函数。

  简单的装饰器的写法:

# 装饰器定义
def d1(func):
    print("decorator_1")
    return func

# 放置装饰器
@d1
def func(n)
    print(n)
View Code

  解释:上面的代码中,执行func()这个函数等价于 func =  d1( func),也就是说,执行代码时,func是被d1装饰的结果的引用,不是原始的func。f = func(100),print(f)和print(func)结果不一样。装饰器另外一个关键特性是:在加载模块时,他们在被装饰的函数定义之后立即执行,可见下面的代码:

def decorator_1(func):
    def miao(func):
        print("fck you, return func")
        return func
    print("decorator_1, return miao")
    return miao

def decorator_2(func):
    print("decorator_2, return func")
    return func

#叠放装饰器,结果等价于d1( d2( func_1))
@decorator_1
@decorator_2
def func_1(val):
    print(val)


if __name__ == "__main__":
    print("Main()")
    func_1(1000)
View Code

  执行结果为:

decorator_2, return func
decorator_1, return miao
Main()
fyou, return func

  这说明d2,d1两个装饰器在真正的被装饰函数func调用前,被执行。假如d2装饰了n个函数,那么d2要先执行n次。

  

参数化装饰器

  参数化装饰器一般是有几层嵌套,其中某一层及其以内是装饰器,外面的是包装着装饰器的工厂函数,工厂函数可以接受外界传给它的其他参数。

  看看例子:

# 《Fluent Python》page 165 example-7-15

import time 

def clock(func):

    def clocked(*args): # ??
        # extra ..
        result =  func(*args)
        return result

    return clocked
View Code

  那么这里就有问题了:

  Q1:*args是谁的参数?

  从上下文中可知*args应该是func接受的函数,但是在上文装饰器的定义中,没有显式看到*args。由此推测:装饰器中的*args, **kwargs这些容器分别存放了被修饰函数func的positional args 和 keyword args。

  Q2:如果改为decorator_1(func, *args, **kwargs),那么这两个容器放的是谁的参数?

  ans:经过尝试,发现会抛出异常。如果写:

  • @decorator_1(**dict1),                     decorator_1()missing a required positional arg 'func'.
  • @decorator_1(kwargs=dict1) ,                     decorator_1()missing a required positional arg 'func'.
  • @decorator_1(func=func_1, kwargs=dict1),   name 'func_1' is not defined.

  通过查看他人代码实例,发现每个装饰器及其内部的函数,都有一个函数只有func一个参数。在《Fluent Python》page 76 example-7-25的注释中,第二条注释写道:decorate(func)是真正的函数。第一条注释:clock(...)是装饰器工厂函数。由此可以推测,在Q2中,d1这个入口不是真正的装饰器,真正装饰器是miao(),参数只有一个func。

  下面的代码能够说明这一情况:

dict1 = {
        "name":"miao",
        "type":"zhizhi"
}

dict2 = {
    "name":"guagua",
}

def decorator_1(**kwargs):
    def miao(func):
        def zhi(**kwargs):
            for k,v in kwargs.items(): # 打印dict1
                print(k, v)
            return func
        return zhi
    for k,v in kwargs.items(): # 打印dict2
            print(k, v)
    print("decorator_1, return miao")
    return miao

@decorator_1(**dict2)
def func_1(**kwargs):
    print("f")

if __name__ == "__main__":
    func_1(**dict1) 

  d1接受的是dict2, func_1的参数是dict1, 运行结果是先打印dict2的内容,再打印dict1的内容。也就是说:miao(func)是真正的装饰器,其内部函数使用的**kwargs, *args都是被装饰函数的参数;作为工厂函数的d1,它接受的参数是外部直接传给的,区别于func的参数。参数化装饰器的另一个特点是,要写作 @decorator(),因为它可以接受参数。

 

装饰器的应用

  • 单分派,多分派函数,把多个函数捆绑组成一个泛函数。
  • 标准库的装饰器
    • functools.wraps
    • functools.lru_cache,参数化装饰器,做缓存工作,提高函数的速度,但被其修饰的函数所有参数必须可散列。
    • functools.singledispatch( Py 3.4+)

 

posted @ 2018-08-19 13:32  Tsu_jo  阅读(194)  评论(0编辑  收藏  举报