闭包、装饰器

闭包

闭包的定义

在 Python 中,闭包是一个嵌套函数(内部函数),它能够“记住”并访问其外部(嵌套)函数的作用域中的变量,即使在外部函数已经执行完毕之后。闭包的主要特性是它可以访问定义时的环境中的变量。

闭包的组成

  1. 外部函数:包含外部作用域中的变量。
  2. 内部函数:在外部函数内部定义,并可以访问外部函数的变量。
  3. 自由变量:内部函数引用但并未声明的变量(即外部函数中的变量)。

如何创建闭包

创建闭包的过程一般分为以下几步:

  1. 定义一个外部函数,并在其中定义一个内部函数。
  2. 在外部函数中定义一些变量(自由变量,参数也算),然后在内部函数中访问这些变量。
  3. 外部函数返回内部函数的引用(引用不能带括号)。

闭包的工作机制

闭包的关键在于它所捕获的环境(即外部函数的作用域),即使外部函数已经执行完毕,内部函数仍然能够访问外部函数中的变量。这是通过 Python 的作用域规则实现的。

示例代码

以下是一个示例,用于说明闭包的工作原理:

def outer_function(x):
    # 外部函数的变量
    def inner_function(y):
        # 内部函数可以访问外部函数的变量
        return x + y
    return inner_function

# 创建一个闭包
closure = outer_function(10)

# 调用闭包,输出结果
print(closure(5))  # 输出 15

解析:

  1. 环境保持:当 outer_function 被调用时,变量 x 的值是 10。闭包 inner_function 捕获了这个环境,当我们调用 closure(5) 时,y 是 5,而 x 依然是 10,因此返回的结果是 15。
  2. 绑定:在 closure 被创建时,它并没有执行,实际上只保存了对 inner_function 和它的环境的引用。因此,inner_function 中的 x 是在 outer_function 被调用时的状态。

多个闭包

我们可以创建多个闭包,每个闭包都有自己的外部变量。

closure1 = outer_function(10)
closure2 = outer_function(20)

print(closure1(5))  # 输出 15
print(closure2(5))  # 输出 25

注意事项

  1. 内存管理:由于闭包保持了对外部变量的引用,可能会导致内存无法释放。在不再需要时,要注意手动清理引用。
  2. 作用域解析:当内部函数使用外部函数的变量时,如果在内部函数中重新定义了同名变量,Python 会首先在内部函数的局部作用域中查找该变量,而不是外部作用域。
  3. 性能考量:在某些情况下,频繁创建闭包可能会带来性能开销,特别是在大型程序中,所以需要合理使用。

闭包的使用

1. 数据封装

闭包可以创建私有变量,允许外部函数访问内部变量而不提供直接访问。这种方式类似于类的私有属性,能够防止外部直接修改这些变量。

def make_counter():
    count = 0

    def counter():
        nonlocal count  # 通过 nonlocal 关键字访问外部函数的变量
        count += 1
        return count

    return counter  # 返回内部函数作为闭包

counter1 = make_counter()
print(counter1())  # 输出 1
print(counter1())  # 输出 2

2. 状态保持

闭包可以用来保持某些状态信息,比如计数器、记分板等,这在异步编程或回调函数中非常有用。

def make_counter(initial):
    count = initial

    def counter():
        nonlocal count
        count += 1
        return count

    return counter

counter2 = make_counter(100)
print(counter2())  # 输出 101
print(counter2())  # 输出 102

3. 回调函数

在传递函数作为参数时,闭包可以捕获特定的上下文数据。这对于编写异步代码、事件处理或者回调函数非常有用。

def make_callback(message):
    """创建一个闭包作为回调函数"""
    def callback():
        print(f"回调函数被调用,消息是: {message}")
    return callback  # 返回闭包

def execute_callback(callback):
    """执行传入的回调函数"""
    print("即将执行回调函数...")
    callback()  # 调用回调函数

# 创建一个回调函数,捕获特定的上下文数据(消息)
my_callback = make_callback("Hello from the callback!")

