Python装饰器模式介绍、使用方法

一、Python装饰器模式介绍

Python装饰器模式(Decarator Pattern)是一种编程模式,它允许我们在不修改现有代码的情况下为已有函数添加新的功能。在Python中,装饰器模式通常使用@符号来表示。装饰器本质上是一个高阶函数,它可以接受一个函数作为参数,返回一个新的函数作为结果。这个新函数拥有了原函数的功能,同时还添加了装饰器的功能。

装饰器模式的主要功能是增强已有函数的功能,例如添加日志、计时、缓存等功能。这样可以提高代码的复用性和可维护性。

优点:

  1. 增强已有函数的功能,提高代码的复用性和可维护性;
  2. 不修改原函数的代码,避免了可能引入的新问题。

缺点:

  1. 由于装饰器本质上是一个函数,因此会产生额外的函数调用开销;
  2. 如果装饰器过多,会导致代码的可读性降低。

应用场景:

  1. 缓存:可以为一些需要大量计算的函数添加缓存功能,减少函数的计算时间;
  2. 日志:可以为一些需要记录日志的函数添加日志功能;
  3. 计时:可以为一些需要计时的函数添加计时功能。

使用方式:

  1. 直接使用@符号将装饰器函数放在需要装饰的函数上方,例如@decorator_func;
  2. 可以使用装饰器函数对需要装饰的函数进行包装,然后手动调用包装后的函数。

在应用程序开发中,装饰器模式经常用于日志、缓存、权限控制等方面。例如,可以为需要进行权限控制的函数添加一个装饰器函数,如果用户没有相应的权限,就会进行拦截。此外,它还可以用于在运行时动态修改函数的行为。

二、装饰器模式的使用

工作原理

        利用Python的函数闭包特性,在运行装饰器函数时,将原函数作为参数传递给装饰器函数。

装饰器函数内部定义一个新函数来包装原函数,并在新函数中添加装饰器的功能。

最后返回新函数,替代原函数。当调用原函数时,实际上调用的是新函数。

此时,新函数会首先执行装饰器的功能,然后再执行原函数的功能。

示例一:函数添加计时功能

假设有一个函数 add,其功能是将两个数字相加并返回结果。现在需要给这个函数添加计时功能,记录函数执行时间,可以使用装饰器模式实现。

装饰器函数示例代码如下:

这个装饰器函数接收一个函数作为参数,返回一个新的函数,用于包装原函数并添加计时功能。

使用装饰器函数对 add 函数进行装饰:

现在,调用 add 函数时,会自动执行装饰器函数中的计时功能。

import time

# 定义装饰器函数
def time_decarator(func): # 接收函数作为参数
    # *args 和 **kwargs 分别表示接受不定数量的位置参数和关键字参数;
    # 当函数的参数前面带有一个 * 号时,表示将多个参数打包成一个元组传入函数;
    # 当参数前面带有两个 * 号时,表示将多个参数打包成一个字典传入函数。
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to execute.")
        return res
    return wrapper # 返回新的函数:包装原函数并添加计时器

@time_decarator
def add(x, y):
    return x + y

print(add(1, 2))

运行结果:

Function add took 0.0 seconds to execute.
3

以上代码的工作原理是,使用 @ 符号将装饰器函数 timing_decorator 放在 add 函数定义之前。这样,在调用 add 函数时,会先执行装饰器函数 timing_decorator,返回一个新的函数 wrapper,这个新函数会记录函数的执行时间,并调用原函数 add 来实现加法操作。最后,新函数 wrapper 返回计算结果,并输出函数执行时间。

如果不使用 @ 符号,可以直接在调用函数时手动进行函数装饰,示例代码如下:

def add(x, y):
    return x + y

add = time_decorator(add)

这样,调用 add 函数时会先执行装饰器函数,返回一个新函数用于包装原函数并添加计时功能。

示例二:函数添加日志功能

使用 Python 装饰器模式可以方便地实现函数添加日志功能,具体实现步骤如下:

        1. 定义装饰器函数 logger,该函数将会接收一个函数作为参数,然后返回一个新函数。定义了一个名为 logger 的装饰器函数,该函数接收一个函数 func 作为参数,并返回一个新的函数 wrapper

        2. 在需要添加日志功能的函数上方添加 @logger 装饰器。将 logger 装饰器应用于 my_func 函数上方,表示对该函数添加日志功能。

        3. 调用 my_func 函数,查看输出结果。

