八、装饰器

本文深入浅出地介绍了Python装饰器的基本概念、语法糖、应用场景及其实现原理,并提供了多个实例帮助理解。

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

装饰器

1)本质:装饰器的本质是函数,其基本语法都是用关键字def去定义的。

(2)功能:装饰其他函数,即:为其他函数添加附加功能。

(3)原则:不能修改被装饰的函数的源代码,不能修改被装饰的函数的调用方式。即:装饰器对待被修饰的函数是完全透明的。

第一步

我们想在函数login中输出调试信息,我们可以这样做

def login():
    print('in login')

def printdebug(func):
    print('enter the login')
    func()
    print('exit the login')

printdebug(login)

这个方法讨厌的是每次调用login是,都通过printdebug来调用,但毕竟这是可行的。

让代码变得优美一点

既然函数可以作为返回值,可以赋值给变量,我们可以让代码优美一点。

def login():
    print('in login')

def printdebug(func):
    def __decorator():
        print('enter the login')
        func()
        print('exit the login')
    return __decorator  #function as return value

debug_login = printdebug(login)  #function assign to variable

debug_login()  #execute the returned function

这样我们每次只要调用debug_login就可以了,这个名字更符合直觉。我们将原先的两个函数printdebug和login绑定到一起,成为debug_login。这种耦合叫内聚:-)。

让代码再优美一点

printdebug和login是通过debug_login = printdebug(login)这一句来结合的,这一句似乎也是多余的,能不能在定义login是加个标注,从而将printdebug和login结合起来?

上面的代码从语句组织的角度来讲很难再优美了,Python的解决方案是提供一个语法糖(Syntax Sugar),用一个@符号来结合它们。


def printdebug(func):
    def __decorator():
        print('enter the login')
        func()
        print('exit the login')
    return __decorator 

@printdebug  #combine the printdebug and login
def login():
    print('in login')

login()  #make the calling point more intuitive

可以看出decorator就是一个:使用函数作参数并且返回函数的函数。通过改进我们可以得到:

  • 更简短的代码,将结合点放在函数定义时
  • 不改变原函数的函数名

在Python解释器发现login调用时,他会将login转换为printdebug(login)()。也就是说真正执行的是__decorator这个函数。

加上参数

login函数带参数

login函数可能有参数,比如login的时候传人user的信息。也就是说,我们要这样调用login:

1                  longin(user)

Python会将login的参数直接传给__decorator这个函数。我们可以直接在__decorator中使用user变量:

def printdebug(func):
    def __decorator(user):    #add parameter receive the user information
        print('enter the login')
        func(user)  #pass user to login
        print('exit the login')
    return __decorator 

@printdebug 
def login(user):
    print('in login:' + user)

login('jatsz')  #arguments:jatsz

我们来解释一下login(‘jatsz’)的调用过程:

[decorated] login(‘jatsz’)   =>   printdebug(login)(‘jatsz’)  =>  __decorator(‘jatsz’)  =>  [real] login(‘jatsz’)

装饰器本身有参数

我们在定义decorator时,也可以带入参数,比如我们这样使用decorator,我们传入一个参数来指定debug level。

@printdebug(level=5)
def login
    pass

为了给接收decorator传来的参数,我们在原本的decorator上在包装一个函数来接收参数:


def printdebug_level(level):  #add wrapper to recevie decorator's parameter
    def printdebug(func):
        def __decorator(user):   
            print('enter the login, and debug level is: ' + str(level)) #print debug level
            func(user) 
            print('exit the login')
        return __decorator 
    return printdebug    #return original decorator

@printdebug_level(level=5)   #decorator's parameter, debug level set to 5
def login(user):
    print('in login:' + user)

login('jatsz')

解释一下login(‘jatsz’)整个调用过程:

[decorated]login(‘jatsz’) => printdebug_level(5) => printdebug[with closure value 5](login)(‘jatsz’) => __decorator(‘jatsz’)[use value 5]  => [real]login(‘jatsz’)

装饰有返回值的函数

有时候login会有返回值,比如返回message来表明login是否成功。

1                    login_result = login(‘jatsz’)

我们需要将返回值在decorator和调用函数间传递:


def printdebug(func):
    def __decorator(user):   
        print('enter the login')
        result = func(user)  #recevie the native function call result
        print('exit the login')
        return result        #return to caller
    return __decorator 

@printdebug 
def login(user):
    print('in login:' + user)
    msg = "success" if user == "jatsz" else "fail"
    return msg  #login with a return value

result1 = login('jatsz');
print result1  #print login result

result2 = login('candy');
print result2

我们解释一下返回值的传递过程:

…omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1

应用多个装饰器

我们可以对一个函数应用多个装饰器,这时我们需要留心的是应用装饰器的顺序对结果会产生。影响比如:


def printdebug(func):
    def __decorator():   
        print('enter the login')
        func()
        print('exit the login')
    return __decorator 

def others(func):    #define a other decorator
    def __decorator():
        print '***other decorator***'
        func()
    return __decorator

@others         #apply two of decorator
@printdebug
def login():
    print('in login:')

@printdebug    #switch decorator order
@others
def logout():
    print('in logout:')

login()
print('---------------------------')
logout()

我们定义了另一个装饰器others,然后我们对login函数和logout函数分别应用这两个装饰器。应用方式很简单,在函数定义是直接用两个@@就可以了。我们看一下上面代码的输出:


$ python deoc.py
***other decorator***
enter the login
in login:
exit the login
---------------------------
enter the login
***other decorator***
in logout:
exit the login

我们看到两个装饰器都已经成功应用上去了,不过输出却不相同。造成这个输出不同的原因是我们应用装饰器的顺序不同。回头看看我们login的定义,我们是先应用others,然后才是printdebug。而logout函数真好相反,发生了什么?如果你仔细看logout函数的输出结果,可以看到装饰器的递归。从输出可以看出:logout函数先应用printdebug,打印出“enter the login”。printdebug的__decorator调用中间应用了others的__decorator,打印出“other decorator”。其实在逻辑上我们可以将logout函数应用装饰器的过程这样看(伪代码):


@printdebug    #switch decorator order
(
    @others
    (
        def logout():
            print('in logout:')
    )
)
我们解释一下整个递归应用decorator的过程:

[printdebug decorated]logout() =>

printdebug.__decorator[call [others decorated]logout() ] =>

printdebug.__decorator.other.__decorator[call real logout]

7,灵活运用
什么情况下装饰器不适用?装饰器不能对函数的一部分应用,只能作用于整个函数。

login函数是一个整体,当我们想对部分函数应用装饰器时,装饰器变的无从下手。比如我们想对下面这行语句应用装饰器:

1                     msg = "success" if user == "jatsz" else "fail"

怎么办?

一个变通的办法是“提取函数”,我们将这行语句提取成函数,然后对提取出来的函数应用装饰器:


def printdebug(func):
    def __decorator(user):   
        print('enter the login')
        result = func(user)
        print('exit the login')
        return result     
    return __decorator 

def login(user):
    print('in login:' + user)
    msg = validate(user)  #exact to a method
    return msg 

@printdebug  #apply the decorator for exacted method
def validate(user):
    msg = "success" if user == "jatsz" else "fail"
    return msg

result1 = login('jatsz');
print result1

来个更加真实的应用,有时候validate是个耗时的过程。为了提高应用的性能,我们会将validate的结果cache一段时间(30 seconds),借助decorator和上面的方法,我们可以这样实现:

import time

dictcache = {}

def cache(func):
    def __decorator(user):   
        now = time.time()
        if (user in dictcache):
            result,cache_time = dictcache[user]
            if (now - cache_time) > 30:  #cache expired
                result = func(user)
                dictcache[user] = (result, now)  #cache the result by user
            else:
                print('cache hits')
        else:
            result = func(user)
            dictcache[user] = (result, now)
        return result     
    return __decorator 

def login(user):
    print('in login:' + user)
    msg = validate(user) 
    return msg 

@cache  #apply the cache for this slow validation
def validate(user):
    time.sleep(5)  #simulate 10 second block
    msg = "success" if user == "jatsz" else "fail"
    return msg

result1 = login('jatsz'); print result1 
result2 = login('jatsz'); print result2    #this login will return immediately by hit the cache
result3 = login('candy'); print result3

Reference:

https://blog.youkuaiyun.com/xionghuixionghui/article/details/65442554 原文
http://stackoverflow.com/questions/739654/understanding-python-decorators –Understanding Python decorators

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html –Python装饰器学习(九步入门)

http://www.python.org/dev/peps/pep-0318/ –PEP 318 – Decorators for Functions and Methods

