【问题标题】:Python, are list and string variables actually scoped differently?Python,列表和字符串变量的范围实际上不同吗?
【发布时间】:2021-03-23 11:43:59
【问题描述】:

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

我很困惑为什么 string 必须声明为全局,就像在global foo2 行中一样,但 list未声明为全局,因为在global foo1 中没有行。

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

感谢任何见解。我想确保我理解它是如何工作的。

【问题讨论】:

  • += 被实现为可变类型的突变,但不可变类型的重新绑定。一个要点是a += b 通常不等同于a = a + b。 (正如您所观察到的,对于字符串来说,它是)。

标签: python-3.x string list scope


【解决方案1】:

在 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

【讨论】:

  • 感谢您提供清晰的示例和其他链接。我将深入研究 += 运算符。乍一看,我假设a += 1 等价于a = a + 1。此外,您将字符串包装在列表中的方法很漂亮。例如,在退出函数后,我可以“连接”完整的字符串,例如:" ".join(['Zapdos', 'used', 'Thunder.'])
【解决方案2】:

您需要了解变量作用域在 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 视为全局变量,尽管在函数内进行了赋值。

【讨论】:

  • 谢谢,您对dis 读数的评论很有帮助!将来遇到令人困惑的行为时,我将使用此软件包进行深入研究。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-17
  • 1970-01-01
  • 2021-12-13
  • 2018-12-06
  • 2017-12-08
  • 2019-10-16
  • 1970-01-01
相关资源
最近更新 更多