Python学习之装饰器(一)

✨ 众所周知,Python装饰器是一种常见的元编程特性,它提供了一种方便的方法来修改或增强现有函数的行为,而不需要修改函数的源代码,保持代码的可读性和可维护性。

1. 先了解一下闭包

🔊 闭包概念:在一个内部函数中,对外部作用域的变量进行引用,并且外部函数的返回值为内部函数,那么内部函数就叫做闭包。

示例:

#🌾【外层函数】
def outer_func(year):
    #内层函数
    def inner_func(month):
        print(f'year:{year}, month:{month}')
    return inner_func

#🌾 调用外层函数
closure_func = outer_func('2023')
closure_func (12)

#🌾【输出结果】:year:2023, month:12

调用func时,产生了闭包inner_func,其持有外层函数的自由变量year,当函数func的生命周期结束之后,year这个变量依然存在,因为它被闭包引用了,不会被回收。

🔊 闭包的特点:
内部函数可以读取外层函数内的局部变量,并让其常驻内存,不会被回收,所以注意内存泄漏

2. 什么是装饰器模式

装饰器模式是一种允许在运行时动态地改变对象或类功能的技术。在Python中,装饰器实际是一种特殊的函数,装饰器函数接收一个函数作为参数,并返回一个修改后的函数,新函数具有与原始函数相同的名称和参数。在调用原始函数之前或之后,可执行一些额外的操作,如计时、日志记录或修改参数等。

下面是一个简单的装饰器示例,它用于在函数调用前后显示信息。

def add_info(func):
    def wrapper(*args, **kwargs):
        print(f"Call function {func.__name__} with args {args} and kwargs {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} returned {result}")
        return result
    return wrapper

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

# 调用add函数
print(add(3, 5))

输出结果

Call function add with args (3, 5) and kwargs {}
Function add returned 8
8

在上述示例中,add_info是一个装饰器函数,它定义了一个名为wrapper的嵌套函数。在调用被装饰的函数add时,实际上会先执行add_info函数,并传入add函数作为参数,然后将其返回的wrapper函数作为修改后的add函数使用。在wrapper函数内部,我们首先打印了一些调试信息,然后通过func(*args, **kwargs)调用原始函数,并返回其返回值。因此,在调用add函数时,我们会对其输入输出进行了一些额外的监测和记录。

3.复合装饰器

除了上述示例中展示的基本装饰器模式之外,Python装饰器还有一些其他的高级用法,例如多个装饰器的复合、装饰器的参数传递、类装饰器等。下面再来看一下例子:

#🌾:多个装饰器的复合
#1. 运行后,greet函数被传进来,先通过hello装饰函数,按我们想要的效果装饰包裹一下;
def hello(func):
    def wrapper(*args, **kwargs):
        print("Hello,")
        return func(*args, **kwargs)
    return wrapper

#2. 接受hello()装饰器函数,装饰包裹后的函数,然后通过world装饰函数,按我们想要的效果再装饰包裹一下;
def world(func):
    def wrapper(*args, **kwargs):
        print("World!")
        return func(*args, **kwargs)
    return wrapper

@hello
@world
def greet():
    print("How are you?")

#🌾:调用此时被"@hello","@world" 装饰包裹后的函数,这时的greet()函数体,不再是你看的greet()函数体了。
greet()

通过上述例子,我们可以实现使用多个不同的装饰器来组成函数的装饰链,每个装饰器负责一项特定的任务。

4.装饰器参数传递

我们还可以向装饰器传递参数,使其更加灵活,以适应不同的使用场景。

#🌾:1. 装饰器参数传递
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

#🌾:2. 装饰自定义函数
@repeat(3)
def hi(name):
    print(f"Hi, {name}")

#🌾:3. 调用被装饰的自定义函数
hi("Alice")

输出:

Hi, Alice
Hi, Alice
Hi, Alice

在上述示例中,我们定义了一个装饰器工厂repeat,它接收一个times参数,并返回一个新的装饰器decorator。在decorator中,我们使用for循环来执行被装饰函数3次。这样,我们就可以为同一个函数应用多个不同的装饰器,以实现更加灵活的装饰器功能。

5.类装饰器

我们还可以使用类装饰器来实现更复杂的装饰器逻辑,除了上面介绍的函数装饰器,我们还可以将装饰器实现为类。实现装饰器类时,我们需要在该类中实现__init__()__call__()方法。

#🌾:类装饰器
class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Do something before function execution")
        result = self.func(*args, **kwargs)
        print("Do something after function execution")
        return result

#🌾 装饰自定义函数
@MyDecorator
def my_func():
    print("My function")

#🌾 调用被装饰的自定义函数
my_func()

输出

Do something before function execution
My function
Do something after function execution

在上述示例中,我们定义了一个类MyDecorator来实现装饰器,其中__init__()方法用于接收被装饰的函数my_func__call__()方法用于实现装饰器的逻辑。与函数装饰器类似,我们可以在__call__()方法中执行一些额外的操作,例如打印调试信息、记录日志等。

5. 装饰器的实际用途

装饰器可用于许多实际用途,比如:

  • 计时:测量函数执行时间;
  • 日志记录:记录函数调用时的日志信息;
  • 缓存:缓存函数的结果,以避免重复计算;
  • 输入验证:检查函数参数是否符合预期。

以下是几个具体的示例。

🐹 1、用装饰器计时函数执行时间:

#🌾:导入三方模块
import time

#🌾:定义装饰器函数
def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper

#🌾:装饰自定义函数
@timer
def slow_function():
    time.sleep(2)

#🌾:调用被装饰器定义的函数
slow_function()

输出:

#输出:
slow_function took 2.005126953125 seconds to run.

🐹 2. 用装饰器记录函数调用信息:

#🌾:定义装饰器函数
def logger(func):
    import logging
    loggingigasicConfig(filename="log.txt", level=logging.INFO)

    def wrapper(*args, **kwargs):
        logging.info(f"Function {func.__name__} was called with args={args} and kwargs={kwargs}.")
        result = func(*args, **kwargs)
        logging.info(f"Function {func.__name__} returned {result}.")
        return result
    return wrapper

#🌾:装饰自定义函数
@logger
def add(a, b):
    return a + b

#🌾:调用被装饰函数
add(3, 5) 

这个例子中,我们在wrapper函数中使用Python的日志模块记录函数调用信息和返回结果。我们可以在一个名为log.txt的文件中看到输出。

🐹 3. 用装饰器实现输入验证:

#🌾:定义装饰器函数
def validate_inputs(func):
    def wrapper(*args, **kwargs):
        for arg in args:
            if noteisinstance(arg, int):
                raise TypeError("All arguments must be integers.")
        for value in kwargs.values():
            if noteisinstance(value, int):
                raise TypeError("All keyword arguments must be integers.")
        return func(*args, **kwargs)
    return wrapper

#🌾:装饰自定义函数
@validate_inputs
def sum_numbers(a, b, c):
    return a + b + c

#🌾:调用
sum_numbers(1, 2, 3)
sum_numbers("1", "2", c=3)  # This line will raise a TypeError.

这个装饰器用于验证函数的输入参数是否符合预期。如果有任何参数不是整数,装饰器将抛出TypeErrors异常。

posted on 2024-12-08 16:51  梁飞宇  阅读(45)  评论(0)    收藏  举报