<think>嗯,用户让我介绍一下Python装饰器的用法。首先,我得回忆一下装饰器的基本概念。装饰器是用来修改或增强函数行为的工具,本质上是一个高阶函数,接受一个函数作为参数,然后返回一个新的函数。可能用户已经对函数和闭包有一定的了解,但可能需要从基础开始解释。 用户可能已经知道Python中的函数是一等公民,可以作为参数传递或者返回。装饰器就是利用这一点,在代码中通过@符号来应用。我需要举几个简单的例子,比如记录日志、测量执行时间这样的常见用例,这样用户能直观地理解装饰器的作用。 接下来,我应该分步骤说明如何创建装饰器。首先是定义一个装饰器函数,里面包含一个包装函数,用来包裹原函数。然后展示如何用@语法来应用这个装饰器。这里可能需要解释装饰器的执行顺序,比如多个装饰器叠加时的顺序问题。 用户可能会有疑问,比如装饰器会不会改变原函数的元信息?这时候需要提到使用functools.wraps来保留原函数的名称和文档字符串。这也是一个常见的注意事项,应该在例子中展示如何正确使用它。 另外,可能需要介绍带参数装饰器,这种情况需要再包裹一层函数,用来接受参数。这部分可能稍微复杂,但可以通过例子逐步分解。比如一个可以重复执行某函数多次的装饰器,并允许指定重复次数。 还有类装饰器,虽然不如函数装饰器常见,但有时候用类来实现装饰器会更方便,尤其是需要维护状态的情况。可以简单提一下,但可能不需要深入,除非用户有进一步的需求。 用户可能的误区包括不理解装饰器在导入时立即执行,而不是在调用时。比如,如果装饰器内部有打印语句,可能在函数定义时就会执行。这一点需要提醒,避免用户在使用时产生困惑。 最后,总结装饰器的常见应用场景,比如授权检查、缓存、日志等,让用户知道装饰器的实际用途,而不仅仅是语法。同时,可以推荐一些进一步学习的资源,比如官方文档或者进阶教程,帮助用户深入学习。</think>好的!以下是对 Python 装饰器(Decorator)的详细介绍,尽量用简单易懂的方式说明其用法和原理👇 --- ### **一、什么是装饰器?** 装饰器是一种 **修改或增强函数行为** 的工具,本质上是一个高阶函数(接受函数作为参数,并返回新的函数)。 通过装饰器,你可以在 **不修改原函数代码** 的情况下,为函数添加新功能(如日志、计时、权限验证等)。 --- ### **二、基础用法** #### 1. 最简单的装饰器 ```python def my_decorator(func): # 装饰器函数,接受一个函数作为参数 def wrapper(): # 定义一个包裹原函数的新函数 print("函数执行前...") func() # 调用原函数 print("函数执行后...") return wrapper # 返回新函数 @my_decorator # 通过 @ 语法应用装饰器 def say_hello(): print("Hello!") say_hello() # 调用被装饰后的函数 ``` **输出**: ``` 函数执行前... Hello! 函数执行后... ``` --- #### 2. 装饰器的作用解析 - **`@my_decorator` 等价于**:`say_hello = my_decorator(say_hello)` - 装饰器将原函数 `say_hello` 替换为 `wrapper` 函数,从而添加了额外逻辑。 --- ### **三、处理带参数的函数** 如果原函数有参数,需要在 `wrapper` 中使用 `*args` 和 `**kwargs` 接收任意参数: ```python def decorator_with_args(func): def wrapper(*args, **kwargs): print("准备执行函数...") result = func(*args, **kwargs) # 传递参数给原函数 print("执行完毕") return result # 返回原函数的结果 return wrapper @decorator_with_args def add(a, b): return a + b print(add(3, 5)) # 输出:准备执行函数... 执行完毕 8 ``` --- ### **四、装饰器自身带参数** 若装饰器需要接受额外参数(如配置选项),需要再嵌套一层函数: ```python def repeat(n): # 外层函数接受装饰器参数 def decorator(func): # 内层函数接受被装饰的函数 def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator @repeat(3) # 装饰器参数,表示重复执行3次 def greet(name): print(f"Hello {name}!") greet("Alice") ``` **输出**: ``` Hello Alice! Hello Alice! Hello Alice! ``` --- ### **五、保留原函数的元信息** 装饰器会覆盖原函数的名称、文档等元信息,使用 `functools.wraps` 解决: ```python from functools import wraps def log_time(func): @wraps(func) # 保留原函数的元信息 def wrapper(*args, **kwargs): print("开始计时...") result = func(*args, **kwargs) print("计时结束") return result return wrapper @log_time def calculate(x, y): """计算两数之和""" return x + y print(calculate.__name__) # 输出 "calculate"(而不是 "wrapper") print(calculate.__doc__) # 输出 "计算两数之和" ``` --- ### **六、类装饰器** 装饰器也可以用类实现(通过 `__call__` 方法): ```python class Timer: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): import time start = time.time() result = self.func(*args, **kwargs) end = time.time() print(f"函数执行耗时: {end - start:.2f}秒") return result @Timer def long_operation(): time.sleep(2) long_operation() # 输出:函数执行耗时: 2.00秒 ``` --- ### **七、常见应用场景** 1. **日志记录**:自动记录函数调用信息。 2. **性能分析**:统计函数执行时间。 3. **权限验证**:检查用户权限后再执行函数。 4. **缓存**:缓存函数结果(如 `functools.lru_cache`)。 5. **错误重试**:函数出错时自动重试。 --- ### **、总结** - **核心思想**:装饰器通过高阶函数实现“代码增强”。 - **关键语法**:使用 `@decorator_name` 快速应用。 - **注意事项**:处理参数、保留元信息、多层嵌套等。 希望这些例子能帮助你理解装饰器的用法!如果有具体需求,可以进一步探讨如何实现特定功能的装饰器 😊
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值