Python中的 LOAD_DEREF & LOAD_CLOSURE

LOAD_DEREF

在Python(特别是CPython实现)的字节码指令集中,LOAD_DEREF 是一个操作码,用于从函数的闭包(如果存在)或从当前函数的局部作用域外的命名空间(enclosing scope)中加载一个变量。

这一点在处理嵌套作用域和闭包(closures)时特别重要。当一个内部函数引用了一个在外部函数中定义的变量,这个变量就会被视为一个“自由变量”(free variable)。LOAD_DEREF 指令用于在内部函数中加载这种自由变量。

假设我们有以下Python代码:

def outer():
    x = 10
    def inner():
        print(x)
    return inner

func = outer()
func()

在这里,inner 是一个闭包,因为它引用了外部作用域(即 outer 函数)中的变量 x。当 inner 被调用时,它需要能够访问 x,即使 outer 的执行已经完成。这就是 LOAD_DEREF 指令发挥作用的地方。

如果我们用Python的 dis 模块来反汇编 inner 函数,我们可能会看到类似以下内容的字节码:

  5           0 LOAD_GLOBAL              0 (print)
              2 LOAD_DEREF               0 (x)
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

注意在第2行中的 LOAD_DEREF 指令。这个指令加载了在 inner 函数外部定义的变量 x

LOAD_CLOSURELOAD_FASTLOAD_GLOBAL 等其他字节码指令一样,LOAD_DEREF 是CPython中的实现细节,并不是Python语言规范的一部分。不同的Python实现(比如PyPy, Jython等)可能会有不同的方式来实现相同的功能。

LOAD_CLOSURE

在Python的字节码层面,LOAD_CLOSURE 是一个操作码(opcode)用于实现闭包(closures)的功能。这个操作码负责从当前函数的闭包(closure)中加载一个自由变量(free variable)。

Python中的函数可以引用定义在它们外部作用域的变量。当一个函数引用了这样的外部变量并且被当做对象返回,这个函数就被称为闭包。这些外部作用域的变量称为自由变量。

LOAD_CLOSURE 指令在函数的字节码中出现,用于从该函数的闭包中获取一个自由变量,并将其压入操作数栈顶。这使得该函数能在稍后执行时访问这个自由变量。

举个简单的例子:

def outer(x):
    def inner(y):
        return x + y
    return inner

# inner 函数是一个闭包,它引用了 outer 函数的局部变量 x

在这个例子中,inner 函数是一个闭包,因为它访问了外部作用域(outer 函数)的变量 x。如果你查看 inner 函数的字节码(通过内置的 dis 模块),你可能会看到 LOAD_CLOSURE 指令,用于加载 x 这个自由变量。

要注意的是,Python字节码并非Python语言规范的一部分,而是CPython解释器(Python的一个实现)的内部细节。因此,其他Python实现(如PyPy、Jython等)可能没有 LOAD_CLOSURE 这一操作码,或者实现闭包的方式可能不同。

LOAD_CLOSURE和LOAD_DEREF的区别

LOAD_CLOSURELOAD_DEREF 都是与Python(特别是CPython实现)中的闭包和自由变量相关的字节码指令,但它们在闭包构建和运行时具有不同的用途。

LOAD_CLOSURE

  • 用途: 当你创建一个闭包(也就是说,一个内部函数引用了外部作用域中的一个或多个变量)时,LOAD_CLOSURE 被用于获取这些外部变量,也称为自由变量。
  • 时机: 这个指令通常在闭包函数对象被创建时执行。
  • 功能: 它将自由变量加载到闭包对象的环境中,以便稍后使用。

LOAD_DEREF

  • 用途: 在闭包内部运行时,当需要访问自由变量时,使用 LOAD_DEREF 指令。
  • 时机: 这个指令在闭包被调用并需要访问一个或多个自由变量时执行。
  • 功能: 它从闭包对象的环境中取出自由变量,并将其值压入操作数栈顶,以便在闭包内部进行其他操作(例如,运算或函数调用)。

简单来说,LOAD_CLOSURE 在闭包创建时建立自由变量的环境,而 LOAD_DEREF 在闭包运行时从这个环境中读取自由变量。

例子

考虑以下代码:

def outer(x):
    def inner(y):
        return x + y
    return inner

在这里,inner 是一个闭包,它引用了外部(outer)的变量 x

  • inner 被定义(即创建闭包)时,LOAD_CLOSURE 指令将用于捕获 x 的当前值或引用,并将其存储在闭包的环境中。
  • inner 被调用并执行 return x + y 时,LOAD_DEREF 指令用于从闭包的环境中获取 x 的值。

这样,即使 outer 的执行已经完成,inner 仍然可以访问 x 的值。这就是闭包如何“记住”其外部变量的环境的基本机制。

请注意,这些都是CPython的实现细节,并不是Python语言规范的一部分。其他Python实现可能有不同的方式来实现这些功能。

疑问

为什么在 inner 被定义(即创建闭包)时,LOAD_CLOSURE 指令捕获了 x,在调用并执行 return x + y 时,LOAD_DEREF 指令还要从闭包的环境中再获取一次 x 的值呢 ?

回答

在Python中创建和调用闭包时,LOAD_CLOSURELOAD_DEREF 的分离主要是为了满足两个不同阶段的需求:

创建闭包时的需求(LOAD_CLOSURE

  1. 环境捕获:闭包需要“捕获”它引用的自由变量的当前环境。这样即使外部函数(如 outer)的执行上下文已经消失,闭包仍然能够访问这些变量。

  2. 存储状态:使用 LOAD_CLOSURE 指令,解释器将自由变量(如 x)与闭包(inner)关联,并存储在一个特殊的数据结构中,通常称为“单元”(cell)。

调用闭包时的需求(LOAD_DEREF

  1. 动态查找:当闭包(inner)被调用时,它可能需要基于最新的变量状态进行操作。使用 LOAD_DEREF,闭包可以动态地从其存储的环境中获取自由变量的当前值。

  2. 运行时需求:闭包的执行逻辑可能依赖于这些自由变量,所以在运行时需要加载这些变量。LOAD_DEREF 指令从闭包的环境中取出这些变量,并将其值压入操作数栈以进行后续操作。

为什么需要两步?

简而言之,LOAD_CLOSURELOAD_DEREF 分离成两个步骤主要是为了灵活性和动态性。

  • 灵活性:这种设计允许闭包在其创建之后的任何时间点被调用,而不管其外部环境是否还存在。这是通过在闭包创建时捕获和存储自由变量的环境来实现的。

  • 动态性:Python是一种动态语言,这意味着在运行时可以进行很多操作,包括但不限于动态地改变变量的值。LOAD_DEREF 允许闭包在每次调用时都能够访问其自由变量的最新值。

这两个指令共同允许Python闭包在运行时保持对外部作用域(enclosing scope)变量的实时访问,同时也保留了这些变量的状态,即使它们的原始作用域已经不存在。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青衫客36

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值