# 将回调函数传递给另一个函数进行执行
execute_callback(my_callback)

代码解析

  1. make_callback 函数:
    • 这个函数接收一个 message 参数,并定义了一个名为 callback 的内部函数。
    • callback 内部函数会打印出捕获的 message。当你调用 make_callback 时,返回这个闭包。
  2. execute_callback 函数:
    • 这个函数接收一个回调函数作为参数,并在调用之前打印提示信息,然后执行回调。
  3. 创建和执行回调:
    • 调用 make_callback("Hello from the callback!") 创建了一个闭包,捕获了字符串。
  4. 执行回调:
    • 将创建的回调函数 my_callback 传递给 execute_callback,当 execute_callback 调用 callback() 时,输出将显示 回调函数被调用,消息是: Hello from the callback!

4. 装饰器

装饰器是闭包的一个重要应用,它允许在不修改被装饰函数的情况下,增强或改变函数的行为。

def decorator(func):
    def wrapper(*args, **kwargs):
        print("Before the function call.")
        result = func(*args, **kwargs)
        print("After the function call.")
        return result
    return wrapper

@decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Alice")

闭包和类

需求:根据配置信息使用闭包实现不同人的对话信息,例如对话:

  • 张三:到北京了吗?
  • 李四:已经到了,放心吧。

类实现:

class Person:
    def __init__(self, name):
        self.name = name  # 初始化名字

    def speak(self, message):
        return f"{self.name}:{message}"  # 返回格式化的对话文本

# 根据不同的人名创建 Person 实例
zhangsan = Person("张三")
lisi = Person("李四")

# 调用对话
print(zhangsan.speak("到北京了吗?"))  # 输出: 张三:到北京了吗?
print(lisi.speak("已经到了,放心吧。"))  # 输出: 李四:已经到了,放心吧。

闭包实现

def create_dialogue(name):
    def dialogue(message):
        return f"{name}:{message}"  # 使用闭包捕获名字
    return dialogue  # 返回内嵌函数

# 根据不同的人名创建对话
zhangsan_dialogue = create_dialogue("张三")
lisi_dialogue = create_dialogue("李四")

# 调用对话
print(zhangsan_dialogue("到北京了吗?"))  # 输出: 张三:到北京了吗?
print(lisi_dialogue("已经到了,放心吧。"))  # 输出: 李四:已经到了,放心吧。

代码分析

  1. 闭包实现
    • create_dialogue 函数创建一个闭包,它接受一个名字作为参数,并返回一个内部函数 dialogue。这个内部函数能引用其外部环境中的变量 name。通过这个方式,我们为不同的人创建了对话接口。
  2. 类实现
    • Person 类通过构造方法初始化实例变量 name,并提供 speak 方法来返回格式化的对话信息。创建不同的 Person 实例可以轻松地模拟不同人的对话。

相同之处与不同之处

  • 相同之处
    • 都能通过参数化的方式创建具有特定名字的对话功能。
    • 使用了封装的理念,将对话逻辑与人物信息关联在一起。
  • 不同之处
    • 闭包是由函数构成的,而类是一个完整的对象模型,具有更多的功能和可扩展性,因为类有继承,更庞大。
    • 类可以轻松扩展其他方法和属性,而闭包则较为简单轻量,适合小型任务。

修改闭包内使用的外部变量

在 Python 中,使用闭包时,外部变量是“封闭”在内部函数中的。如果你想要修改这些外部变量,必须使用 nonlocal 关键字。nonlocal 关键字允许在嵌套函数中修改外部函数的变量。

使用闭包和 nonlocal 修改外部变量

def create_counter():
    count = 0  # 外部变量

    def counter():
        nonlocal count  # 声明要使用外部变量 count
        count += 1  # 修改 count
        return count

    return counter  # 返回内部函数作为闭包

# 创建计数器
my_counter = create_counter()

