欢迎访问宙启技术站
智能推送

Python中的装饰器函数及其应用技巧

发布时间:2023-06-16 09:33:23

Python中的装饰器可以被看作是函数的修饰器,它们是一种特殊的函数,拥有能够接受其他函数作为参数并对这些函数进行修饰的能力。装饰器函数可以被用来修改函数的行为,例如增加缓存、在函数执行前/后增加日志记录等。装饰器采用“@function_name”的语法糖,这使得程序的代码十分简洁易读,同时也为其他开源库提供了很好的扩展性。

Python装饰器函数的基本形式

Python中的装饰器函数可以用一个普通的函数来实现。这个函数必须具备能够接受另一个函数对象作为参数,并返回一个新的函数对象的能力。一般情况下,装饰器函数的返回值都是一个接受任何参数并调用被装饰函数的函数。

下面是一个简单的装饰器的例子:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function call...")
        result = func(*args, **kwargs)
        print("After function call...")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("John")

在上面的例子中,my_decorator() 函数是一个装饰器函数,可以被用来对下面的 greet() 函数进行修饰。通过添加 @my_decorator 语法糖,我们把 greet() 函数传递给 my_decorator() 函数进行处理,最后返回了一个新的函数对象--- wrapper() 函数。

wrapper() 函数是在 my_decorator() 函数中定义的,它承担了对被修饰函数进行包装并执行额外操作的责任。在本例中,wrapper() 打印一条消息,然后调用 greet() 函数并存储其结果。最后,它再度打印另一条消息并返回 greet() 函数的结果。这里使用了 *args 和 **kwargs 去支持 greet 函数的任何参数。

装饰器函数的应用技巧

利用装饰器来增加类的功能

装饰器不仅仅可以被用来装饰方法,还可以用来装饰类。这在小型程序以及快速验证中非常有用。下面是一个示例, 应用装饰器来使得一个类实例化时自动进行属性赋值:

def auto_assign(func):
    def wrapper(instance, *args, **kwargs):
        func(instance, *args, **kwargs)
        instance.__dict__.update((k, v) for k, v in instance.__annotations__.items() if k not in instance.__dict__)
    return wrapper

@auto_assign
class Person:
    name: str
    age: int
    city: str

p = Person()
p.name = "John"
print(p.__dict__)

这里我们使用了 python的注解来为类添加额外的信息。我们可以在注解后面定义变量的类型,这在一些类型别名功能中,比如Pydantic一样奏效。功能是,创建 Person 对象时,由自动帮我们把缺少的属性值设置为None。在这个例子中,我们把name属性值设为 John,然后使用 __dict__ 打印了整个对象的信息,从结果中我们可以看出,age和city 的值都被设置为了 None。

装饰器工厂

有时候,我们需要一个拥有参数的装饰器来进行不同的工作,为了便于实现,我们可以定义一个装饰器工厂。一个装饰器工厂就是一个函数,它返回一个装饰器函数。这个工厂通常接受一些参数,这些参数可以让你根据自己的需要生成定制的装饰器。

下面是一个简单的例子:

def trace(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[LEVEL {level}] Executing function {func.__name__} with {args} and {kwargs}")
            result = func(*args, **kwargs)
            print(f"Function {func.__name__} returned {result}")
            return result
        return wrapper
    return decorator

@trace(level=2)
def greet(name):
    print(f"Hello {name}")

greet("Tom")

在这个例子中,我们定义了一个带有参数的装饰器 trace(),它返回一个装饰器函数 decorator()。decorator() 函数再次返回一个新的函数 wrapper(),它用于真正修饰被装饰函数 greet()。参数 level 的值来自于原始调用方向 trace() 传递的值。

这里,wrapper() 函数会在被装饰函数 greet() 前面、后面打印出一些内容来模拟方法的“跟踪”,这对调试非常有帮助。

在本例中,greet("Tom") 函数的执行输出为:

[LEVEL 2] Executing function greet with ('Tom',) and {}
Hello Tom
Function greet returned None

使用类装饰器

装饰器不仅可以用来装饰单个函数,还可以用来装饰整个类。下面是一个基本的实现,类装饰器相当于把一个类传进一个包装类,用这个包装类来封装原有类的所有行为:

class Decorator:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):
        obj = self.cls(*args, **kwargs)
        obj.__class__ = self.new_cls
        return obj

    def __new__(cls, new_cls=None):
        if new_cls:
            cls.new_cls = new_cls
            return cls
        else:
            return cls

    def __getattr__(self, name):
        try:
            return getattr(self.cls, name)
        except AttributeError:
            return getattr(self.new_cls, name)

class MyDecorator(Decorator):

    def __call__(self, *args, **kwargs):
        result = self.cls(*args, **kwargs)
        result = self.add_property(result)
        return result
        
    def add_property(self, obj):
        obj.name = "Tom"
        obj.age = 25
        return obj

@MyDecorator
class Person:
    pass

p = Person()
print(f"Name: {p.name}, Age: {p.age}")

这里我们定义了一个类 Decorator,表示一个装饰器的基本结构,这个类的 __call__ 方法实现了对被装饰类的对象进行包装。我们还定义了 MyDecorator 子类,它要做的工作是向 Person 类添加 name 和 age 这两个属性。最后,我们使用 @MyDecorator 语法糖装饰 Person 类,应用装饰器来增加类的功能。

总结

Python中装饰器函数非常有用,可以用来增加函数的功能并且十分灵活。它们是一个特殊的函数,拥有能够接受其他函数作为参数并对这些函数进行修饰的能力。由于装饰器的处理本质上在函数调用阶段,所以它们不仅是 Python 代码的一部分,而且还可以通过“组合”等方式来简化代码。为了学习 Python 装饰器,我们需要掌握一些常见的用法。这些用法包括构建基本装饰器函数,利用装饰器来增加类的功能、装饰器工厂以及类装饰器。理解了这些技巧,我们就可以在实际应用中轻松地利用装饰器根据不同的