Python:带有就地添加功能的LOAD_FAST与LOAD_DEREF [英] Python: LOAD_FAST vs. LOAD_DEREF with inplace addition

查看:235
本文介绍了Python:带有就地添加功能的LOAD_FAST与LOAD_DEREF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

上周五,我去了一次求职面试,不得不回答以下问题:为什么这段代码会引发异常( UnboundLocalError:分配前引用了本地变量'var'在包含 var + = 1 )的行上?

Last Friday I went to a job interview and had to answer the following question: why does this code raise an exception (UnboundLocalError: local variable 'var' referenced before assignment on the line containing var += 1)?

def outer():
    var = 1

    def inner():
        var += 1
        return var

    return inner

我无法给出正确的答案;这个事实确实让我感到不安,当我回到家时,我非常努力地寻找正确的答案。好吧,我已经找到了答案,但是现在还有其他事情让我感到困惑。

I couldn't give a proper answer; this fact really upset me, and when I came home I tried really hard to find a proper answer. Well, I have found the answer, but now there's something else that confuses me.

我必须提前说我的问题更多是关于设计语言时所做出的决定,而不是它的工作原理。

I have to say in advance that my question is more about the decisions made when designing the language, not about how it works.

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

So, consider this code. The inner function is a python closure, and var is not local for outer - it is stored in a cell (and then retrieved from a cell):

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 时,这种情况会改变内部函数:

This changes when we try to bind something else to var inside the inner function:

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 在本地,这与文档中所说的相符:对名称的分配总是进入最内部的范围

We store var locally, which complies to what is said in the documentation: assignments to names always go into the innermost scope.

现在,当我们尝试增加 var + = 1 时,会出现一个令人讨厌的 LOAD_FAST ,它试图使 var 来自内部的本地范围:

Now, when we try to make an increment var += 1, a nasty LOAD_FAST shows up, which tries to get var from inner's local scope:

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

当然,我们会得到一个错误。现在,这是我不明白的:为什么我们不能用 LOAD_DEREF var c $ c>,然后用 STORE_FAST 将其存储在内部的作用域内吗?我的意思是,这似乎可以与最内在作用域分配相关的内容,同时在某种程度上更直观。至少 + = 代码可以完成我们想要的工作,而我无法解决所描述的方法可能使事情变得混乱的情况。

And of course we get an error. Now, here is what I don't get: why can't we retrieve var with a LOAD_DEREF, and THEN store it inside inner's scope with a STORE_FAST? I mean, this seems to be O.K. with the "innermost scope" assignment stuff, and in the same time it's somewhat more intuitively desirable. At least the += code would do what we want it to do, and I can't come up with a situation in which the described approach could mess something up.

可以吗?我觉得我在这里遗漏了一些东西。

Can you? I feel that I'm missing something here.

推荐答案

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

Python has a very simple rule that assigns each name in a scope to exactly one category: local, enclosing, or global/builtin.

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

(CPython, of course, implements that rule by using FAST locals, DEREF closure cells, and NAME or GLOBAL lookups.)

您修改过的规则确实适用于您的简单案例,但很容易想到这将是模棱两可的(至少对于人类读者而言,如果不是对于编译器而言)。例如:

Your changed rule does make sense for your dead-simple case, but it's easy to come up with cases where it would be ambiguous (at least for a human reader, if not for the compiler). For example:

def outer():
    var = 1

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

    return inner

var + = 1 是否执行 LOAD_DEREF LOAD_FAST ?在知道运行时 spam 的值之前,我们不知道。这意味着我们无法编译函数体。

Does that var += 1 do a LOAD_DEREF or LOAD_FAST? We can't know until we know the value of spam at runtime. Which means we can't compile the function body.

即使您能提出更复杂的规则,感觉上,规则固有的优点就是简单。除了易于实现(因此易于调试,优化等)之外,其他人也很容易理解。当您收到 UnboundLocalError 时,任何中级Python程序员都知道如何通过头脑中的规则来找出问题所在。

Even if you could come up with a more complicated rule that makes sense, there's virtue inherent in the rule being simple. Besides being easy to implement (and therefore easy to debug, optimize, etc.), it's easy for someone to understand. When you get an UnboundLocalError, any intermediate-level Python programmer knows how to work through the rule in his head and figure out what went wrong.

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

Meanwhile, notice that when this comes up in real-life code, there are very easy ways to work around it explicitly. For example:

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

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

You wanted to load the closure variable, and assign to a local variable. There's no reason they need to have the same name. In fact, using the same name is misleading, even with your new rule—it implies to the reader that you're modifying the closure variable, when you really aren't. So just give them different names, and the problem goes away.

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

And that still works with the nonlocal assignment:

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

当然,也有一些技巧,例如使用参数默认值创建以闭包变量的副本开头的局部变量:

Or, of course, there are tricks like using a parameter default value to create a local that starts off with a copy of the closure variable:

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

这篇关于Python:带有就地添加功能的LOAD_FAST与LOAD_DEREF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