一、 引言

装饰器是每一个使用Python的人都会接触到的一种增强函数功能的方式,也是面试官经常会问到的知识点,这里通过一个函数运行时间的装饰器,举例说明常见的四种装饰器实现方法

1 装饰器的定义**

在python中装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数。

例子:现在有一个需求,需要在执行该函数时加上日志
先定义一个函数:
def eat():
print(“开始吃饭了”)
需求:在执行eat()时加上函数执行日志。

1-1 一般方法:

print(“……….开始调用eat()函数……..”)
eat()
print(“……….结束调用eat()函数……..”)

输出:
开始调用eat()函数
开始吃饭了
结束调用eat()函数
这个问题虽然实现了,但是它需求侵入到原来的代码里面,使得原来的业务逻辑变得复杂,这样的代码也不符合“一个函数只做一件事情”的原则

1-2 闭包方法:

def log(func):
def wrapper():
print(“开始调用eat函数”)
func()
print(“结束调用eat函数”)
return wrapper
def eat():
print(“开始吃饭了”)
eat=log(eat)
eat()

输出:
开始调用eat()函数
开始吃饭了
结束调用eat()函数
log(eat)将eat函数作为参数传递给log(),由于wrapper()是log()闭包,所以它可以访问log()
log()的局部变量func, 也就是刚刚传递进来的eat,因此,执行func()与执行eat()是一个效果。
这样就没有修改eat()函数里面的逻辑结构,也没有给主程序带来太多干扰。但总是感觉这个eat()函数有点别扭。
因此python发明了“@语法糖”

1-3 语法糖:

所谓语法糖就是在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更加方面程序员,让程序更加简洁,有更高的可读性。

def log(func):
def wrapper():
print(“开始调用eat函数”)
func()
print(“结束调用eat函数”)
return wrapper
@log
def eat():
print(“开始吃饭了”)
eat()

输出:
开始调用eat()函数
开始吃饭了
结束调用eat()函数
这样就省去了手动将eat()函数传递给log()再将返回值重新赋值的步骤。但是下一个问题如果eat()函数有参数怎么办。

1-4 带参数的语法糖:

带参数的语法糖,可以将参数内部的wrapper()参数
def log(func):
def wrapper(name):
print(“开始调用eat函数”)
func(name)
print(“结束调用eat函数”)
return wrapper
@log
def eat(name):
print(“%s开始吃饭了” % name)
eat(“小甲鱼”)

输出:
开始调用eat()函数
小甲鱼开始吃饭了
结束调用eat()函数
这个方法必须关注eat()函数的参数数量

但是该装饰器依然存在很大问题,如果修改了eat()函数,就必须要修改装饰器log() ,不仅不方面而且也容易出错,因此可以在设计装饰器时进行相关改动。

1-5 带参数的语法糖完美版:

在定义装饰器时可以使用手机参数,将多个参数打包到一个元祖中,在调用时只要使用(*)对其进行解包即可,因此无论eat()函数里无论传入多少参数,都无伤大雅了。

def log(func):
def decorator(param,*params):
print(“开始调用eat函数”)
func(param,*params)
print(“结束调用eat函数”)
return wrapper
@log
def eat(age,name):
print(“%s岁的%s开始吃饭了” % (age,name))
eat(10,”小甲鱼”)

输出:
开始调用eat函数
10岁的小甲鱼开始吃饭了
结束调用eat函数

decorator方法接收一个函数作为参数,使用@decorator装饰eat函数之后,
相当于执行了eat= decorator(eat),而decorator方法return wrapper,
因此被装饰后执行的eat()相当于执行了wrapper()这里decorator(param, *params)
接收所有的参数,并把参数传递给func形参对应的函数(也就是eat函数)执行.

1-6 装饰器的高级玩法-多层装饰器:

@buffer
@performance
@log
def eat(name):
print(“%s开始吃饭了” %name)

在调用时相当于调用了buffer(performance(log(eat)))

二 、使用装饰器装饰函数

2-1 函数装饰器

在面试时大多数面试官都会让你写一个简单的装饰器,一般会让你写一个时间的装饰器。
下面就一个时间的装饰器进行举例说明

import time
def calculate_time(func):
def wrapper(args, *kwargs):
start = time.time()
result = func(args, *kwargs)
end = time.time()
print(“Function run time is: “, end - start)
return result
return wrapper

@calculate_time
def test_func(time_val):
time.sleep(time_val)
print(“Function sleep %s second” % time_val)
return “end”
if name == ‘main‘:
test_func(2)

整个程序的运行过程,
第一 : @calculate_time等价于test_func=calculate_time(test_func(2))
第二 : func=test_func(2),进入wrapper函数中,之后进入func函数中,即执行test_func()
第三 : 输出:Function sleep 2 second
第四 : 输出:Function run time is: 2.0002989768981934
注释:wrapper(args, *kwargs)接收任意参数。args指的是收集参数(包括整型,列表,字符串,元祖),*kwargs是字典

