python装饰器

193 阅读4分钟

我理解的装饰器就是将原函数作为参数传进装饰器函数,返回一个功能更复杂的函数。

#装饰器函数
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
#原函数
def greet():
    print('hello world')

greet = my_decorator(greet)
greet()

# 输出
wrapper of decorator
hello world

这样写好像过于普通了,没必要非得起个装饰器的名字,所以python提供了装饰器语法糖

def my_decoretor(fun):
    def wrapper():
        print("wrapper of decorator")
        fun()
    return wrapper

@my_decoretor
def greet():
    print("Hello World")

greet()

带有参数的装饰器

def my_decoretor(fun):
    def wrapper(message):
        print("wrapper of decorator")
        fun(message)
    return wrapper

@my_decoretor
def greet(message):
    print(message)

# greet = my_decoretor(greet)

greet('Hello World!')

#输出
wrapper of decorator
Hello World!

问题:为什么参数message能传给wrapper函数。 回答:这里其实内部函数wrapper是一个闭包,返回了一个函数,并且这个函数调用了一个外部函数。

参数个数不确定的装饰器
def my_decoretor(fun):
    def wrapper(*args,**kwargs):
        print("wrapper of decorator")
        fun(*args,**kwargs)
    return wrapper

@my_decoretor
def greet(name,age):
    print('Hello,my name is {},and i am {}'.format(name,age))

# greet = my_decoretor(greet)

greet('zjl',17)

#输出
wrapper of decorator
Hello,my name is zjl,and i am 17

*args和**kwargs,表示接受任意数量和类型的参数

带有自定义参数的装饰器

装饰器可以接受原函数任意类型和数量的参数,除此之外,它还可以接受自己定义的参数。 例如,如果想传递一个控制内部函数执行次数的参数,可以这样做:

def my_decorate_with_params(num):
    def my_decoretor(fun):
        def wrapper(*args,**kwargs):
            for i in range(num):
                print("wrapper of decorator")
                fun(*args,**kwargs)
        return wrapper
    return my_decoretor

@my_decorate_with_params(3)
def greet(name,age):
    print('Hello,my name is {},and i am {}'.format(name,age))

# 注意:此处如果不适用@语法糖就得这样写
# greet = my_decorate_with_params(3)(greet)

greet('zjl',17)

#输出
wrapper of decorator
Hello,my name is zjl,and i am 17
wrapper of decorator
Hello,my name is zjl,and i am 17
wrapper of decorator
Hello,my name is zjl,and i am 17

通过上面这个例子可以看出,所谓的支持自定义参数的装饰器无非是再嵌套了一层函数用以接受自定义的参数。这样如果不适用语法糖的话,就得以greet = my_decorate_with_params(3)(greet)这种方式应用装饰器。

保留参数元信息

经过装饰器装饰的函数,如果打印出元信息,会发现其元信息被修改了,如果多个函数被同一个装饰器装饰,丧失了元信息,一旦报错就无法通过函数名定位具体是原来的哪个函数出错了,所以保留函数元信息是十分必要的。

def my_decoretor(fun):
    # @functools.wraps(fun)
    def wrapper(message):
        print("wrapper of decorator")
        fun(message)
    return wrapper

@my_decoretor
def greet(message):
    print(message)

# greet = my_decoretor(greet)

# greet('Hello World!')

print(greet.__name__)
#输出
wrapper

使用@functools.wraps(fun)保留元信息

import functools
def my_decoretor(fun):
    @functools.wraps(fun)
    def wrapper(message):
        print("wrapper of decorator")
        fun(message)
    return wrapper

@my_decoretor
def greet(message):
    print(message)

# greet = my_decoretor(greet)

# greet('Hello World!')

print(greet.__name__)

#输出
greet

类装饰器

类装饰器依赖于函数__call__,一个类如果定义了 __call__函数,其实例就会变成可调用对象(callable),且实例被调用后就会触发__call__函数。

请看下面这个例子:

class MyClass:
    def __init__(self):
        self.num_init = 0
    def __call__(self, *args, **kwargs):
        self.num_init = self.num_init + 1
        print('This class is inited {} timnes.'.format(self.num_init))

obj = MyClass()
print(callable(obj))
obj()
obj()

#输出
True
This class is inited 1 timnes.
This class is inited 2 timnes.

obj是类MyClass的一个实例,因为MyClass定义了__call__函数,obj称为了可调用(callable)对象。 那么,如果将原函数当做参数传进类,并且使原函数重新指向类的一个实例,这个实例也就变成了可调用对象,可以将其他操作在__call__函数中执行,最后执行原函数,这样也就实现了装饰器的作用。

类装饰器:

class MyClassDecorator:
    def __init__(self,fun):
        self.num_init = 0
        self.fun = fun
    def __call__(self, *args, **kwargs):
        self.num_init = self.num_init + 1
        print('This class is called {} timnes.'.format(self.num_init))
        return self.fun(*args, **kwargs)

def greet(message):
    print(message)
    
greet = MyClassDecorator(greet)
greet('HelloWorld')
greet('HelloPython')

#输出
This class is called 1 timnes.
HelloWorld
This class is called 2 timnes.
HelloPython

语法糖写法

class MyClassDecorator:
    def __init__(self,fun):
        self.num_init = 0
        self.fun = fun
    def __call__(self, *args, **kwargs):
        self.num_init = self.num_init + 1
        print('This class is called {} timnes.'.format(self.num_init))
        return self.fun(*args, **kwargs)

@MyClassDecorator
def greet(message):
    print(message)

# greet = MyClassDecorator(greet)

greet('HelloWorld')
greet('HelloPython')

#输出
This class is called 1 timnes.
HelloWorld
This class is called 2 timnes.
HelloPython

装饰器的嵌套

执行顺序从里到外

class MyClassDecorator2:
    def __init__(self,fun):
        self.num_init = 0
        self.fun = fun
    def __call__(self, *args, **kwargs):
        self.num_init = self.num_init + 1
        print('This MyClassDecorator2 is called {} times.'.format(self.num_init))
        return self.fun(*args, **kwargs)

class MyClassDecorator:
    def __init__(self,fun):
        self.num_init = 0
        self.fun = fun
    def __call__(self, *args, **kwargs):
        self.num_init = self.num_init + 1
        print('This MyClassDecorator is called {} times.'.format(self.num_init))
        return self.fun(*args, **kwargs)

@MyClassDecorator2
@MyClassDecorator
def greet(message):
    print(message)

# greet = MyClassDecorator(greet)

greet('HelloWorld')
greet('HelloPython')

#输出
This MyClassDecorator2 is called 1 times.
This MyClassDecorator is called 1 times.
HelloWorld
This MyClassDecorator2 is called 2 times.
This MyClassDecorator is called 2 times.
HelloPython