当前位置: 首页 > 面试题库 >

Python:带有就地添加的LOAD_FAST与LOAD_DEREF

宋景福
2023-03-14
问题内容

上周五,我去了一次工作面试,不得不回答以下问题:为什么这段代码会引发异常(UnboundLocalError: local variable 'var' referenced before assignment在包含的行上var += 1)?

def outer():
    var = 1

    def inner():
        var += 1
        return var

    return inner

我不能给出正确的答案。这个事实真的让我感到不安,当我回到家时,我非常努力地寻找正确的答案。好吧,我 已经 找到了答案,但现在有别的东西混淆了我。

我必须事先 说,我的问题更多是关于设计语言时所做出的决定,而不是它的工作方式。

因此,请考虑以下代码。内部函数是一个python闭包,并且var不是局部的outer-它存储在单元格中(然后从单元格中检索):

def outer():
    var = 1

    def inner():
        return var

    return inner

反汇编如下所示:

0  LOAD_CONST               1 (1)
3  STORE_DEREF              0 (var)  # not STORE_FAST

6  LOAD_CLOSURE             0 (var)
9  BUILD_TUPLE              1
12 LOAD_CONST               2 (<code object inner at 0x10796c810)
15 LOAD_CONST               3 ('outer.<locals>.inner')
18 MAKE_CLOSURE             0
21 STORE_FAST               0 (inner)

24 LOAD_FAST                0 (inner)
27 RETURN_VALUE

recursing into <code object inner at 0x10796c810:

0  LOAD_DEREF               0 (var)  # same thing
3  RETURN_VALUE

当我们尝试将其他东西绑定到var内部函数内部时,这种情况会改变:

def outer():
    var = 1

    def inner():
        var = 2
        return var

    return inner

再一次反汇编:

0  LOAD_CONST               1 (1)
3  STORE_FAST               0 (var)  # this one changed
6  LOAD_CONST               2 (<code object inner at 0x1084a1810)
9  LOAD_CONST               3 ('outer.<locals>.inner')
12 MAKE_FUNCTION            0  # AND not MAKE_CLOSURE
15 STORE_FAST               1 (inner)

18 LOAD_FAST                1 (inner)
21 RETURN_VALUE

recursing into <code object inner at 0x1084a1810:

0  LOAD_CONST               1 (2)
3  STORE_FAST               0 (var)  # 'var' is supposed to be local

6  LOAD_FAST                0 (var)  
9  RETURN_VALUE

我们var在本地存储,这与文档中所说的相符: 名称的分配总是进入最内部的作用域

现在,当我们尝试增加时var += 1,会出现一个讨厌的LOAD_FAST东西,它试图varinner的本地范围获取:

14 LOAD_FAST                0 (var)
17 LOAD_CONST               2 (2)
20 INPLACE_ADD
21 STORE_FAST               0 (var)

当然,我们会得到一个错误。现在, 这是我没有得到的
:为什么我们不能使用来检索,然后var用来LOAD_DEREF将其存储在inner范围内STORE_FAST?我的意思是,这对于“内部作用域”赋值似乎是可以的,同时在某种程度上更直观。至少+=代码可以完成我们想要的工作,而我无法提出一种情况,其中所描述的方法可能会使事情变得混乱。

你能?我觉得我在这里错过了一些东西。


问题答案:

Python有一条非常简单的规则,即将作用域中的每个名称分配给一个类别:本地,封闭式或全局/内置。

(当然,CPython通过使用FAST局部变量,DEREF闭合单元以及NAME或GLOBAL查找来实现该规则。)

更改后的规则对于简单的情况确实很有意义,但是很容易提出模棱两可的情况(至少对于人类读者而言,如果不是编译器)。例如:

def outer():
    var = 1

    def inner():
        if spam:
            var = 1
        var += 1
        return var

    return inner

这样var += 1LOAD_DEREFLOAD_FAST吗?直到我们知道spam运行时的值,我们才能知道。这意味着我们不能编译函数体。

即使您可以提出一个更有意义的复杂规则,但该规则的内在优点也很简单。除了易于实现(因此易于调试,优化等)之外,其他人也很容易理解。当您获得时UnboundLocalError,任何中级Pythonhtml" target="_blank">程序员都知道如何在脑海中遍历该规则并找出出了什么问题。

同时,请注意,当在现实生活中的代码中出现这种情况时,有很简单的方法可以明确地解决它。例如:

def inner():
    lvar = var + 1
    return lvar

您想加载闭包变量,并分配给局部变量。他们没有理由需要使用相同的名称。实际上,即使使用新规则,使用相同的名称也会产生误导,这实际上向读者暗示您正在修改闭包变量,而实际上并非如此。因此,只要给他们起不同的名字,问题就解决了。

这仍然适用于非本地分配:

def inner():
    nonlocal var
    if spam:
        var = 1
    lvar = var + 1
    return lvar

或者,当然,还有一些技巧,例如使用参数默认值来创建以闭包变量的副本开头的本地:

def inner(var=var):
    var += 1
    return var


 类似资料: