python装饰器初步了解

本文深入探讨了Python装饰器的概念,通过实例展示了如何使用装饰器为函数添加额外功能,如日志记录。还讨论了如何处理带参数的函数以及使用`functools.wraps`保持被装饰函数元信息的完整。最后,文章提到了装饰器的语法糖`@`,并解释了为何使用`wraps`来修复函数元数据的重要性。
摘要由CSDN通过智能技术生成

参考:
Python 函数装饰器

关于装饰器的理解

装饰器是一个嵌套函数,可以理解为一个加工场所,把需要装饰的函数传递过去,装饰完成之后再返回出来。

举例

假设现在有一个下面这样的函数:

def foo():
    print('i am foo')

现在需要给它添加一些额外的功能,比如输出函数的执行日志,那我们可以这样做:

def foo():
    print('i am foo')
    logging.warning('foo is running')

我们在函数内部添加一句日志代码不就可以了吗?当然可以,但是假如现在还有其他函数也需要添加这句代码,难道要每一个函数都给它重新加几句代码吗?显然,这样是很麻烦的。
那么,我们还可以这样做:

def use_logging(func):
    logging.warning('%s is running'%func.__name__)
    func()

我们重新定义了一个函数叫做use_logging(func),它接收一个函数作为参数,在内部执行日志的输出,之后再执行传递过来的函数。
从功能上来说,这样就实现了我们的需求:
在这里插入图片描述
这样做当然是可以的,但是实际上我们每次调用的是use_logging(func)这个函数,这就破环了原来的代码结构,而且,每次都需要使用use_logging(func),并且传递参数,也很麻烦。
那么,有没有更好的方法呢,当然有,那就是装饰器。

一个简单的装饰器

装饰器利用嵌套函数来实现,为什么这样呢,看着代码其实可以体会到。

#定义一个装饰器函数,把你需要装饰(添加额外功能)的函数,作为参数拆传递进去
def use_logging(func):#func是 需要装饰的函数的 形参
    
    #内部的函数实现具体的装饰细节
    def wrapper():
        logging.warning('%s is running'%func.__name__)
        func()
    
    return wrapper
def foo():
    print('i am foo')

使用这个装饰器:

foo = use_logging(foo)

foo()的定义如上,use_logging(foo),将foo指向的函数传递进去,在其内部其实就是将wrapper()函数返回出来,执行完之后,就相当于foo = wrapper ,其实就是变量之间的赋值,因为函数名也是一种变量。
foo送进去装饰完之后返回的是另一个函数,但是由于变量之间的相互赋值,将新的函数再次赋值给foo,下次调用还是使用foo(),就好像foo()没变,却添加了额外的功能。

我们看下效果:
在这里插入图片描述
确实的实现了需要的效果,同时函数名还没变。

语法糖

语法糖(Syntactic sugar):

  • 计算机语言中特殊的某种语法
  • 这种语法对语言的功能并没有影响
  • 对于程序员有更好的易用性
  • 能够增加程序的可读性

简而言之,语法糖就是程序语言中提供[奇技淫巧]的一种手段和方式而已。 通过这类方式编写出来的代码,即好看又好用,好似糖一般的语法。固美其名曰:语法糖。

语法糖还没有仔细了解

@就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步的再次赋值的操作。

函数的定义依旧:

#定义一个装饰器函数,把你需要装饰(添加额外功能)的函数,作为参数拆传递进去
def use_logging(func):#func是 需要装饰的函数的 形参
    
    #内部的函数实现具体的装饰细节
    def wrapper():
        logging.warning('%s is running'%func.__name__)
        func()
    
    return wrapper

#这里有不同
@use_logging
def foo():
    print('i am foo')

这时候,使用这个装饰器就不再需要foo = use_logging(foo)这句代码了,我们就可以直接使用foo()
在这里插入图片描述
这样,foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

需要装饰的函数带有参数

假如现在我的函数foo()需要参数,比如:

def foo(*args):
    for name in args:
        print('my name is %s'%name)

在这里插入图片描述
那我们就可以在定义wrapper()函数的时候指定参数,给它定义同样的形参:

def use_decorator(func):
    #实现装饰的函数
    def wrapper(*args):
        logging.warning('%s is running'%func.__name__)
        func(*args)#给出同样的形参
    
    return wrapper
foo('zs','ls','ww')
@use_decorator
def foo(*args):
    for name in args:
        print('my name is %s'%name)

在这里插入图片描述
所以,其实不论foo(*args, **kwargs),是可变参数,或者关键字参数,实现起来都是类似的。

functools.wraps
现在看一个装饰器的例子:
#定义一个装饰器函数
def decorate(func):
    #装饰函数
    def wrapper():
        '''这是内部实现装饰的函数wrapper()注释文档'''
        print('装饰语句')
        func()
        print('装饰语句')

    return wrapper

#这是需要装饰的函数
@decorate
def f():
    '''这是f()的注释文档'''
    print('我是f(),需要装饰')

if __name__ == '__main__':
    f()

在这里插入图片描述
很符合预期的输出。

接下来我们输出下函数f的信息:

#定义一个装饰器函数
def decorate(func):
    #装饰函数
    def wrapper():
        '''这是内部实现装饰的函数wrapper()注释文档'''
        print('装饰语句')
        func()
        print('装饰语句')

    return wrapper

#这是需要装饰的函数
@decorate
def f():
    '''这是f()的注释文档'''
    print('我是f(),需要装饰')

if __name__ == '__main__':
    f()
    print('--------------------------------')
    print(f'函数f()的名称{f.__name__}')
    print(f'函数f()的注释文档{f.__doc__}')

在这里插入图片描述结果是:函数f输出的名称以及注释文档全是内部wrapper函数的。很明显这不是我们想要的,但是为什么会这样呢。

因为在装饰器函数内部实际上是把wrapper函数返回了,也就是
f = decorate(f) —> f = wrapper
变量f以及wrapper都指向的是内部的那个函数对象,所以我们在外部输出的都是wrapper指向的函数对象的信息。

也就是下面这种情况:

	def f1():
	    '''f1的注释'''
	    print('i am f1')
	
	def f2():
	    '''f2的注释'''
	    print('i am f2')

在这里插入图片描述
那么,怎么解决这个问题呢?
需要使用functools.wraps

使用之前需要导入:

from functools import wraps

修改代码为:

from functools import wraps

#定义一个装饰器函数
def decorate(func):
    #装饰函数
    @wraps(func)#这里是添加的代码
    def wrapper():
        '''这是内部实现装饰的函数wrapper()注释文档'''
        print('装饰语句')
        func()
        print('装饰语句')

    return wrapper

#这是需要装饰的函数
@decorate
def f():
    '''这是f()的注释文档'''
    print('我是f(),需要装饰')

if __name__ == '__main__':
    f()
    print('--------------------------------')
    print(f'函数f()的名称{f.__name__}')
    print(f'函数f()的注释文档{f.__doc__}')

在这里插入图片描述
这样就解决了这个问题,区别仅仅是添加了一句:

@wraps(func)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值