理解 Python 装饰器:全面指南

2023-03-30 00:00:00 理解 装饰 指南

Python 装饰器是一种强大的编程技术,它允许在不修改现有代码的情况下修改或增强函数的功能。这对于代码的复用和减少代码冗余非常有用。在本篇文章中,我们将为初学者介绍如何在 Python 中使用装饰器。

基本概念
在 Python 中,函数是一等公民,这意味着函数可以像其他对象一样进行操作,例如将它们作为参数传递给其他函数,将它们作为返回值返回,或者将它们赋值给变量。装饰器利用这个特性,可以将函数作为参数传递给另一个函数,并在传递过程中修改其行为,最终返回一个新的函数。

装饰器的语法如下:

@decorator
def function():
    pass

其中 decorator 是一个函数,它接受一个函数作为参数,并返回一个新的函数。```

在上面的代码中,装饰器 decorator 将被应用于函数 function,并返回一个新的函数。这个新函数就是装饰器的结果,它会替换原始的函数 function。这意味着当我们调用 function() 时,实际上是调用装饰器返回的新函数。

代码演示
让我们看一个简单的例子。假设我们有一个函数,它用于打印一条消息:

def say_hello():
    print("Hello, world!")

现在,我们想在每次调用这个函数时记录一条日志。我们可以使用一个装饰器来实现这个功能:

def log_decorator(func):
    def wrapper():
        print("Calling function: " + func.__name__)
        func()
        print("Function call ended")
    return wrapper

@log_decorator
def say_hello():
    print("Hello, world!")

say_hello()

在上面的代码中,我们定义了一个装饰器 log_decorator,它接受一个函数作为参数,并返回一个新的函数 wrapper。wrapper 函数首先打印一条日志,然后调用传递给装饰器的原始函数,并最后再打印一条日志。最后,我们使用 @log_decorator 装饰器将 say_hello 函数传递给 log_decorator,从而将装饰器应用于 say_hello 函数。当我们调用 say_hello() 时,实际上是调用装饰器返回的 wrapper 函数。

运行上面的代码会产生以下输出:

Calling function: say_hello
Hello, world!
Function call ended

我们可以看到,在调用 say_hello() 时,装饰器记录了一条日志。

现在,让我们看一个更复杂的例子。假设我们有一个函数,它用于检查参数是否为数字,如果是数字则返回它的平方,否则返回错误消息:

def square(num):
    if isinstance(num, (int, float)):
        return num ** 2
    else:
        return "Error: Input must be a number"

现在,我们想在每次调用这个函数时记录一条日志。我们还想使用一个缓存,将之前的函数调用结果缓存起来,以便在以后的调用中可以快速访问。我们可以使用两个装饰器来实现这个功能:

import functools

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Calling function: " + func.__name__)
        result = func(*args, **kwargs)
        print("Function call ended")
        return result
    return wrapper

def cache_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        cache_key = str(args) + str(kwargs)
        if cache_key not in wrapper.cache:
            wrapper.cache[cache_key] = func(*args, **kwargs)
        return wrapper.cache[cache_key]
    wrapper.cache = {}
    return wrapper

@cache_decorator
@log_decorator
def square(num):
    if isinstance(num, (int, float)):
        return num ** 2
    else:
        return "Error: Input must be a number"

print(square(5))  # Calling function: square
                  # Function call ended
                  # 25
print(square(5))  # 25 (从缓存中获取)

在上面的代码中,我们定义了两个装饰器:log_decorator 和 cache_decorator。log_decorator 和上一个例子中的装饰器是相同的,它用于记录日志。cache_decorator 也接受一个函数作为参数,并返回一个新的函数 wrapper。wrapper 函数首先根据传递给原始函数的参数生成一个缓存键,然后检查这个键是否存在于缓存中。如果存在,则直接返回缓存中的值,否则调用原始函数,并将结果存入缓存中。在最后,wrapper 函数返回结果。注意,我们将 cache_decorator 应用于 square 函数之前,这意味着每次调用 square 函数时,都会首先应用 cache_decorator,然后再应用 log_decorator。

运行上面的代码会产生以下输出:

Calling function: square
Function call ended
25
25

我们可以看到,第一次调用 square(5) 时,装饰器记录了一条日志,并将结果缓存到了缓存中。第二次调用 square(5) 时,从缓存中获取结果,不再记录日志。

这只是装饰器的一个简单示例,实际上装饰器可以做很多其他的事情,例如检查函数参数、计时函数执行时间等等。通过灵活地使用装饰器,可以使代码更加简洁、优雅、易于维护。

下面我们再看一个示例,这个示例使用装饰器来检查函数的参数类型。如果参数类型不正确,则会抛出一个异常。

def check_types(*arg_types, **kwarg_types):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Check positional arguments
            for arg, arg_type in zip(args, arg_types):
                if not isinstance(arg, arg_type):
                    raise TypeError(f"Expected argument of type {arg_type}, but got {type(arg)}")
            # Check keyword arguments
            for kwarg_name, kwarg_type in kwarg_types.items():
                if kwarg_name in kwargs and not isinstance(kwargs[kwarg_name], kwarg_type):
                    raise TypeError(f"Expected argument {kwarg_name} of type {kwarg_type}, but got {type(kwargs[kwarg_name])}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@check_types(int, str, pidancode.com=str)
def example_function(num, string, pidancode.com=None):
    print(f"num={num}, string={string}, pidancode.com={pidancode.com}")

example_function(1, "hello", pidancode.com="world")  # num=1, string=hello, pidancode.com=world
example_function("1", "hello", pidancode.com="world")  # TypeError: Expected argument of type <class 'int'>, but got <class 'str'>

在上面的代码中,我们定义了一个名为 check_types 的装饰器。这个装饰器接受任意数量的参数类型和关键字参数类型。装饰器返回一个新函数 wrapper,该函数用于检查函数的参数类型。wrapper 函数首先检查位置参数的类型是否正确,然后检查关键字参数的类型是否正确。如果参数类型不正确,则会抛出一个 TypeError 异常。否则,将调用原始函数并返回结果。在定义 example_function 时,我们使用 @check_types(int, str, pidancode.com=str) 应用了装饰器。这意味着 example_function 将首先被 check_types 装饰器处理,然后再执行。

我们可以看到,当我们使用正确的参数类型调用 example_function 时,函数会正常工作,并打印出预期的输出。但是,当我们使用错误的参数类型调用 example_function 时,将抛出一个 TypeError 异常。

装饰器是 Python 中非常强大和有用的概念。通过使用装饰器,我们可以轻松地添加额外的功能和行为,而不必修改原始函数的代码。同时,装饰器也是 Python 中比较高级和难以理解的概念,初学者在使用装饰器时需要仔细思考和研究,以确保正确使用装饰器。

相关文章