Python基础 - 装饰器02

装饰器02

Posted by 王富杰 on Monday, March 24, 2025

一、装饰器模板

通过上一篇文章,我们已经知道了如何给函数写装饰器,这里总结一下装饰器的模板:

def outer(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

这就是装饰器实现的模板,这个模板并没有增加功能,需要增加功能根据这个模板添加即可。pycharm提供了实时模板功能,例如在pycharm键入main就会自动弹出if name == ‘main’: 这就是实时模板功能。你也可以自定义装饰器模板到实时模板中去。

二、完美伪装

我们先看一下上篇文章中的示例,它伪装的还不够完美。

import time
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()
        print(f'函数运行时间{end - start}秒')
        return response

    return wrapper

@timer
def inside(n):
    sum = 0
    for i in range(n + 1):
        sum += i
    return sum


print(inside)

## 执行结果
<function timer.<locals>.wrapper at 0x7fb163ef3290>
## 而原始函数打印结果是
<function inside at 0x7fbda66e4d40>

这是因为函数有内置属性例如 __name__指定当前函数名,__doc__执行当前函数注释。封装后的wrapper函数的名字是wrapper。因此我们需要这是wrapper的内置属性等于原函数的内置属性。

import time
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()
        print(f'函数运行时间{end - start}秒')
        return response
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper

@timer
def inside(n):
    sum = 0
    for i in range(n + 1):
        sum += i
    return sum

print(inside)

但是这样做仍然不够,因为函数的内置属性太多了,如果一个一个都这么些太繁琐,Python也考虑到了这个问题,提供了功能来实现这个操作。

from functools import wraps
import time

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()
        print(f'函数运行时间{end - start}秒')
        return response
    return wrapper


@timer
def inside(n):
    sum = 0
    for i in range(n + 1):
        sum += i
    return sum

print(inside)

## 执行结果
<function inside at 0x7fde70ef3290>

这里我们使用了functools模块提供的wraps功能实现了完美伪装,wraps也是一个装饰器,它对wrapper函数进行了装饰,它实现的功能就是把原函数的属性都复制给wrapper函数,当然我们也可以自己再写一个装饰器实现这些功能,只是没有必要而已。

三、有参装饰器

在函数内部需要参数时,第一种方式是直接给函数传参,第二种方式是闭包函数。在装饰器中,如果wrapper函数需要参数怎么办,肯定不能通过第一种方式。我们先看下模板:

def outer(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

如果wrapper需要一个参数name,不能直接给wrapper传参,这是因为wrapper的参数是原封不动传给原函数的。也不能给outer传参,因为受限于语法糖,@outer相当于 func = outer(func),语法糖只能接受一个参数,那就是只能传函数名,不能传多余的参数了。所以我们只能采用闭包函数的方式,在outer函数外再包一层。

from functools import wraps

def g_outer(name):
    def outer(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(name)
            res = func(*args, **kwargs)
            return res

        return wrapper
    return outer

@g_outer('wfj')  # 他就相当于@outer,因为g_outer返回了outer的内存地址
def home():
    print('hello world')

print(home)

这里我们实现了参数装饰器。注意@g_outer(‘wfj’) 中g_outer(‘wfj’)相当于函数调用,这个函数返回了outer的内存地址,所以相当于@outer。

四、装饰器叠加

装饰器时可以叠加使用的,如下:

@outer3  # home = outer3(home)     ouer3.wrapper
@outer2  # home = outer2(home)     ouer2.wrapper
@outer1  # home = outer1(home)     ouer1.wrapper
def home():
    pass

叠加装饰器的执行顺序时,先执行原函数头顶上的,也就是先执行outer1。看一个示例:

def outer1(func):
    def wrapper(*args, **kwargs):
        print('开始执行装饰器outer1')
        res = func(*args, **kwargs)
        print('结束执行装饰器outer1')
        return res
    return wrapper

def outer2(x):
    def outer(func):
        def wrapper(*args, **kwargs):
            print('开始执行装饰器outer2')
            res = func(*args, **kwargs)
            print('结束执行装饰器outer2')
            return res
        return wrapper
    return outer

def outer3(func):
    def wrapper(*args, **kwargs):
        print('开始执行装饰器outer3')
        res = func(*args, **kwargs)
        print('结束执行装饰器outer3')
        return res
    return wrapper


@outer3
@outer2(10)
@outer1
def home():
    print('执行函数home')

home()

### 执行结果:
开始执行装饰器outer3
开始执行装饰器outer2
开始执行装饰器outer1
执行函数home
结束执行装饰器outer1
结束执行装饰器outer2
结束执行装饰器outer3

「真诚赞赏,手留余香」

WangFuJie Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付