首页 论坛 置顶 《深入探索Python中的作用域、闭包与装饰器》

正在查看 1 个帖子:1-1 (共 1 个帖子)
  • 作者
    帖子
  • #25804

    大多数Python教程解释了作用域、闭包和装饰器是什么。

    今天,我们不打算进行枯燥的讲座,而是要偷偷溜进后台🎭,窥探Python的引擎室🛠️,以一种能够真正留在你脑海中的方式揭示这些概念背后的魔力。

    到最后,你会明白为什么Python感觉像是一种扭曲现实的语言,并且你再也不会以同样的方式看待装饰器。

    准备好你的爆米花🍿,让我们开始吧。


    🎯 第1部分:作用域,变量的藏宝图

    把Python想象成一座巨大的豪宅🏰。每个房间(函数/模块)都有装满贵重物品(变量)的抽屉。

    当你请求x时,Python会按照以下顺序搜索抽屉:

    • 局部(L) → 你的房间(当前函数)。
    • 封闭(E) → 包含当前函数的任何外部函数。
    • 全局(G) → 整个豪宅(模块级命名空间)。
    • 内置(B) → 外部世界(Python的内置函数,如lenprint)。

    这被称为LEGB规则

    👉 示例:

    x = "Global"
    
    def outer():
        x = "Enclosing"
        def inner():
            x = "Local"
            print(x)
        inner()
    
    outer()

    输出:

    本地

    在幕后,Python 构建了一个 作用域链

    内部局部变量 → 外部局部变量 → 模块全局变量 → 内置函数

    如果在这些抽屉中找不到名字呢?💥 NameError

    所以当你遇到 NameError 时,想象一下 Python 疯狂地在大厦里奔跑,喊道:

    “我发誓我检查了每一个抽屉……你的袜子不见了!” 🧦


    🔄 第二部分:nonlocal,变量借用

    通常,在函数内部赋值会创建一个新的局部抽屉。

    但有时你并不想要一个新的,你想借用隔壁房间爸爸的抽屉。这就是 nonlocal 的作用。

    👉 示例:

    def outer():
        message = "Hello"
        def inner():
    
    nonlocal message
    message = "来自内部的问候"
    inner()
    print(message)
    
    outer()

    输出:

    来自内部的问候

    如果没有 nonlocal,Python 会认为你在 inner 中定义了一个全新的变量。而使用 nonlocal,你实际上是在说:

    “不,Python,使用旧的抽屉。我只是借用它。”


    🪢 第三部分:闭包,函数的时间胶囊

    闭包就像时间胶囊背包 🎒。即使一个函数结束,它的内部函数仍可以永远携带一些变量。

    👉 示例:

    def make_multiplier(n):
    
    def multiplier(x):
            return x * n
        return multiplier
    
    times3 = make_multiplier(3)
    times5 = make_multiplier(5)
    
    print(times3(10))  # 30
    print(times5(10))  # 50
    

    即使 make_multiplier 已经运行结束,times3times5 仍然记得它们的 n

    这是怎么做到的呢?闭包!


    🧬 秘密: __closure__cell 对象

    闭包不仅仅是神奇地“记住”变量,它们实际上是将变量存储在单元格中。

    👉 示例:

    def outer():
        x = 42
        def inner():
            return x
        return inner
    
    func = outer()
    print(func())  # 42

    让我们窥探一下幕后:

    print(func.__closure__)
    print(func.__closure__[0].cell_contents)

     

    这意味着什么?

      • __closure__ → 一个单元对象的元组。
      • 每个单元就像一个装着变量的玻璃罐 🫙。
      • cell_contents → 里面的实际内容(这里是 42)。

    闭包字面上是携带变量罐的函数


    多个单元示例

    def outer():
        a = 10
        b = 20
        def inner():
            return a + b
        return inner
    
    func = outer()
    
    for i, cell in enumerate(func.__closure__):
        print(f"单元格 {i}: {cell.cell_contents}")

    输出:

    单元格 0: 10
    单元格 1: 20

    因此,闭包就像是带着两个罐子 🫙🫙 一个装着a,一个装着b


    闭包不复制值

    闭包保持引用,而不是快照。

    def outer():
    x = [1, 2, 3]
    def inner():
        return x
    return inner
    
    func = outer()
    print(func.__closure__[0].cell_contents)  # [1, 2, 3]
    
    func.__closure__[0].cell_contents.append(4)
    print(func())  # [1, 2, 3, 4]
    

    在上述代码中,首先定义了一个列表 `x`,包含元素 1、2 和 3。接着定义了一个内部函数 `inner`,该函数返回变量 `x`。外部函数 `outer` 返回内部函数 `inner`。随后,调用 `outer` 函数并将其结果赋值给 `func`。通过 `print` 语句输出 `func` 的闭包内容,结果为 `[1, 2, 3]`。接下来,向 `func` 的闭包中的 `cell_contents` 列表添加了元素 4,再次调用 `func`,输出结果为 `[1, 2, 3, 4]`。

    看到了吗?这个容器保存了实际的列表,因此当我们更改它时,闭包会注意到。

    这也解释了为什么 nonlocal 有效:它更新现有的容器,而不是创建一个新的。


    🧰 现实生活中的闭包魔法

    1. 记住点击次数的计数器
    def make_counter():
        count = 0
        def counter(): nonlocal count
            count += 1
    
    return count
    return counter
    
    click = make_counter()
    print(click())  # 1
    print(click())  # 2
    1. 函数工厂(专用计算器)。
    2. 数据隐藏(模拟私有变量)。

    闭包 = 内存 + 函数。


    第四部分:装饰器,函数的服装设计师

    如果函数是演员,那么装饰器就是服装设计师 🎭。它们为函数增添额外的行为,而不改变它们的脚本。

    👉 基本装饰器:

    def shout(func):
        def wrapper():
            return func().upper()
        return wrapper
    
    @shout
    def greet():
        return "hello world"
    
    print(greet())  # HELLO WORLD

    在幕后,@shout 的意思是:

    greet = shout(greet)

    所以 greet 实际上是 wrapper。没有黑魔法,只是重新赋值。


    ⏱️ 实用装饰器示例

    1. 计时器
    import time
    
    def timer(func):
        def wrapper(*args, **kwargs):
            start = time.time()
    
    result = func(*args, **kwargs)
    end = time.time()
    print(f"{func.__name__} 耗时 {end-start:.2f}")
    return result
    return wrapper
    
    @timer
    def slow():
        time.sleep(2)
    return "完成"
    
    ()
    1. 记录器(记录函数调用)。
    2. 记忆化(缓存昂贵的结果)。
    3. 安全检查(仅在登录后允许)。
    4. 堆叠装饰器(链式多个装饰)。

    🔮 高级魔法:装饰器工厂与类装饰器

    装饰器也可以接受参数。这就是装饰器工厂

    def repeat(times):
        def decorator(func):
            def wrapper(*args, **kwargs):
                for _ in range(times):
                    func(*args, **kwargs)
            return wrapper
        return decorator
    
    @repeat(3)
    def say_hi():
        print("Hi!")
    

    这段代码定义了一个装饰器 `repeat`,它接受一个参数 `times`,表示要重复调用被装饰函数的次数。装饰器内部定义了一个 `decorator` 函数,该函数又定义了一个 `wrapper` 函数。在 `wrapper` 函数中,使用 `for` 循环根据 `times` 的值多次调用传入的函数 `func`,并将其参数 `args` 和 `kwargs` 传递给 `func`。最后,使用 `@repeat(3)` 装饰器装饰了 `say_hi` 函数,使其在调用时会打印 “Hi!” 三次。

    
    say_hi()

    执行为:

    say_hi = repeat(3)(say_hi)

    我们甚至可以装饰,在它们创建时进行转换,就像给钢铁侠一套全新的战衣 🦾。


    🧠 大局观

      • 作用域 → Python 寻找变量的藏宝图 🗺️(LEGB 规则)。
      • 闭包 → 背包 🎒,在父级消失后仍然携带变量。
      • __closure__ & cell → 实际存储闭包内容的罐子 🫙。
      • 装饰器 → 使用闭包重新连接函数的服装设计师 🎭。

    真正的超能力:Python 中的函数是一级公民。你可以传递它们、存储它们、返回它们并对它们进行包装。其他所有的闭包和装饰器都建立在此基础上。


    🎉 最终篇章

    下次你看到类似的内容时:

    @something
    def do_magic():
    

    记住:

    • Python 只是将你的函数替换为一个包装版本。
    • 那个包装器可能在其闭包中携带了一些变量。
    • 而且 Python 静默地检查了作用域链以使一切正常工作。

    这就是 Python 的真正秘诀:🪄 简单的规则 + 灵活的函数 = 无尽的魔法。

正在查看 1 个帖子:1-1 (共 1 个帖子)
  • 哎呀,回复话题必需登录。