[python]装饰器的用法

本文通过实例讲解了Python装饰器的基本概念及工作原理,包括装饰器的使用步骤、内部运作机制、参数传递方式等,并介绍了如何解决装饰器导致的函数元信息丢失问题。

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

Step1

先从一个小例子开始,然后逐步引出装饰器的作用。

def name(name = 'James'):
    print('My name is {}.'.format(name))
        
name()

输出结果为:

My name is James.

如果此时想在打印“My name is...”之前加上打招呼的语句,且不能修改name()函数,就可以使用装饰器。

def greeting(func):
    def wrapper(*args, **kargs):
        print('Nice to meet you!')
        return func(*args, **kargs)
    return wrapper

@greeting
def name(name = 'James'):
    print('My name is {}.'.format(name))
    
name()
print(name.__name__)

输出结果为:

Nice to meet you!
My name is James.
wrapper

Step2

单步运行这段加入装饰器的代码(使用spyder IDE),看看装饰器是如何工作的。

运行到def name(name = 'James')这行后,执行step in操作,立即跳转到def greeting(func)这行,在ipdb中输入func(),返回结果为:

ipdb> func()
My name is James.

说明greeting()内带入的参数是name()函数

再单步运行到return wrapper这行,在ipdb中输入wrapper,返回结果为(每次返回的地址都不一样,下同):

ipdb> wrapper
<function greeting.<locals>.wrapper at 0x0A9E34B0>

然后就会跳出greeting(func)函数。继续执行,直到运行完name()这行代码后,然后就会输出:

Nice to meet you!
My name is James.

此时,在ipdb中输入name,返回结果为:

ipdb> name
<function greeting.<locals>.wrapper at 0x0A9E34B0>

可以看到wrapper和name的返回结果一致(虽然每次执行后at后面的地址会变,但两者的值一定是一样的),说明wrapper和name是同一个函数

执行完最后一行后,显示:

wrapper

明明是name()函数,为何函数名变成了wrapper?

Step3

我们重头再回顾下刚才这段代码,然后理解一下装饰器的工作原理。

def greeting(func):
    def wrapper(*args, **kargs):
        print('Nice to meet you!')
        return func(*args, **kargs)
    return wrapper

@greeting
def name(name = 'James'):
    print('My name is {}.'.format(name))
    
name()
print(name.__name__)
  1. @后面紧跟一个函数(此处是greeting()函数),这个函数必须有定义
  2. @greeting这行后紧跟一个函数,这个函数是greeting(func)函数的输入参数。在该例子中,greeting()函数的输入参数是name()函数,即func=name。所以,当执行func()的时候,实际上是执行了name()函数
  3. 在greeting()函数内定义了wrapper()函数,并返回wrapper()函数,这个返回值赋值给了同名的name,即执行完 greeting(name)后,将返回值wrapper赋值给name,name这个函数名已经不再属于原来那个name()函数了,而是属于wrapper()函数。这就可以回答为何print(name.__name__)返回的是wrapper而不是name
  4. 执行wrapper()函数的时候,func实际上是最原始的name()函数,先输出"Nice to meet you!",再调用func()函数——原始的name()函数,这样就实现了不改变来name函数并先输出期望的内容

为了解决name.__name__的值是wrapper而不是name,可以使用两个方法:

a. 在wrapper()函数内增加一行“wrapper.__name__ = func.__name__”:

def greeting(func):
    def wrapper(*args, **kargs):
        wrapper.__name__ = func.__name__
        print('Nice to meet you!')
        return func(*args, **kargs)
    return wrapper

b. 使用python内置的functools.wraps:

import functools

def greeting(func):
    @functools.wraps(func)
    def wrapper(*args, **kargs):
#        wrapper.__name__ = func.__name__
        print('Nice to meet you!')
        return func(*args, **kargs)
    return wrapper

Step4

希望按照个人意愿随意输出打招呼的话,而不仅仅只是“Nice to meet you!”,可以在@greeting后面加入参数,例如@greeting("Hello, everyone!"),此时,就需要在greeting()函数中增加一层。

import functools

def greeting(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kargs):
            print(text)
            return func(*args, **kargs)
        return wrapper
    return decorator

@greeting("Hello, everyone!")
def name(name = 'James'):
    print('My name is {}.'.format(name))

name()
print(name.__name__)

可以看到,greeting()函数的输入参数是打招呼的话,而不是@greeting下面一行的name。执行完greeting()后,返回decorator()函数,该函数带有输入参数,为name()函数。

@greeting("Hello, everyone!")
def name(name = 'James'):

上面这两句相当于:

decorator(name)

最后返回:

name=wrapper()

执行name()相当于执行wrapper()

Step5

目前有一个需求,同时支持@greeting和@greeting()两种装饰器。

import functools

def greeting(text_or_func):
    
    if not isinstance(text_or_func, str):
        @functools.wraps(text_or_func)
        def wrapper(*args, **kargs):
            print('Nice to meet you!')
            text_or_func(*args, **kargs)
        return wrapper
    
    if isinstance(text_or_func, str):
        def decorator(text_or_func):
            def wrapper(*args, **kargs):
                print(text_or_func)
                text_or_func(*args, **kargs)
            return wrapper
        return decorator

@greeting()
def name(name = 'James'):
    print('My name is {}.'.format(name))

name()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值