print(my_counter())  # 输出: 1
print(my_counter())  # 输出: 2
print(my_counter())  # 输出: 3

示例解析

  1. count 变量:这是一个外部变量在 create_counter 函数中定义,并被内部的 counter 函数所引用。
  2. nonlocal 关键字:在 counter 函数内部,我们使用 nonlocal count 声明我们打算修改 count 变量,这样 Python 就知道我们在引用的是 create_counter 函数中的 count,而不是在 counter 函数中创建的一个新的局部变量。
  3. 修改外部变量:通过调用 my_counter(),每次都会增加 count 的值,达到了修改外部变量的目的。

使用 global 修改全局变量的示例

count = 0  # 全局变量

def increment():
    global count  # 声明要使用全局变量 count
    count += 1  # 修改 count
    return count

print(increment())  # 输出: 1
print(increment())  # 输出: 2

global 和 nonlocal 的区别

特性globalnonlocal
作用域用于访问全局作用域的变量用于访问外层(非全局)嵌套函数的变量
使用场景当需要在函数内部修改全局变量时使用当在嵌套函数中需要修改外部函数的局部变量时使用
声明位置只能在函数内部声明,说明该函数会使用全局变量必须在嵌套函数中使用,声明所引用的外部变量
示例global count 可用于修改全局 countnonlocal count 可用于修改外部函数中的 count

变量作用域

在 Python 中,变量的作用域和生存周期通常被归类为四种类型:局部变量(local)、封闭变量(enclosure)、全局变量(global)和内置变量(builtin)。下面是这四种变量的详细介绍。

1. 局部变量(Local)

  • 定义:局部变量是在函数或方法内部定义的变量,只能在该函数或方法内部访问。
  • 生存周期:当函数执行完成后,局部变量会被销毁。
def my_function():
    local_var = 10  # 局部变量
    print(local_var)

my_function()  # 输出: 10
# print(local_var)  # 这行会抛出 NameError,因为 local_var 不在全局作用域内

2. 封闭变量(Enclosure)

  • 定义:封闭变量是定义在外部函数中的变量,被其内部函数(或闭包)所引用。内部函数可以访问外部函数的局部变量。
  • 生存周期:封闭变量的生存周期与外部函数的执行有关;外部函数结束后,这些变量仍然可以被内部函数访问(只要内部函数的引用仍然有效)。
def outer_function():
    enclosure_var = "Hello"  # 封闭变量

    def inner_function():
        print(enclosure_var)  # 访问封闭变量

    return inner_function

closure = outer_function()
closure()  # 输出: Hello

3. 全局变量(Global)

  • 定义:全局变量是在模块级别定义的变量,可以在整个模块的任何地方进行访问和修改。
  • 生存周期:全局变量的生存周期从定义时开始,直到 Python 程序结束时。
global_var = "I am global"  # 全局变量

def my_function():
    print(global_var)  # 访问全局变量

my_function()  # 输出: I am global

如果需要在函数中修改全局变量,需使用 global 关键字。

global_var = 0

def increment():
    global global_var  # 声明要使用全局变量
    global_var += 1

increment()
print(global_var)  # 输出: 1

4. 内置变量(Built-in)

  • 定义:内置变量(或内置对象)是一组预定义的变量和函数,它们在每个 Python 模块中都可以直接访问,无需任何导入。
  • 生存周期:内置变量在 Python 解释器启动时加载,并在整个程序运行期间存在。
  • 示例:Python 的内置函数如 len()print()type() 等,以及一些关键字和常量,如 TrueFalseNone 等。
print(len("Hello"))  # 访问内置函数,输出: 5

装饰器

装饰器的工作原理

装饰器可以看作一个函数,其参数是另一个函数,并且它会返回一个新的函数。可以用@decorator_name的语法来应用装饰器。

基本结构

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()  # 调用被装饰的函数
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

# 调用装饰后的函数
say_hello()

执行过程分解

1、装饰器定义

首先定义了一个装饰器 my_decorator,它接受一个函数 func 作为参数。

