理解 Python 装饰器:全面指南
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 中比较高级和难以理解的概念,初学者在使用装饰器时需要仔细思考和研究,以确保正确使用装饰器。
相关文章