Python基础:装饰器

Published on 2025-04-16 13:38 in 分类: 博客 with 狂盗一枝梅
分类: 博客

装饰器是Python中最强大且优雅的特性之一,它允许你在不修改函数或类源代码的情况下,动态地扩展它们的功能。装饰器本质上是一个接受函数作为参数返回新函数的高阶函数。对应到java,其形式和注解切面非常相似。

一、装饰器的基本使用

下面用一个例子解释装饰器

def log(fun):
    def wrapper(*args, **kwargs):
        print("方法调用前")
        result = fun(*args, **kwargs)
        print("方法调用后")
        return result

    return wrapper


@log
def hello():
    print("Hello,World")


if __name__ == '__main__':
    hello()

运行结果:

方法调用前
Hello,World
方法调用后

上面的代码执行了hello方法,hello方法的@log注解实际上相当于:

hello = log(hello)
hello()

log方法返回的方法实际上已经变成了wrapper方法,而wrapper方法接收的参数是*args, **kwargs,所以它可以接受任意参数。

来看下它的等价代码:

def log(fun):
    def wrapper(*args, **kwargs):
        print("方法调用前")
        result = fun(*args, **kwargs)
        print("方法调用后")
        return result

    return wrapper


def hello():
    print("Hello,World")


if __name__ == '__main__':
    hello = log(hello)
    hello()

运行结果和装饰器的运行结果是一样的。

二、装饰器传参

如果装饰器方法本身需要一些参数控制装饰器的行为,该如何传参呢?比如以上的log装饰器,我需要传一个参数level,形式如下:

@log(level="INFO")
def hello():
    print("Hello,World")

打印日志的时候需要将该字符串放到日志前面,如何实现?

这里需要用到三层嵌套,修改后的代码如下

def log(level):
    def decorator(fun):
        def wrapper(*args, **kwargs):
            print(f"{level}:方法调用前")
            result = fun(*args, **kwargs)
            print(f"{level}:方法调用后")
            return result

        return wrapper
    return decorator


@log(level="INFO")
def hello():
    print("Hello,World")


if __name__ == '__main__':
    hello()

运行结果:

INFO:方法调用前
Hello,World
INFO:方法调用后

此时的hello()方法调用等价于以下代码:

hello = log(level="INFO")(hello)
hello()

完整的等价代码如下所示:

def log(level):
    def decorator(fun):
        def wrapper(*args, **kwargs):
            print(f"{level}:方法调用前")
            result = fun(*args, **kwargs)
            print(f"{level}:方法调用后")
            return result

        return wrapper

    return decorator


# @log(level="INFO")
def hello():
    print("Hello,World")


if __name__ == '__main__':
    hello = log(level="INFO")(hello)
    hello()

三、函数签名变更问题

装饰器传参的问题也解决了,接下来看看装饰器引发的函数签名变化问题,运行以下代码,hello方法的__name__属性还是hello吗?

def log(level):
    def decorator(fun):
        def wrapper(*args, **kwargs):
            print(f"{level}:方法调用前")
            result = fun(*args, **kwargs)
            print(f"{level}:方法调用后")
            return result

        return wrapper

    return decorator


@log(level="INFO")
def hello():
    print("Hello,World")


if __name__ == '__main__':
    print(hello.__name__)

输出:

wrapper

没错,hello的方法名已经变成了wrapper,这对于某些依赖函数签名的代码执行就会出错。

首先想到的解决方案就是复制hello方法的__name__到wrapper方法。。实际上使用内置的functools模块可以解决这个问题,只需要在wrapper方法上使用@functools.wraps(log)标记一下即可,来看下它的使用:

import functools


def log(level):
    def decorator(fun):
        @functools.wraps(log)
        def wrapper(*args, **kwargs):
            print(f"{level}:方法调用前")
            result = fun(*args, **kwargs)
            print(f"{level}:方法调用后")
            return result

        return wrapper

    return decorator


@log(level="INFO")
def hello():
    print("Hello,World")


if __name__ == '__main__':
    print(hello.__name__)

输出:

log


参考文档:https://liaoxuefeng.com/books/python/functional/decorator/index.html

END.


#python
复制 复制成功