定义装饰器

def my_decorator(func):

在装饰器内部,定义了一个新的函数 wrapper,这个函数将包裹原始函数。它的目的是在调用原始函数之前和之后添加一些额外的逻辑。

def wrapper():

2. 装饰器应用

当你使用 @my_decorator 装饰 say_hello 函数时,Python 解释器会执行以下操作:

say_hello 函数作为参数传递给 my_decorator,然后 my_decorator 返回 wrapper 函数。

say_hello = my_decorator(say_hello)

3. 函数调用

当调用 say_hello() 时,实际上调用的是 wrapper() 函数,而不是原始的 say_hello 函数。

say_hello()  # 实际上是调用 wrapper()

4. 进入 wrapper 函数

由于 wrapper 函数没有参数,因此直接进入其主体代码。

5. 执行wrapper函数

在 wrapper 函数的第一行,执行 print("Something is happening before the function is called.")

接下来,执行 func(),此时 func 指的就是原始的 say_hello 函数。

6. 调用被装饰的函数

接下来,执行 func(),此时 func 指的就是原始的 say_hello 函数。

这样实际上是在调用原始的 say_hello 函数:

say_hello()  # 实际调用,打印 print("Hello!")

7. 返回 wrapper

从 say_hello 函数返回到 wrapper,接下来,执行 print("Something is happening after the function is called.")

带参数的装饰器

装饰器在装饰函数时,需要根据被装饰的函数定义的格式来适当的接收参数和返回,所以闭包函数中的内函数也要相应的接收数据和返回数据

函数带参:下面是三个函数带参的示例,对比一下

1. 无参无返回的函数

没有处理参数,也不返回任何东西,只在函数调用前后执行简单操作。

def simple_decorator(func):
    def wrapper():
        print("Before calling the function.")
        func()  # 调用被装饰的函数
    return wrapper

@simple_decorator
def greet():
    print("Hello!")

# 调用装饰后的函数
greet()

2. 有参无返回的函数

这个装饰器可以接收传递给被装饰函数的参数,并调用被装饰的函数。

def print_argument(func):
    def wrapper(*args, **kwargs):  # 这里接收任意参数
        print("Calling function with arguments:", args, kwargs)
        func(*args, **kwargs)  # 调用被装饰的函数,传递参数
    return wrapper

@print_argument
def greet(name):
    print(f"Hello, {name}!")

# 调用装饰后的函数
greet("Alice")   # Hello, Alice!

3. 有参有返回的函数

这个装饰器接受被装饰函数的参数,并在调用该函数后对返回值进行一些修改。

def return_decorator(func):
    def wrapper(*args, **kwargs):  # 接收任意参数和关键字参数
        result = func(*args, **kwargs)  # 调用被装饰的函数
        return f"Decorated: {result}"  # 修改并返回结果
    return wrapper

@return_decorator
def greet(name):
    return f"Hello, {name}!"  # 返回问候

# 调用装饰后的函数
result = greet("Alice")
print(result)  # 输出:Decorated: Hello, Alice!

4. 结论:

通用装饰器固定格式:

def outer(func):
    def inner(*args, **kwargs):  # 接收任意参数和关键字参数
        result = func(*args, **kwargs)
        return result
    return inner

装饰器带参:需要嵌套三层。下面是装饰器带参器示例

1、有参无返回的装饰器

这个装饰器接受一个参数,并且没有返回值,主要用于修改被装饰的函数的行为。

def param_decorator(param):
    # 外层函数,接受装饰器参数
    def decorator(func):
        # 内层函数,接受被装饰的函数
        def wrapper():
            # 在包装函数中使用装饰器参数
            print(f"装饰器参数: {param}")
            func()  # 调用被装饰的函数
        return wrapper  # 返回包装函数
    return decorator  # 返回内层函数

@param_decorator("测试参数")
def say_hello():
    print("你好!")

# 调用被装饰的函数
say_hello()