2-2 含有参数的函数装饰器

在实际开发中,我们的装饰器往往需要接收参数,根据传入装饰器的参数不同实现不同功能,
那就需要编写一个返回decorator的高阶函数,写出来会更复杂

import time
def cacular(time_val):
def decorot(func):
def wrapper(param,*params):
star=time.time()
result=func(time_val=time_val,param,*params)
end=time.time()
print(“Function run time is: “, end-star )
return result
return wrapper
return decorot
@cacular(2)
def test_time(time_val):
time.sleep(time_val)
print(“Function sleep %s second” %time_val)
if name == “main“:
test_time()

在这个案例中将装饰器的参数传给了原函数,因此调用函数时不需要传值。
带参数的装饰器相当于执行了test_func = calculate_time(2)(test_func)
首先执行calculate_time(2),返回的是decorator函数,再调用返回的decorator函数,参数是test_func函数,返回值最终跟无参数的装饰器,是wrapper函数

2-3 类装饰器
2-3-1 call 方法(仿函数/函数对象)

当实例以函数的格式使用时, 调用的是call方法内的函数
实现call后,可以将对象当做函数一样去使用,称为仿函数或函数对象,例如

class Student(object):
def init(self, name):
self.name = name

def __call__(self):
    print('My name is %s.' % self.name)

test = Student(‘Michael’)
test()

2-3-2 使用类的call方法实现装饰器

import time

class CalculateTime(object):
def init(self, func):
self.__func = func

def __call__(self, *args, **kwargs):
    start = time.time()
    result = self.__func(*args, **kwargs)
    end = time.time()
    print("Function run time is: ", end - start)
    return result

@CalculateTime
def test_func(time_val):
time.sleep(time_val)
print(“Function sleep %s second” % time_val)
return “end”

if name == ‘main‘:
test_func(2)

如上所示,@CalculateTime等价于test_func=CalculateTime(test_func),
并调用init函数,进行初始化。之后调用call()函数,返回result,
等价于test_func(),即执行test_func()函数

2-4 带参数的类装饰器

使用类实现带参数的装饰器的思想与函数实现装饰器一致,同样是将装饰器的深度加深一层,这里是在call方法将原函数再包一层
需要注意,类的init方法接收装饰器需要的参数进行初始化,而不再接收原函数作为参数
原函数作为call的形参传入,结果返回wrapper函数

import time

class CalculateTime(object):
def init(self, time_val):
self.__time_val = time_val

def __call__(self, func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(time_val=self.__time_val, *args, **kwargs)
        end = time.time()
        print("Function run time is: ", end - start)
        return result
    return wrapper

@CalculateTime(2)
def test_func(time_val):
time.sleep(time_val)
print(“Function sleep %s second” % time_val)
return “end”

if name == ‘main‘:
test_func()

在上面的例子中,test_func = CalculateTime(2)(test_func),
其中先执行CalculateTime(2)进行类的初始化,之后相当于执行call(test_func),最终返回wrapper函数

三、使用装饰器装饰类

1、函数装饰器

def DecoratorClass(cls):
def wrapper(args, *kwargs):
print(‘class name:’, cls.name)
return cls(args, *kwargs)
return wrapper

@DecoratorClass
class Example(object):
def init(self, name):
self.name = name

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

e = Example(‘Bryce’)
e.fun()

输出:
class name: Example
self.name = Bryce

2、带参数的函数装饰器

def DecoratorClass(description):
def decorator(cls):
def wrapper(args, *kwargs):
print(description, cls.name)
return cls(args, *kwargs)
return wrapper
return decorator

@DecoratorClass(‘class name:’)
class Example(object):
def init(self, name):
self.name = name

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

e = Example(‘Bryce’)
e.fun()

输出:
class name: Example
self.name = Bryce

3、类装饰器

class DecoratorClass(object):
def init(self, cls):
self._cls = cls

def __call__(self, *args, **kwargs):
    print('class name:', self._cls.__name__)
    return self._cls(*args, **kwargs)

@DecoratorClass
class Example(object):
def init(self, name):
self.name = name

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

e = Example(‘Bryce’)
e.fun()

输出:
class name: Example
self.name = Bryce

4、带参数的类装饰器

class DecoratorClass(object):
def init(self, description):
self.__description = description

def __call__(self, cls):
    def wrapper(*args, **kwargs):
        print(self.__description, cls.__name__)
        return cls(*args, **kwargs)
    return wrapper

@DecoratorClass(‘class name:’)
class Example(object):
def init(self, name):
self.name = name

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

e = Example(‘Bryce’)
e.fun()

输出:
class name: Example
self.name = Bryce

评 论