Python,列表变量和字符串变量的作用域实际上不同吗?

Python, are list and string variables actually scoped differently?

我正在学习 Python 3 中的作用域,这个例子让我感到困惑。在函数内部调用时比较列表变量和字符串变量的行为:

foo1 = []
foo2 = ''
def f():
    foo1.append(3)
    global foo2
    foo2 += 'c'
    print('foo1-in-f:',foo1)
    print('foo2-in-f:',foo2)

print('foo1-before:',foo1)
print('foo2-before:',foo2)
f()
print('foo1-after:',foo1)
print('foo2-after:',foo2)

正如预期的那样,输出是:

foo1-before: []
foo2-before:
foo1-in-f: [3]
foo2-in-f: c
foo1-after: [3]
foo2-after: c

我很困惑为什么 字符串 必须 声明为全局 ,如行 global foo2,但是 list 未声明为全局,因为没有第 global foo1.

我 运行 代码省略了 global foo2 行,不出所料得到了 UnboundedLocalError: local variable 'foo2' referenced before assignment。但是,为什么我没有收到 foo1 的错误消息?

任何见解表示赞赏。我想确保我了解这到底是如何工作的。

您需要了解 Python 中变量作用域的工作原理。 Python 不要求您声明变量,但假定在函数体中赋值的变量是局部变量。您可以在编译器生成的字节码中看到这一点:

foo1 = []
foo2 = ''
def f():
    foo1.append(3)
    foo2 += 'c'

from dis import dis
dis(f)
  4           0 LOAD_GLOBAL              0 (foo1)
              2 LOAD_METHOD              1 (append)
              4 LOAD_CONST               1 (3)
              6 CALL_METHOD              1
              8 POP_TOP

  5          10 LOAD_FAST                0 (foo2)
             12 LOAD_CONST               2 ('c')
             14 INPLACE_ADD
             16 STORE_FAST               0 (foo2)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

foo1 是从全局上下文加载的,因为 foo1.append(3) 是一个项目赋值操作——它修改了实际引用。 AFAIK Python 字符串是不可变的,这意味着您需要在进行赋值之前复制该值,这是您在函数内部所做的,因为 foo2 += 'c' 实际上会创建一个新字符串。尝试 运行 foo1 += [3],你会得到与 foo1 相同的 UnboundLocalError

foo1.append(3)是一项赋值运算,等价于foo1[len(foo1):] = [3]。由于上述原因,这种操作对于 Python 字符串是不可能的 - 尝试 运行 foo2[:] = 'c' 并且您将得到错误 'str' object does not support item assignment.

现在,global 关键字基本上告诉解释器将 foo2 视为全局变量,尽管在函数内进行了赋值。

在 Python 函数中,所有变量引用 假定为全局变量 除非在本地命名。所有新对象都在本地范围内创建,并且对将对象传输或修改到另一个范围存在限制。

你可以做到:

a=1
def f(): return a+1      # unnamed integer object created and returned

>>> f()
2

您可以修改全局可变变量的内容,因为这不会将本地命名对象分配给全局范围:

ls=['string']
def f():
    ls.append('another')  # unnamed string object created and added to ls
    ls[0]+='_modified'    # ls[0] read, new object created with +=, 
                          # new object added to ls[0]   

>>> f()
>>> ls
['string_modified', 'another']

但是使用 ls+=[something] 是错误的,因为赋值 += 被视为 ls 在范围内是局部的然后重新分配到全局范围:

ls=[]
def f():
    ls+=['new entry'] # UnboundLocalError

您看到的问题不一定是修改全局变量。将函数在本地使用的名称重新分配给全局范围。

Python 网站上有一个 FAQ on this issue

Eli Bendersky 的博客上有一个 expanded FAQ