这种嵌套结构允许我们在装饰器中使用参数,同时保持原始函数的功能。通过外层的接受参数函数,和内层的实际装饰逻辑函数,我们能够在运行时提供更灵活的扩展和功能。这也是 Python 装饰器设计中的一个常见模式。

执行步骤

1. 调用外层装饰器

首先,外层函数 param_decorator 被调用,并传入参数 "测试参数"。这一步的结果是返回内层的 decorator 函数。

decorator = param_decorator("测试参数")

2. 调用内层装饰器

接着,Python 会将被装饰的函数 say_hello 传递给内层函数 decorator

say_hello = decorator(say_hello)

此时,decorator 函数接受 say_hello 作为参数,并返回 wrapper 函数。因此,最终,say_hello 的引用被替换成了 wrapper 函数:

say_hello = wrapper

2、. 有参有返回的装饰器

以下是一个带参数的装饰器的示例:

def repeat(num_times):  # 第一个函数,接受装饰器参数
    def decorator_repeat(func):  # 第二个函数,接受被装饰的函数
        def wrapper(*args, **kwargs):  # 第三个函数,接受任意参数
            for _ in range(num_times):  # 根据参数 num_times 重复执行
                func(*args, **kwargs)  # 调用原函数,传递参数
        return wrapper  # 返回包装函数
    return decorator_repeat  # 返回装饰器函数

@repeat(3)  # 使用装饰器装饰 greet 函数,重复 3 次
def greet(name):
    print(f"Hello, {name}!")

# 调用装饰后的函数
greet("Alice")

上述代码中,@repeat(3) 将 greet 装饰为每次调用时打印三次问候。

多个装饰器的使用

在 Python 中,可以将多个装饰器应用于同一个函数。多个装饰器将按照从内到外的顺序依次执行,每个装饰器都装饰前一个装饰器返回的函数。这种方式可以很方便地组合不同的功能。

以下是一个使用多个装饰器的示例:

def decorater1(func):
    def wrapper(*args, **kwargs):
        print("装饰器 1: 函数执行前的操作")
        result = func(*args, **kwargs)
        print("装饰器 1: 函数执行后的操作")
        return result
    return wrapper

def decorater2(func):
    def wrapper(*args, **kwargs):
        print("装饰器 2: 函数执行前的操作")
        result = func(*args, **kwargs)
        print("装饰器 2: 函数执行后的操作")
        return result
    return wrapper

@decorater1
@decorater2
def my_function():
    print("这是 my_function 的主体部分")

# 调用函数
my_function()
闭包、装饰器

可以看到,装饰器是从内向外依次应用的。首先是 decorater2,然后是 decorater1

类似于函数嵌套调用的过程

类装饰器

类装饰器是 Python 中的一种装饰器,它使用一个类来包装一个函数或方法。这种装饰器的主要好处是可以利用类的状态和方法来扩展被装饰的功能。与传统的函数装饰器不同,类装饰器能够保持状态,具有更多的灵活性。

类装饰器的结构

  1. 定义一个类,该类实现 __call__ 方法。
  2. 在 __init__ 方法中,可以初始化一些参数或状态。
  3. __call__ 方法实际上是放在装饰的函数(或方法)上的功能。

示例:

class MyDecorator:
    def __init__(self, func):
        self.func = func  # 保存被装饰的函数

    def __call__(self, *args, **kwargs):
        print("装饰器开始执行")
        result = self.func(*args, **kwargs)  # 调用被装饰的函数
        print("装饰器结束执行")
        return result

@MyDecorator
def say_hello(name):
    print(f"你好, {name}!")

# 调用被装饰函数
say_hello("Alice")

解释

  1. 如果使用的是一个类实现的装饰器,那么该装饰器执行完成后,被装饰函数指向该类的实例对象 MyDecorator 是一个类,它接收一个函数作为参数,并将其保存在 self.func 中。
  2. __call__ 方法定义了当实例被调用时的行为。正是因为 Python 中的类可以实现 __call__ 方法,使得类的实例可以像函数一样被调用。当你在 Python 中创建一个类并定义了 __call__ 方法后,你的类的实例就成为了一个可调用对象。
    在本例中,它在调用被装饰的函数前后打印了提示信息。
  3. say_hello 函数被 MyDecorator 类装饰,调用时先执行 __call__ 方法,进而调用原始的 say_hello

