Python + =与.extend()在全局变量的函数内

dwa*_*son 5 python methods global operator-keyword

我已经阅读了其他一些SO(PythonScope全局变量不需要全局),但似乎没有任何内容可以像我想的那样明确解释,而且我在精神上筛选PyDocs是否告诉我问题的答案时遇到了麻烦:

myList = [1]

def foo():
    myList = myList + [2, 3]
def bar():
    myList.extend([2, 3])
def baz():
    myList += [2, 3]
Run Code Online (Sandbox Code Playgroud)

现在,可以理解,

>>> foo()
UnboundLocalError
Run Code Online (Sandbox Code Playgroud)

bar()  # works
myList # shows [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

但是之后

>>> baz()
UnboundLocalError
Run Code Online (Sandbox Code Playgroud)

然而,我认为,+=在这种情况下extend(),隐含地称为方法运算符的东西,但错误意味着由于某种原因它实际上不会被+=视为extends().这与Python解析应该如何工作一致吗?

我本以为调用与方法运算符等效的函数,它们在所有情况下都是等价的.相反,它似乎将其视为+=实际的赋值运算符.除此之外,这并不完全正确,因为如果我做了某些事情(诚然做作):

myList = range(50000000) # wait a second or two on my laptop before returning
myList += [0]            # returns instantly
myList = myList + [1]    # wait a second or two before returning
Run Code Online (Sandbox Code Playgroud)

所有这些都是预期的,如果+=实际上只是电话extend().

有没有办法,我失踪,使得它清楚地表明了一些更细的区分(或非常明显的一点...)myListbaz()需要被当作一个局部变量,并因此+=不能被隐式转换为extend()使其认识到全球变量?

jam*_*lak 4

+=不隐式调用extend(). 首先,它是一个增强赋值运算符

如果你看一下上面的部分assignment说:

将对象分配给单个目标的递归定义如下。

如果目标是标识符(名称):

如果该名称未出现在当前代码块的全局语句中:该名称将绑定到当前本地命名空间中的对象。否则:名称绑定到当前全局命名空间中的对象。

由于增强作业是:

增强赋值是单个语句中二元运算和赋值语句的组合:

它遵循相同的规则。如你看到的:

>>> def baz():
        myList += [2, 3]


>>> dis.dis(baz)
  2           0 LOAD_FAST                0 (myList)
              3 LOAD_CONST               1 (2)
              6 LOAD_CONST               2 (3)
              9 BUILD_LIST               2
             12 INPLACE_ADD         
             13 STORE_FAST               0 (myList)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE  
Run Code Online (Sandbox Code Playgroud)

增强赋值对目标(与普通赋值语句不同,不能是解包)和表达式列表求值,对两个操作数执行特定于赋值类型的二元运算,并将结果赋给原始目标。目标仅评估一次。

第一次调用尝试评估myList,这会导致LOAD_FAST由于没有global声明它被假定为局部变量:

LOAD_FAST(var_num)

将本地 引用推co_varnames[var_num]入堆栈。

找不到它,因此引发错误。如果找到那么我们就到达 oppcode INPLACE_ADD,它调用执行扩展工作的方法myList.__iadd__,一旦此操作完成,结果将被分配回变量,但我们永远不会走到这一步。

无论如何,您不应该真正操作globals,从函数返回新结果或将其作为参数传递。