Python+;=与全局变量上的函数内部的.extend()比较

Python+;=与全局变量上的函数内部的.extend()比较,python,methods,global,operator-keyword,Python,Methods,Global,Operator Keyword,我读过一些其他的SO(和),但似乎没有什么能像我想的那样明确地解释,我在精神上难以筛选是否告诉我问题的答案: myList = [1] def foo(): myList = myList + [2, 3] def bar(): myList.extend([2, 3]) def baz(): myList += [2, 3] 现在,可以理解 >>> foo() UnboundLocalError: local variable 'myList' r

我读过一些其他的SO(和),但似乎没有什么能像我想的那样明确地解释,我在精神上难以筛选是否告诉我问题的答案:

myList = [1]

def foo():
    myList = myList + [2, 3]
def bar():
    myList.extend([2, 3])
def baz():
    myList += [2, 3]
现在,可以理解

>>> foo()
UnboundLocalError: local variable 'myList' referenced before assignment

但是

>>> baz()
UnboundLocalError: local variable 'myList' referenced before assignment
然而,我认为像
+=
这样的东西隐式地调用了方法操作符,在本例中是
extend()
,但错误意味着出于某种原因,它实际上没有将
+=
视为
extends()
。这与Python解析应该如何工作一致吗

我本以为调用与方法操作符等价的函数,它们在所有情况下都是等价的。相反,它似乎将
+=
视为实际的赋值运算符。不过,这并不是完全正确的,因为如果我做了什么(当然是做作的):

如果
+=
实际上只是调用
extend()
,那么所有这些都是预期的


是否有更细微的区别(或非常明显的点…)让我清楚地知道
baz()
中的
myList
需要被视为局部变量,因此
+=
不能隐式转换为
extend()
这样它就可以识别全局变量了?

当您改变列表时,应该说是全局myList。我所说的变异是指改变引用。第一个示例和第三个示例基本相同,您只需使用+=作为速记

myList = [1]

def foo():
    global myList
    myList = myList + [2, 3]
def bar():
    myList.extend([2, 3])
def baz():
    global myList
    myList += [2, 3]

foo()
bar()
baz()

+=
不隐式调用
extend()
。首先,这是一个问题

如果你看一下
作业
一节,它会说:

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

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

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

由于扩充赋值是:

增广赋值是在单个语句中,二进制操作和赋值语句的组合:

它遵循同样的规则。 如你所见:

>>> 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  
扩充赋值计算目标(与普通赋值语句不同,目标不能是解包)和表达式列表,对两个操作数执行特定于赋值类型的二进制运算,并将结果赋值给原始目标。目标仅评估一次

第一次调用尝试计算
myList
,这将导致
LOAD\u FAST
,因为没有
global
语句,所以假定它是一个局部变量:

LOAD\u FAST(var\u num)

将对本地
co\u varnames[var\u num]
的引用推送到堆栈上

找不到它,因此引发了错误。如果找到了然后我们进入oppcode
INPLACE\u ADD
,它调用方法
myList.\uuu iadd\uuuu
,该方法执行扩展任务,一旦该操作完成,结果将被分配回变量,但我们永远不会走到这一步


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

范围较大的示例表明,我的第一个示例(L=L+M)并不等同于我的第三个示例(L+=M),否则它们都会花费大量时间,如果它们是(主动的)更改引用。显然您是正确的,+=不会更改引用。我已经用“is”操作符检查过了。我的意思是,如果需要调整列表大小,我认为+=/extend()/append()可以隐式更改引用操作符,但是用户/代码不需要更改引用。感谢您发布答案并跟进,我非常感谢!啊,好吧,现在更合理了,谢谢你的解释。我还得多考虑一下如何摆弄反汇编程序,因为熟悉它会对我有所帮助。是的,我在实践中不会这样做——我犯过一次错误,不知道为什么会得到这个输出,所以我很好奇,但后来做了正常的事情,只是把它作为参数传递。谢谢5年后,这又咬了我一口,但方式不同。如果
isinstance(其他,numpy.ndarray)
isinstance(orig,list)
,则
orig+=other
失败,并出现numpy错误(
操作数无法广播
),但
orig.extend(其他)
有效
myList = [1]

def foo():
    global myList
    myList = myList + [2, 3]
def bar():
    myList.extend([2, 3])
def baz():
    global myList
    myList += [2, 3]

foo()
bar()
baz()
>>> 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