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。
我正在学习 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。