详解Python的装饰器 通俗易懂的解释!
发布日期:2021-05-20 05:59:03 浏览次数:22 分类:精选文章

本文共 3997 字,大约阅读时间需要 13 分钟。

Python装饰器是377大力推到的一个强大的工具,它能够在不需要修改原有代码的情况下,为函数或对象添加额外功能。这在软件开发中非常有用,特别是在需要记录调试信息、处理事务、缓存结果或验证权限时。装饰器的设计初衷是抽象出代码中的通用功能,使其能够被多次复用,从而提高代码的可维护性和扩展性。

为什么需要装饰器

假设你有一个简单的程序,包含say_hello()和say_goodbye()两个函数。最初的实现可能如下:

def say_hello():    print("hello!")def say_goodbye():    print("hello!")  # 这里有个bugif __name__ == '__main__':    say_hello()    say_goodbye()

然而,在实际调用中,你发现程序错误地打印了两个“hello”。经过调试,你发现问题出在say_goodbye()函数上。为了解决这个问题,你的老板要求在调用每个函数之前记录函数名称,并输出相应的调试信息。你如何实现这一点?

初步解决方案

你的初步想法是为每个函数手动添加调试逻辑:

def say_hello():    print("[DEBUG]: enter say_hello()")    print("hello!")def say_goodbye():    print("[DEBUG]: enter say_goodbye()")    print("hello!")if __name__ == '__main__':    say_hello()    say_goodbye()

这样虽然解决了问题,但显得非常low-level,每个函数都需要手动调用调试函数。这样的实现难以扩展,尤其当业务逻辑逐渐复杂化时。

装饰器的登场

这个时候,装饰器就应运而生。装饰器是一种函数,它可以为另一个函数或对象添加额外功能,而无需修改原有的代码结构。装饰器的核心是将实现相同功能的逻辑进行抽象和封装,通过装饰器,你可以在不修改函数定义的前提下,给函数 加上调试功能、性能监控、事务处理等。

简单装饰器的实现

在早期的Python版本(版本小于2.4,2004年以前),装饰器的实现方式比较直接。你可以定义一个装饰器函数,它接收目标函数作为参数,并返回一个新的函数,这个新函数携带额外的功能:

def debug(func):    def wrapper():        print("[DEBUG]: enter %s()" % func.__name__)        return func()    return wrapper@debugdef say_hello():    print("hello!")@debugdef say_goodbye():    print("goodbye!")

这样,say_hello和say_goodbye函数被装饰后,执行时会自动输出调试信息。不过,这种实现方式对于带有参数的函数并不友好。为了解决这个问题,Python提供了@语法糖,使得装饰器的写法更加简洁。

带参数的装饰器

为了更灵活地使用装饰器,我们可以让装饰器函数能够接收和处理函数的参数。Python的*args和**kwargs参数提供了很大的灵活性:

def debug(func):    def wrapper(*args, **kwargs):        print("[DEBUG]: enter %s()" % func.__name__)        return func(*args, **kwargs)    return wrapper@debugdef say(something):    print("hello %s!" % something)

这通过装饰器,函数say在被调用时,会先输出调试信息,然后执行原有的功能。

高级装饰器

在实际应用中,装饰器往往需要自己带有参数。例如,你希望根据不同的log级别选择不同的调试信息:

def logging(level):    def decorator(func):        def wrapper(*args, **kwargs):            print("[%s]: enter %s()" % (level, func.__name__))            return func(*args, **kwargs)        return wrapper    return decorator@logging(level="INFO")def say(something):    print("hello %s!" % something)

这通过登记装饰器函数,并传递log级别,实现了高度定制的调试功能。

基于类的装饰器

装饰器不仅可以是函数,也可以是类。类装饰器允许你在构造函数时传递参数,并在__call__方法中执行额外功能:

class logging:    def __init__(self, level="INFO"):        self.level = level    def __call__(self, func):        def wrapper(*args, **kwargs):            print("[%s]: enter %s()" % (self.level, func.__name__))            return func(*args, **kwargs)        return wrapper@logging(level="DEBUG")def do(something):    print("do %s..." % something)

这种实现方式有时比简单的函数装饰器更加灵活,尤其是在处理与类相关的任务时。

内置装饰器

Python的内置装饰器,如@property、@staticmethod和@classmethod,虽然设计目的不同,但其实都是基于函数装饰器的原理。例如,@property装饰器用于创建属性,通过切掉 setter和 deleter 方法的外观,相当于为属性管理器进行装饰。

常见问题及解决方案

1. 装饰器返回的函数的名字和文档不正确

在早期实现的装饰器中,装饰器函数返回的wrapper函数的名字和文档会覆盖原函数的属性,这是因为wrapper函数是其实调用目标函数的中间层。为了避免这个问题,Python提供了functools.wraps装饰器,它可以复制目标函数的属性:

from functools import wrapsdef logging(func):    @wraps(func)    def wrapper(*args, **kwargs):        print("[DEBUG]: enter %s()" % func.__name__)        return func(*args, **kwargs)    return wrapper@loggingdef say(something):    print("hello %s!" % something)

这样,say函数的名称和文档将分别是“say”和“hello

!”,而不会受到wrapper函数的影响。

2. 装饰器无法装饰@staticmethod或@classmethod

与Flex Shea的文章相同,静态方法和类方法装饰器返回的是特殊的对象类型,这些对象期望接收类似装饰器的调用方式。在实际应用中,可以将装饰器放在对应的装饰器之前:

class Car:    def __init__(self, model):        self.model = model    @staticmethod    @logging    def check_modelvehicles(obj):        if isinstance(obj, Car):            print("The model of your car is %s" % obj.model)        else:            print("%s is not a car" % obj)

3. 装饰器的可扩展性

当你逐渐意识到装饰器的强大能力时,可能会想要为现有的装饰器层添加更多功能。这时候,可以使用第三方库如wrapt,或是自定义更复杂的装饰器结构。

4. wrapt装饰器

wrapt是一个功能强大的包,它可以帮助你创建能够完整保留目标函数属性的装饰器。使用wrapt,你可以在目标函数的签名和文档等信息完全保留的情况下,实现复杂的装饰器逻辑:

import wrapt@wrapt.decoratordef logging(wrapped, instance, args, kwargs):    print("[DEBUG]: enter %s()" % wrapped.__name__)    return wrapped(*args, **kwargs)

这种方式不仅保留了目标函数的属性,还允许你根据不同的调用情境自定义装饰器行为。

小结

装饰器是一种强大的工具,它能够在不修改现有代码的情况下,为函数或对象添加新的功能。装饰器的使用可以显著提升代码的可维护性和扩展性。虽然装饰器的学习和使用需要一定的技巧,但是一旦掌握了装饰器的基本原理,编写和使用装饰器会变得十分容易。

上一篇:Python 函数装饰器--最全面的详细的装饰器文章
下一篇:git教程之Version Control

发表评论

最新留言

不错!
[***.144.177.141]2025年05月02日 15时07分08秒