初探python装饰器

前言

本文用于记录总结自己在python装饰器学习过程中的理解,如有疑问欢迎讨论。

可变长参数

def wrapper(*args, **kwargs):
    print('before test1 ...')
    func(*args, **kwargs)
    print('after test1 ...')
return wrapper  

wrapper(a, b, c, k1 = 'v1',k2 = 'v2')

简单理解 :

  • * args 在函数中视为元组中的元素;
  • ** kwargs 视为字典中元素,其中k为字典中的key,v为字典中的value。

详见如下:
可变长参数

闭包

闭包指的是:延伸了作用域的函数。如内部函数对外部作用域的变量进行引用,则该内部函数即被认为是闭包。

  • 闭包的作用在于保存了当前的运行状态。
  • 装饰器中所返回的是闭包函数。

装饰器是什么

python装饰器可以看作是对原函数(类)的增强,比如函数运行前后添加内容,但不会改变原有函数内部的内容。
装饰器其实是对闭包的一种应用,结合可变长参数可以适配各种函数(类)。

装饰器的四种分类

函数装饰函数

以下例子来自python装饰器

不带参数装饰器

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)  # 此处拿到了被装饰的函数func
        time.sleep(2)  # 模拟耗时操作
        long = time.time() - start
        print('共耗时{}秒'.format(long))
    return wrapper  # 返回内层函数的引用

@timer
def add(a, b):
    print(a + b)

if __name__ == '__main__':
    add(1, 2)  # 正常调用add

输出:
3
共耗时2.01200008392秒

装饰器的执行流程:

  1. 模块加载时,会执行@后面的函数(上述例子中执行timer函数,其中的func代表add函数)
  2. 执行timer函数后返回内部wrapper函数。其中func为原add函数
  3. 调用add函数,实际是调用了wrapper函数,先执行start = time.time(),再调用add函数,再执行之后的操作

可以理解为,当在func上增加装饰器后。在模块执行后,当前func指向了装饰器内返回的warpperr函数。当我们调用func时,其实是在调用warpper函数。

带参数装饰器

def auth(permission):
    def _auth(func):
        def wrapper(*args, **kwargs):
            print("验证权限[{}]...".format(permission))
            func(*args, **kwargs)
            print("执行完毕...")
        return wrapper
    return _auth

@auth("add")
def add(a, b):
    """
    求和运算
    """
    print(a + b)

add(1, 2)  # 正常调用add

输出:
验证权限[add]...
3
执行完毕...

与不带参数的装饰器类似,只是在执行装饰器的时候可以通过传参来按需配置某些属性
唯一注意的是该装饰器存在3层结构

执行流程:

  1. 模块加载时,会执行@后面的函数(上述例子中执行auth函数,其中的permission代表auth函数的参数)
  2. 执行auth函数后返回内部_auth函数。其中func为原add函数
  3. 执行_auth函数,返回内部的wrapper函数
  4. 调用add函数,实际是调用了wrapper函数,先执行输出,再调用add函数,再执行之后的操作

多装饰器的执行顺序

def test1(func):
    def wrapper(*args, **kwargs):
        print('before test1 ...')
        func(*args, **kwargs)
        print('after test1 ...')
    return wrapper #返回内层函数的引用

def test2(func):
    def wrapper(*args, **kwargs):
        print('before test2 ...')
        func(*args, **kwargs)
        print('after test2 ...')
    return wrapper #返回内层函数的引用

@test2
@test1
def add(a, b):
    print(a+b)

add(1, 2) #正常调用add

输出:
before test2 ...
before test1 ...
3
after test1 ...
after test2 ...

装饰器是可以嵌套调用的。当有多个装饰器作用于同一个函数的时候,会从最近的装饰器开始执行。比如上面例子中先执行@test1再执行@test2

函数装饰类

例子来自:python装饰器4种类型

def wrapClass(cls):
    def inner(a):
        print('class name:', cls.__name__)
        return cls(a)
    return inner

@wrapClass
class Foo():
    def __init__(self, a):
        self.a = a

    def fun(self):
        print('self.a =', self.a)


m = Foo('xiemanR')
m.fun()

输出:
('class name:', 'Foo')
('self.a =', 'xiemanR')

执行顺序:

  1. 模块加载时,执行warpClass函数,其中cls为Foo该类,返回inner内部函数
  2. 执行Foo('xiemanR')时,实际调用inner函数,打印class name
  3. 调用Foo(a) 进行类初始化一个实例并返回给引用m
  4. m.fun调用Foo的fun函数

利用函数装饰类,可以对类初始化之前做一些额外的操作。上述例子只是在类进行初始化前打印类的名称。但在实际当中,亦可以通过反射向类中动态增加属性,或者将该类放入自定义容器进行管理

类装饰函数

class ShowFunName:
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        print('function name:', func.__name__)
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running...".format(level=self.level, func=func.__name__))
            return func(*args, **kwargs)
        return wrapper

@ShowFunName(level='info')
def Bar(a):
    return a

if __name__ == '__main__':
    print(Bar('xiemanR'))


结果:
('function name:', 'Bar')
[info]: the function Bar() is running...
xiemanR

执行流程:

  1. 模块加载时,初始化ShowFunName类实例,执行init方法。其中level为入参
  2. 执行实例的__call__函数,返回wrapper函数,将Bar指向wrapper
  3. 调用Bar函数,实际调用的是wrapper函数,wrapper函数内进行日志输出,并且执行原来的Bar函数并返回

注意:
以类作为装饰器,必须实现initcall方法
__init__: 接收传入参数
__call__: 接受被装饰者(函数或类)

类装饰类

class ShowClassName(object):
    def __init__(self, level):
        self.level = level

    def __call__(self, cls):
        print('class name:', cls.__name__)

        def wrapper(value):
            print("[{level}]: the cls {cls}() is ready return ...".format(level=self.level, cls=cls.__name__))
            return cls(value)

        return wrapper


@ShowClassName(level='info')
class Foobar(object):
    def __init__(self, a):
        self.value = a
        print 'init', self.value

    def fun(self):
        print('fun')

if __name__ == '__main__':
    a = Foobar('xiemanR')
    a.fun()

结果:
('class name:', 'Foobar')
[info]: the cls Foobar() is ready return ...
init xiemanR
fun

执行流程与类装饰函数类似,但需要注意的是wrapper方法的参数个数要跟Foobar__init__中的一致,因为执行Foobar初始化实际调用的是wrapper方法,在wrapper内部才真正初始化Foobar

理解

  • 装饰器跟静态代理类似,可以为原有函数增加一些附属操作而不改变函数本身,但很大一点不同的是装饰器不会修改函数(类)的调用方式。
  • 装饰器的执行是在模块加载的时候进行的,此时返回的是内部的闭包函数但不会真正执行,只有真正进行函数调用的时候才执行闭包函数
  • 装载了装饰器后的函数(类)经过加载后,该引用指向的是闭包函数