# 定义装饰器函数,实现添加日志功能
def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Function {func.__name__} with args: {args} and kwargs {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@logger
def my_func(a, b):
    return a - b

print(my_func(2, 1))

在上面的代码中,我们调用了 my_func 函数,并传入了两个参数 2和 1。由于 my_funclogger 装饰器修饰,因此在函数执行前会先输出一条日志,记录调用的函数名及其参数,然后再执行函数并返回结果。最后,我们将函数的返回值打印出来,输出结果为:

Function my_func with args: (2, 1) and kwargs {}
1

从输出结果可以看出,函数 my_func 被成功修饰,成功添加了日志功能。

示例三:函数添加缓存功能

使用 Python 装饰器模式可以方便地实现函数添加缓存功能,可以有效地提高程序的执行效率。具体实现步骤如下:

        1. 定义装饰器函数 cache,该函数将会接收一个函数作为参数,然后返回一个新函数。

定义了一个名为 cache 的装饰器函数,该函数接收一个函数 func 作为参数,并返回一个新的函数 wrapper。在 wrapper 函数中,我们使用了一个字典 cache_map 来作为缓存,记录之前已经执行过的函数结果。具体实现逻辑如下:

  • 首先生成缓存的 key,key 的生成方式由传入的参数决定;
  • 检查缓存中是否已经有值,如果有,则直接返回缓存中的值;
  • 如果缓存中没有值,则调用函数,将结果存入缓存并返回。

        2. 在需要添加缓存功能的函数上方添加 @cache 装饰器。

cache 装饰器应用于 my_func 函数上方,表示对该函数添加缓存功能。

        3. 调用 my_func 函数多次,查看输出结果。

# 定义装饰器函数,实现添加缓存功能
def cache(func):
    cache_map = {}
    def wrapper(*args, **kwargs):
        # 生成缓存的key
        key = str(args) + str(kwargs)

        # 查询缓存中是否已经有值,如果有,则直接返回缓存中的值
        if key in cache_map:
            return cache_map[key]

        # 如果缓存中没有值,则调用函数,将结果存入缓存并返回
        res = func(*args, **kwargs)
        cache_map[key] = res
        return res
    return wrapper

@cache
def my_func(a, b):
    return a + b

r1 = my_func(1,2)
r2 = my_func(1,2)
r3 = my_func(3,4)
print(r1,r2,r3)

在上面的代码中,我们多次调用 my_func 函数,并传入不同的参数。由于 my_funccache 装饰器修饰,因此在函数执行前会先检查缓存是否存在相应的值,如果存在,则直接返回缓存中的值,否则执行函数并将结果存入缓存。最后,我们将函数的返回值打印出来,输出结果为:

3 3 7

从输出结果可以看出,函数 my_func 被成功修饰,成功添加了缓存功能。

第一次调用 my_func(1,2) 时,函数会执行并将结果 3 存入缓存;

第二次调用 my_func(1,2) 时,函数直接从缓存中取出结果 3 并返回;

第三次调用 my_func(3,4) 时,由于参数不同,函数会重新执行并将结果 7 存入缓存。

三、其他

*args 和 **kwargs参数说明

在 Python 中,*args**kwargs 分别表示接受不定数量的位置参数和关键字参数。

  • 当函数的参数前面带有一个 * 号时,表示将多个参数打包成一个元组传入函数;
  • 当参数前面带有两个 * 号时,表示将多个参数打包成一个字典传入函数。

在函数调用时,可以将一些额外的参数传递给这个函数,这些参数会被打包成一个元组和字典传入函数中,可以在函数内部访问和处理这些参数。

下面是一个使用 *args**kwargs 的示例:

def my_func(*args, **kwargs):
    print(args)
    print(kwargs)

my_func(1, 2, a=3, b=4)

输出结果为:

(1, 2)
{'a': 3, 'b': 4}

在上面的示例中,函数 my_func 接受了不定数量的位置参数 *args 和不定数量的关键字参数 **kwargs。在调用函数 my_func 时,传入了两个位置参数 12,以及两个关键字参数 a=3b=4。在函数内部,args 参数将被打包为一个元组 (1, 2),而 kwargs 参数将被打包为一个字典 {'a': 3, 'b': 4}

猜你喜欢

转载自blog.csdn.net/songpeiying/article/details/131920435