装饰器使用场合

装饰器常用于以下场景:

  1. 日志记录:在函数调用前后记录日志。
  2. 权限检查:在执行某些功能之前检查用户权限。
  3. 测量性能:记录函数执行的时间。
  4. 缓存结果:在调用时缓存结果,以提高性能。

1. 日志记录

这个装饰器在函数调用前后记录日志信息。

def log_func(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function '{func.__name__}' with arguments: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function '{func.__name__}' returned {result}")
        return result
    return wrapper

@log_func
def add(a, b):
    return a + b

# 调用示例
add(3, 5)

输出:

Calling function 'add' with arguments: (3, 5), {}
Function 'add' returned 8

2. 权限检查

这个装饰器检查用户是否具备调用某个功能的权限。

def requires_permission(permission):
    def decorator(func):
        def wrapper(user_permissions):
            if permission in user_permissions:
                return func()
            else:
                return "Access Denied"
        return wrapper
    return decorator

@requires_permission('admin')
def delete_user():
    return "User deleted."

# 示例
print(delete_user(['user']))  # 输出: Access Denied
print(delete_user(['admin']))  # 输出: User deleted.

3. 测量性能

这个装饰器记录函数执行所需的时间。

import time

def time_it(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function '{func.__name__}' executed in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@time_it
def compute_sum(n):
    return sum(range(n))

# 调用示例
compute_sum(10**6)

4. 缓存结果

这个装饰器在第一次调用函数时缓存结果,以提高性能。

def cache(func):
    memo = {}
    def wrapper(*args):
        if args in memo:
            return memo[args]  # 返回缓存结果
        result = func(*args)
        memo[args] = result  # 缓存计算结果
        return result
    return wrapper

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

# 调用示例
print(fibonacci(30))  # 输出: 832040

5. 参数化路由

装饰器常常用于 Web 框架(如 Flask 和 Django)中的路由定义。装饰器在路由中的应用主要是为了简化视图函数的注册、权限控制、请求处理等。这使得开发者能够更加优雅地组织代码,并在函数开始执行时自动应用相关的逻辑。

下面是使用 Flask 框架的一个简单示例,其中装饰器用于定义路由:

from flask import Flask

app = Flask(__name__)

@app.route('/')  # 装饰器用于定义路由
def home():
    return "欢迎来到首页!" 

@app.route('/about')
def about():
    return "这是关于页面!"

if __name__ == '__main__':
    app.run(debug=True)

@app.route('/')

  • 这里的 @app.route('/') 是一个装饰器,它将 home 函数注册为处理根路径 / 的路由。
  • Flask 会在背景中处理这个装饰器,确保当访问该 URL 时,home 函数将被调用。

6. 中间件

除了基本的路由,装饰器还可以用于实现请求前置处理、权限检查等中间件功能。例如:

from flask import request, abort

def require_auth(f):
    def wrapper(*args, **kwargs):
        if not request.headers.get('Authorization'):
            abort(401)  # 未授权
        return f(*args, **kwargs)
    return wrapper

@app.route('/protected')
@require_auth  # 应用自定义装饰器
def protected():
    return "访问受保护的资源!"

6. Django 中的示例

在 Django 框架中,装饰器同样被广泛使用,特别是在视图函数中:

from django.http import HttpResponse
from django.views.decorators.http import require_GET

@require_GET  # 装饰器限制请求为 GET 请求
def my_view(request):
    return HttpResponse("这是一个视图!")

发布者:LJH,转发请注明出处:https://www.ljh.cool/42971.html

Like (0)
LJHLJH
Previous 2025年7月2日 下午4:33
Next 4天前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注