为什么写入变量会改变其范围?

Why does writing to a variable change its scope?

采用以下代码示例

var = True
def func1():
    if var:
        print("True")
    else:
        print("False")
        # var = True

func1()

这会打印 True 正如人们所期望的那样。

但是,如果我取消注释 # var = True,我会得到错误

UnboundLocalError: local variable 'var' referenced before assignment

为什么写入变量会使原本可访问的变量无法访问?这种设计选择背后的基本原理是什么?

注意我知道如何解决它(使用 global 关键字)。我的问题是为什么它决定这样做。

因为:

  • 命名空间存在:同一个变量名可以在模块级和函数内部使用,互不相关

  • Python不需要声明变量,方便使用

  • 还需要一种方法来区分局部变量和全局变量

  • 在可能出现意外行为的情况下,抛出错误比默默接受更好

所以Python选择了规则“如果一个变量名在一个函数中被赋值,那么这个名称指的是一个局部变量”(因为如果它从未被赋值,它显然不是局部变量,因为它永远不会得到一个值)。

您的代码可能 被解释为首先使用模块级变量(在 if: 行中),然后在稍后使用局部变量进行赋值。 但是,这通常不是预期的行为。所以 Guido 决定 Python 不会那样工作,而是抛出错误。

这在 4.2.2 Resolution of names

部分中有描述

When a name is not found at all, a NameError exception is raised. If the current scope is a function scope, and the name refers to a local variable that has not yet been bound to a value at the point where the name is used, an UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.

If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

Python 默认为通过赋值进行隐式变量声明,以消除对额外显式声明的需要。只是“隐式声明”留下了几个选项,嵌套范围中的赋值意味着什么,最突出的是:

  • 赋值总是在最内层的范围内声明一个变量。
  • 赋值总是在最外层作用域中声明一个变量。
  • 赋值在最内层作用域中声明一个变量,除非在任何外部作用域中声明。
  • 赋值在最内层作用域中声明一个变量,只有在赋值后才可读。

后两个选项意味着变量没有仅由赋值本身明确定义的范围。它们是“通过赋值声明 + X”,这可能导致不相关代码之间的意外交互。

剩下的决定是“写入变量”最好发生在孤立的 local 还是共享的 global 变量上。


Python 设计者认为显式标记写入全局变量更为重要。

Python FAQ: Why am I getting an UnboundLocalError when the variable has a value?

[...]
This explicit declaration is required in order to remind you that (...) you are actually modifying the value of the variable in the outer scope

这是对纯粹阅读全局变量的故意不对称,这被认为是正确的做法。

Python FAQ: What are the rules for local and global variables in Python?

[...]
On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you’d be using global all the time.

如果在嵌套作用域中使用在外部作用域中定义的变量名,则取决于您在该嵌套作用域中如何处理它:

  1. 如果你只读取一个变量,它是同一个变量。

  2. 如果你到一个变量,那么Python会自动创建一个新的本地变量,与外层作用域不同

    此局部变量阻止访问外部作用域中同名的变量。

所以写入一个变量不会改变它的范围,它会创建一个不同的局部变量。

您无法在分配给此局部变量之前读取它。