详解Python 装饰器

  • Post category:Python

Python 装饰器是一种特殊的语法结构,它可以在不修改原函数代码的情况下,动态地增加某些功能或者修改函数的行为。在实际编程中,装饰器被广泛应用于日志记录、性能分析、权限验证等方面。下面我将为您详细讲解 Python 装饰器的使用方法,以及两个示例说明。

一、装饰器基础

1.1 简单的装饰器

我们先看一个简单的装饰器示例,它可以在函数执行前后打印出函数名称和执行时间:

import time

def time_it(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print('{} took {} seconds to run.'.format(func.__name__, end - start))
        return result
    return wrapper

@time_it
def my_function():
    time.sleep(1)
    print('Function executed.')

my_function()

上述代码中,我们定义了一个 time_it 装饰器,它是一个闭包函数,接受一个函数作为参数,并返回一个新的函数 wrapper。在新的函数中,我们记录了函数执行前的时间戳 start,执行了原函数 func(包含 *args**kwargs 参数),并记录了函数执行后的时间戳 end。最后,我们打印出函数名称和执行时间,并返回函数执行结果。

通过在 my_function 函数上加上 @time_it 装饰器语法糖,我们就成功地将 my_function 函数传递给 time_it 函数进行装饰,使得在函数执行前后打印出了执行时间。

1.2 带参数的装饰器

除了上面的示例之外,我们还可以定义带参数的装饰器,具体表示为:

import time

def logger(prefix):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print('{}: {} took {} seconds to run.'.format(prefix, func.__name__, end - start))
            return result
        return inner_wrapper
    return wrapper

@logger('Test')
def my_function():
    time.sleep(1)
    print('Function executed.')

my_function()

上述代码中,我们定义了一个 logger 装饰器,并且这个装饰器接受一个 prefix 参数。在 logger 函数内部,我们再次定义了一个闭包函数 wrapper,接收一个函数 func 作为参数。在 wrapper 函数内部,我们定义了另一个闭包函数 inner_wrapper,并对其进行了类似的封装。此时,我们不仅能够在函数执行前后打印出函数名称和执行时间,还能够增加一个前缀用以区分日志来源。

最后,我们在 my_function 函数上加上 @logger('Test') 装饰器语法糖,使得 my_function 函数包含了日志记录的功能,并且日志前缀设定为 ‘Test’。

二、装饰器高级应用

2.1 类装饰器

除了函数以外,我们也可以使用类作为装饰器。假设我们要统计某个类中所有函数的执行时间,我们可以定义一个 TimeIt 类装饰器:

import time

class TimeIt:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print('{} took {} seconds to run.'.format(self.func.__name__, end - start))
        return result

@TimeIt
def my_function():
    time.sleep(1)
    print('Function executed.')

my_function()

上述代码中,我们定义了一个 TimeIt 类,它接收一个函数作为参数,并定义了 __call__ 方法,使得该对象能够像函数一样进行调用。在调用时,我们记录了函数执行前的时间戳 start,执行了原函数 self.func(包含 *args**kwargs 参数),并记录了函数执行后的时间戳 end。最后,我们打印出函数名称和执行时间,并返回函数执行结果。

通过在 my_function 函数上加上 @TimeIt 装饰器语法糖,我们就成功地将 my_function 函数传递给 TimeIt 类进行装饰,使得在函数执行前后打印出了执行时间。

2.2 堆栈式装饰器

另一个有趣的例子是实现堆栈式装饰器,这种装饰器可以对函数进行嵌套调用,并在函数返回时返回调用堆栈。下面是一个示例代码:

def stack_trace(func):
    def wrapper(*args, **kwargs):
        wrapper.calls.append(func.__name__)
        return func(*args, **kwargs)
    wrapper.calls = []
    return wrapper

@stack_trace
def func_1():
    print('calling func 1')
    func_2()

@stack_trace
def func_2():
    print('calling func 2')

func_1()
print(func_1.calls)

上述代码中,我们定义了一个 stack_trace 装饰器,它接收一个函数 func 作为参数,并返回一个新的函数 wrapper。在新的函数中,我们使用了 wrapper.calls 作为一个列表变量记录了函数的调用堆栈。最后,我们返回了带有堆栈信息的装饰器。

func_1func_2 函数上,我们分别使用了 @stack_trace 装饰器语法糖,使得在函数调用时,函数的调用堆栈会逐步加入到 wrapper.calls 列表中。最后,我们打印出了 func_1 的调用堆栈信息。