python装饰器以及functools的wraps使用

本文介绍了Python闭包函数和装饰器的概念及用法,包括不带参和带参装饰器。指出使用装饰器修饰函数后,查看函数帮助文档和名称会出现问题,而functools模块的wraps装饰器可解决此问题,让输出符合预期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、闭包函数
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包

import sys
debug_log = sys.stderr
def trace(func):
    if debug_log:
        def callf(*args, **kwargs):
            """A wrapper function."""
            debug_log.write('Calling function: {}\n'.format(func.__name__))
            res = func(*args, **kwargs)
            debug_log.write('Return value: {}\n'.format(res))
            return res
        return callf
    else:
        return func

注:

1、如果想修改外部变量的值,可以是使用`nonlocal`进行声明
2、由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

二、何谓“装饰器”?
“装饰器是一个函数,其主要用途是包装另一个函数或类。这种包装的首要目的是透明地修改或增强被包装对象的行为。” --《A Byte of Python》
在上面定义好的闭包函数使用@函数语法糖,便可将trace声明为装饰器函数:

@trace
def square(x):
    """Calculate the square of the given number."""
    return x * x
if __name__ == '__main__':
    print(square(9))

以上函数完全等价于:@trace 等价于 trace(square)。如果不用装饰器语法,我们也可以这样写:

def _square(x):
    return x * x
square = trace(_square)

在闭包函数中func(*args, **kwargs)之前,我们可以加入任何的我们想要加入的新增,从而对func本身逻辑并没有影响。
嗯…以上是便是不带参的装饰器的基本的用法了,下面再简单说明一下带参装饰器:

def trace(log_level):
    def impl_f(func):
        print(log_level, 'Implementing function: "{}"'.format(func.__name__))
        return func
    return impl_f

@trace('[INFO]')
def print_msg(msg):
	 print(msg)
	 
[INFO] Implementing function: "print_msg"
>>> @trace('[DEBUG]')
def assert_(expr): assert expr

[DEBUG] Implementing function: "assert_"
>>> print_msg('Hello, world!')
Hello, world!

带参数的trace装饰器函数等价于:trace("[INFO]")(print_msg)

嗯…装饰器拥有如此强大的功能,是否感到一点兴奋!假设我们把如上代码提供给其他程序员使用,他可能会想看一下square函数的帮助文档:

>>> from decorator_wraps_test import square
>>> help(square) # print(square.__doc__)
Help on function callf in module decorator_wraps_test:
callf(*args, **kwargs)
    A wrapper function

看到这样的结果,使用decorator_wraps_test.py模块的程序员一定会感到困惑。他可能会带着疑问敲入如下代码:

print(square.__name__)
callf

这下,他可能会想看一看decorator_wraps_test.py的源码,找一找问题究竟出现在了哪里。我们知道,Python中所有对象都是“第 一类”的。比如,函数(对象),我们可以把它当作普通的数据对待:我们可以把它存储到容器中,或者作为另一个函数的返回值。这样,把trace返回的callf存储到square时,我们得到的不仅仅是callf函数执行语句,还有其上下文环境:

>>> print('debug_log' in square.__globals__)
True
>>> print('sys' in square.__globals__)
True

因此,使用装饰器修饰过的函数square,实际上是一个trace函数返回的“闭包”对象callf,这就揭示了上面help(square)以及print(square.name)的输出结果了。
那么,怎样才能在使用装饰器的基础上,还能让help(square)及print(square.name)得到我们期待的结果呢?这就是functools模块的wraps装饰器的作用了。
三、functools之wraps
先看看效果:

import functools
import sys
debug_log = sys.stderr

def trace(func):
    if debug_log:
        @functools.wraps(func)
        def callf(*args, **kwargs):
            """A wrapper function."""
            debug_log.write('Calling function: {}\n'.format(func.__name__))
            res = func(*args, **kwargs)
            debug_log.write('Return value: {}\n'.format(res))
            return res
        return callf
    else:
        return func

@trace
def square(x):
    """Calculate the square of the given number."""
    return x * x

if __name__ == '__main__':
    print(square(9))
    print(square.__doc__)
    print(square.__name__)

执行以上,输出:

Calling function: square
81
Return value: 81
Calculate the square of the given number.
square

至此,完美撒花!我们使用了一个带参数的wraps装饰器“装饰”了嵌套函数callf,得到了预期的效果。

参考:https://www.cnblogs.com/myd7349/p/how_to_use_wraps_of_functools.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值