用函数与类实现Python的函数装饰器与类装饰器


一、引言

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

装饰器的概念

装饰器是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic
装饰器实际上也是一个函数 ,这个函数以闭包的形式定义,而我们知道在Python中万物皆对象,即函数也是一个对象,而且函数对象可以被赋值给变量,所以将原函数作为变量传递给装饰器函数,就能够给原函数添加装饰器中的功能

这里举一个最简单的装饰器例子:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("decorator sentence")
        func(*args, **kwargs)
    return wrapper

@decorator
def hello():
    print("hello world")

hello()

>>>decorator sentence
>>>hello world

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


二、用装饰器装饰函数

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)

这个例子中wrapper(*args, **kwargs)接收任意参数,而且将函数test_func执行结果return,实现的是有参有返的装饰器(这里的有参有返指的是原函数有参有返)。
虽然代码最后并没有用到原test_func的返回结果,写成有参有返只是想展现一种比较全能的装饰器格式。

结果(后面结果相同):

Function sleep 2 second
Function run time is:  2.0045318603515625

2、带参数的函数装饰器

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

import time

def calculate_time(time_val):  # time_val为装饰器接收的参数
    def decorator(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(time_val=time_val, *args, **kwargs)  # 将装饰器接收的参数作为原函数的参数
            end = time.time()
            print("Function run time is: ", end - start)
            return result
        return wrapper
    return decorator

@calculate_time(2)
def test_func(time_val):
    time.sleep(time_val)
    print("Function sleep %s second" % time_val)
    return "end"

if __name__ == '__main__':
    test_func()

在这个案例中将装饰器的参数传给了原函数,因此调用函数时不需要传值。实际开发中可以根据需要使用在不同的地方
进一步的,带参数的装饰器相当于执行了hello = calculate_time(2)(test_func)
首先执行calculate_time(2),返回的是decorator函数,再调用返回的decorator函数,参数是test_func函数,返回值最终跟无参数的装饰器,是wrapper函数


3、类装饰器

call 方法(仿函数/函数对象)

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

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

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

s = Student('Michael')
s() # self参数不要传入

>>>My name is Michael.
使用类的__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)

可以看到上面的例子在类的初始化方法__init__中将原函数传递进去,在__call__方法中调用原函数,并将装饰器需要实现的功能写在方法内即可
执行test_func = CalculateTime(test_func),相当于执行了CalculateTime装饰类的对象


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 decorator(cls):
    def wrapper(*args, **kwargs):
        print('class name:', cls.__name__)
        return cls(*args, **kwargs)
    return wrapper

@decorator
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 decorator_desc(description):
    def decorator(cls):
        def wrapper(*args, **kwargs):
            print(description, cls.__name__)
            return cls(*args, **kwargs)
        return wrapper
    return decorator

@decorator_desc('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()

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()

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()


参考:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584
https://blog.csdn.net/ITlanyue/article/details/82147720
https://blog.csdn.net/xiemanr/article/details/72510885


版权声明:本文为Bryce_Liu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。