python装饰器底层原理详解

  • Post category:Python

Python 装饰器是 Python 中非常重要的编程技巧,特别是对于那些需要在已有程序基础上动态增加功能或修改功能的人来说,装饰器功能的强大是不容忽视的。下面将详细阐述 Python 装饰器的底层原理,并提供两个示例以帮助读者更好地理解。

什么是装饰器?

在 Python 中,装饰器(decorator)是一种语法糖(syntactic sugar),可以动态地修改函数或类的功能。装饰器通常用来包装或“装饰”一个函数或类,以增加或修改它们的行为。装饰器本质上是一个高阶函数(higher-order function),它接受一个函数或类作为参数,返回一个经过修改的函数或类。Python 装饰器可以用于很多场景,例如:

  • 记录函数执行时间
  • 缓存数据
  • 校验输入输出参数
  • 鉴权
  • 日志记录
  • 等等

装饰器的使用方法

在 Python 中,装饰器的使用方法非常简单:

@decorator_func
def func():
    pass

上述语法等价于:

def func():
    pass
func = decorator_func(func)

装饰器函数 decorator_func 接受一个函数 func 作为参数,返回一个经过修改的 func 函数。这种语法糖使得我们可以轻松地为一个函数添加多个装饰器。

装饰器的底层原理

Python 装饰器的工作原理可以概括为:在函数定义时,Python 解释器会自动执行被装饰函数(func)所在的代码块,并将其作为参数传递给装饰器(decorator_func)函数。装饰器函数会返回一个新的函数(new_func),并将其赋值给原函数(func)的变量名。从此以后,调用原函数(func)实际上是调用经过装饰器修改后的新函数(new_func)。

装饰器的底层原理可以用以下代码来模拟:

def decorator_func(func):
    def new_func():
        # Add functionality to original function
        print("Adding functionality to original function")
        func()
    return new_func

def func():
    print("Original function")

# Call decorator function on the original function
func = decorator_func(func)
# Call the decorated function
func()

上述代码实现了装饰器的核心概念。我们首先定义了一个装饰器函数 decorator_func,其接受一个函数 func 作为参数,并返回一个新的函数 new_func。在 new_func 函数中,我们添加了对原函数 func 的功能扩展。接着,我们定义了一个原函数 func,最后调用 decorator_func 函数,将其返回的函数 new_func 赋值给原函数 func,从而实现了对原函数的装饰。

示例一:记录函数执行时间

我们经常需要记录函数的执行时间,下面是一个简单的装饰器,它可以用来记录函数的执行时间:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper

@timer
def test():
    time.sleep(1)

test()

上述代码中,装饰器函数 timer 接受一个函数 func 作为参数,返回一个新的函数 wrapper。在新函数 wrapper 中通过 time 模块记录了函数开始和结束的时间,并计算了函数运行的时间。最终输出函数的执行时间。wrapper 函数返回原函数 func 的结果,从而保持了原函数的行为不变。通过在 test 函数前面加上 @timer,我们成功地为该函数添加了一个记录执行时间的装饰器。

示例二:缓存函数结果

对于一些耗时长的函数,我们可能希望能够缓存其返回结果,以减少重复计算的开销。下面是一个简单的装饰器,它可以用来实现函数的结果缓存:

def cache(func):
    cached_results = {}

    def wrapper(*args):
        if args in cached_results:
            print("Using cached result")
            return cached_results[args]
        print("Calculating result for the first time")
        result = func(*args)
        cached_results[args] = result
        return result

    return wrapper

@cache
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(10))
print(fib(10))

上述代码中,装饰器函数 cache 接受一个函数 func 作为参数,返回一个新的函数 wrapper。在新函数 wrapper 中,我们定义了一个缓存字典 cached_results,用于保存函数的返回结果。wrapper 函数首先检查缓存字典中是否已有该函数参数对应的结果。如果存在,则直接从缓存中返回结果;否则调用原函数 func 计算结果,并将结果保存到缓存字典中。

通过在 fib 函数前面加上 @cache,我们成功地为该函数添加了一个结果缓存的装饰器。本例中的斐波那契数列计算会在第一次调用时比较耗时,但是之后的调用会变得非常快。