UnboundLocalError 尝试使用已(重新)分配的变量(应该是全局的)(即使在第一次使用后)

发布于 2025-01-09 23:57:13 字数 1505 浏览 0 评论 0 原文

当我尝试这段代码时:

a, b, c = (1, 2, 3)

def test():
    print(a)
    print(b)
    print(c)
    c += 1
test()

我从 print(c) 行收到一个错误,内容如下:

UnboundLocalError: local variable 'c' referenced before assignment

或者在某些旧版本中:

UnboundLocalError: 'c' not assigned

如果我注释掉 c += 1,则所有打印成功。

我不明白:如果 c 不行,为什么打印 ab 可以工作? c += 1 如何导致 print(c) 失败,即使它出现在代码的后面?

看起来赋值 c += 1 创建了一个本地变量 c,它优先于全局 c 。但是变量如何在存在之前“窃取”作用域呢?为什么c在这里显然是本地的?


另请参阅如何在函数中使用全局变量?,了解有关如何重新分配全局变量的问题从函数内部,以及 是否可以修改 python 中位于外部(封闭)作用域但不是全局作用域中的变量? 用于从 a 重新分配封闭函数(闭包)。

请参阅为什么访问全局变量不需要“global”关键字? 对于 OP 预期错误但没有得到错误的情况,只需访问不带 global 关键字的全局变量即可。

<子>参见Python 中的名称如何“解除绑定”?哪些代码会导致“UnboundLocalError”?对于OP预期变量是本地变量的情况,但在每种情况下都存在阻止赋值的逻辑错误。

有关相关问题,请参阅如何在实际代码中发生“NameError:在封闭范围内赋值之前引用的自由变量'var'”?造成的del 关键字。

When I try this code:

a, b, c = (1, 2, 3)

def test():
    print(a)
    print(b)
    print(c)
    c += 1
test()

I get an error from the print(c) line that says:

UnboundLocalError: local variable 'c' referenced before assignment

or in some older versions:

UnboundLocalError: 'c' not assigned

If I comment out c += 1, all the prints are successful.

I don't understand: why does printing a and b work, if c does not? How did c += 1 cause print(c) to fail, even when it comes later in the code?

It seems like the assignment c += 1 creates a local variable c, which takes precedence over the global c. But how can a variable "steal" scope before it exists? Why is c apparently local here?


See also How to use a global variable in a function? for questions that are simply about how to reassign a global variable from within a function, and Is it possible to modify a variable in python that is in an outer (enclosing), but not global, scope? for reassigning from an enclosing function (closure).

See Why isn't the 'global' keyword needed to access a global variable? for cases where OP expected an error but didn't get one, from simply accessing a global without the global keyword.

See How can a name be "unbound" in Python? What code can cause an `UnboundLocalError`? for cases where OP expected the variable to be local, but has a logical error that prevents assignment in every case.

See How can "NameError: free variable 'var' referenced before assignment in enclosing scope" occur in real code? for a related problem caused by the del keyword.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

近箐 2025-01-16 23:57:14

当初始化后(通常在循环或条件块中)对变量使用 del 关键字时,也可能会出现此问题。

This issue can also occur when the del keyword is utilized on the variable down the line, after initialization, typically in a loop or a conditional block.

硬不硬你别怂 2025-01-16 23:57:14

在下面的 n = num 示例中,n 是局部变量,num 是全局变量:

num = 10

def test():
  # ↓ Local variable
    n = num
       # ↑ Global variable
    print(n)
  
test()

因此,没有错误:

10

但是在下面的 num = num 例子中,两侧的 num 都是局部变量,而右侧的 num 尚未定义:

num = 10

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

所以,出现以下错误:

UnboundLocalError:赋值前引用了局部变量“num”

另外,即使删除 num = 10 如下所示:

# num = 10 # Removed

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

也存在以下相同的错误:

UnboundLocalError:赋值前引用了局部变量“num”

所以要解决上面的错误,请将 global num 放在 num = num 之前,如下所示:

num = 10

def test():
    global num # Here
    num = num 
    print(num)
  
test()

然后,上面的错误解决为 :

10

或者,在num = num之前定义局部变量num = 5,如下所示:

num = 10

def test():
    num = 5 # Here
    num = num
    print(num)
  
test()

那么,上面的错误就解决了,如下所示

5

In this case of n = num below, n is a local variable and num is a global variable:

num = 10

def test():
  # ↓ Local variable
    n = num
       # ↑ Global variable
    print(n)
  
test()

So, there is no error:

10

But in this case of num = num below, num on the both side are local variables and num on the right side is not defined yet:

num = 10

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

So, there is the error below:

UnboundLocalError: local variable 'num' referenced before assignment

In addition, even if removing num = 10 as shown below:

# num = 10 # Removed

def test():
   # ↓ Local variable
    num = num
         # ↑ Local variable not defined yet
    print(num)
  
test()

There is the same error below:

UnboundLocalError: local variable 'num' referenced before assignment

So to solve the error above, put global num before num = num as shown below:

num = 10

def test():
    global num # Here
    num = num 
    print(num)
  
test()

Then, the error above is solved as shown below:

10

Or, define the local variable num = 5 before num = num as shown below:

num = 10

def test():
    num = 5 # Here
    num = num
    print(num)
  
test()

Then, the error above is solved as shown below:

5
三生殊途 2025-01-16 23:57:14

要修复此错误,我们可以使用 nonlocal 关键字,它允许您修改最近的非全局封闭范围中的变量。

class S:
    def fun1(self):
        left_depth = right_depth = 0
        def inner():
            nonlocal left_depth
            nonlocal right_depth
            a = 5
            left_depth += 1
            print(a, left_depth)
        inner()

s = S()
s.fun1()

To fix this error, we can use the nonlocal keyword, which allows you to modify a variable in the nearest enclosing scope that is not global.

class S:
    def fun1(self):
        left_depth = right_depth = 0
        def inner():
            nonlocal left_depth
            nonlocal right_depth
            a = 5
            left_depth += 1
            print(a, left_depth)
        inner()

s = S()
s.fun1()
孤星 2025-01-16 23:57:14

访问类变量的最佳方式是直接通过类名访问

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1

The best way to reach class variable is directly accesing by class name

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1
西瓜 2025-01-16 23:57:14

如果您定义与方法同名的变量,您也会收到此消息。

例如:

def teams():
    ...

def some_other_method():
    teams = teams()

解决方案是将方法 teams() 重命名为 get_teams() 等其他名称。

由于它仅在本地使用,因此 Python 消息相当具有误导性!

你最终会得到这样的东西来解决它:

def get_teams():
    ...

def some_other_method():
    teams = get_teams()

You can also get this message if you define a variable with the same name as a method.

For example:

def teams():
    ...

def some_other_method():
    teams = teams()

The solution, is to rename method teams() to something else like get_teams().

Since it is only used locally, the Python message is rather misleading!

You end up with something like this to get around it:

def get_teams():
    ...

def some_other_method():
    teams = get_teams()
机场等船 2025-01-16 23:57:13

Python 对函数中的变量的处理方式有所不同,具体取决于您是从函数内部还是外部为它们赋值。 如果在函数内分配变量,则默认情况下将其视为局部变量。因此,当您取消注释该行时,您将尝试在为局部变量 c 分配任何值之前引用它。

如果您希望变量c引用在函数之前分配的全局c = 3,请将其放在

global c

函数的第一行。

对于 python 3,现在

nonlocal c

您可以使用它来引用最近的包含 c 变量的封闭函数作用域。

Python treats variables in functions differently depending on whether you assign values to them from inside or outside the function. If a variable is assigned within a function, it is treated by default as a local variable. Therefore, when you uncomment the line, you are trying to reference the local variable c before any value has been assigned to it.

If you want the variable c to refer to the global c = 3 assigned before the function, put

global c

as the first line of the function.

As for python 3, there is now

nonlocal c

that you can use to refer to the nearest enclosing function scope that has a c variable.

清眉祭 2025-01-16 23:57:13

Python 有点奇怪,因为它将各种范围的所有内容都保存在字典中。原始的 a、b、c 位于最上面的范围中,因此位于最上面的字典中。该函数有自己的字典。当您到达 print(a)print(b) 语句时,字典中没有该名称的任何内容,因此 Python 会查找列表并在全局中找到它们字典。

现在我们到达c+=1,当然,它相当于c=c+1。当 Python 扫描该行时,它会说“啊哈,有一个名为 c 的变量,我将把它放入我的本地作用域字典中。”然后,当它为赋值右侧的 c 寻找 c 的值时,它会找到名为 c 的局部变量,该变量还没有值,因此会抛出错误。

上面提到的语句 global c 只是告诉解析器它使用全局范围内的 c,因此不需要新的。

它之所以说它所做的行存在问题,是因为它在尝试生成代码之前有效地查找名称,因此从某种意义上说,它认为它还没有真正执行该行。我认为这是一个可用性错误,但通常来说,学会不要太认真地对待编译器的消息是一个很好的做法。

如果有什么安慰的话,我可能花了一天的时间挖掘和试验同样的问题,然后才发现 Guido 写的关于解释一切的词典的内容。

更新,请参阅评论:

它不会扫描代码两次,但它确实分两个阶段扫描代码:词法分析和解析。

考虑一下这行代码的解析是如何工作的。词法分析器读取源文本并将其分解为词素,即语法的“最小组成部分”。因此,当它到达该行时,

c+=1

它会将其分解为类似的

SYMBOL(c) OPERATOR(+=) DIGIT(1)

内容 解析器最终希望将其放入解析树并执行它,但由于它是一个赋值,因此在执行之前,它会在本地字典中查找名称 c ,不会没有看到它,并将其插入字典中,将其标记为未初始化。在完全编译的语言中,它只会进入符号表并等待解析,但由于它没有第二次传递的奢侈,所以词法分析器会做一些额外的工作以使以后的生活更轻松。只是,然后它会看到操作符,看到规则说“如果你有一个操作符+ =,则左侧必须已初始化”并说“哎呀!”

这里的要点是它还没有真正开始解析该行。这一切都是在为实际解析做准备,因此行计数器尚未前进到下一行。因此,当它发出错误信号时,它仍然认为它在上一行。

正如我所说,你可能会说这是一个可用性错误,但实际上这是一个相当普遍的事情。有些编译器对此更诚实,并说“XXX 行或其周围有错误”,但这个编译器却没有。

Python is a little weird in that it keeps everything in a dictionary for the various scopes. The original a,b,c are in the uppermost scope and so in that uppermost dictionary. The function has its own dictionary. When you reach the print(a) and print(b) statements, there's nothing by that name in the dictionary, so Python looks up the list and finds them in the global dictionary.

Now we get to c+=1, which is, of course, equivalent to c=c+1. When Python scans that line, it says "aha, there's a variable named c, I'll put it into my local scope dictionary." Then when it goes looking for a value for c for the c on the right hand side of the assignment, it finds its local variable named c, which has no value yet, and so throws the error.

The statement global c mentioned above simply tells the parser that it uses the c from the global scope and so doesn't need a new one.

The reason it says there's an issue on the line it does is because it is effectively looking for the names before it tries to generate code, and so in some sense doesn't think it's really doing that line yet. I'd argue that is a usability bug, but it's generally a good practice to just learn not to take a compiler's messages too seriously.

If it's any comfort, I spent probably a day digging and experimenting with this same issue before I found something Guido had written about the dictionaries that Explained Everything.

Update, see comments:

It doesn't scan the code twice, but it does scan the code in two phases, lexing and parsing.

Consider how the parse of this line of code works. The lexer reads the source text and breaks it into lexemes, the "smallest components" of the grammar. So when it hits the line

c+=1

it breaks it up into something like

SYMBOL(c) OPERATOR(+=) DIGIT(1)

The parser eventually wants to make this into a parse tree and execute it, but since it's an assignment, before it does, it looks for the name c in the local dictionary, doesn't see it, and inserts it in the dictionary, marking it as uninitialized. In a fully compiled language, it would just go into the symbol table and wait for the parse, but since it WON'T have the luxury of a second pass, the lexer does a little extra work to make life easier later on. Only, then it sees the OPERATOR, sees that the rules say "if you have an operator += the left hand side must have been initialized" and says "whoops!"

The point here is that it hasn't really started the parse of the line yet. This is all happening sort of preparatory to the actual parse, so the line counter hasn't advanced to the next line. Thus when it signals the error, it still thinks its on the previous line.

As I say, you could argue it's a usability bug, but its actually a fairly common thing. Some compilers are more honest about it and say "error on or around line XXX", but this one doesn't.

七堇年 2025-01-16 23:57:13

查看反汇编代码可能会清楚发生了什么:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

如您所见,访问 a 的字节码是 LOAD_FAST,而访问 b 的字节码是 LOAD_GLOBAL。这是因为编译器已识别出在函数内对 a 进行了赋值,并将其归类为局部变量。局部变量的访问机制与全局变量有根本的不同——它们在框架的变量表中静态分配一个偏移量,这意味着查找是一个快速索引,而不是全局变量更昂贵的字典查找。因此,Python 将 print a 行读取为“获取槽 0 中保存的局部变量 'a' 的值,并打印它”,当它检测到该变量仍未初始化时,引发异常。

Taking a look at the disassembly may clarify what is happening:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

As you can see, the bytecode for accessing a is LOAD_FAST, and for b, LOAD_GLOBAL. This is because the compiler has identified that a is assigned to within the function, and classified it as a local variable. The access mechanism for locals is fundamentally different for globals - they are statically assigned an offset in the frame's variables table, meaning lookup is a quick index, rather than the more expensive dict lookup as for globals. Because of this, Python is reading the print a line as "get the value of local variable 'a' held in slot 0, and print it", and when it detects that this variable is still uninitialised, raises an exception.

感性不性感 2025-01-16 23:57:13

当您尝试传统的全局变量语义时,Python 有相当有趣的行为。我不记得细节了,但是您可以很好地读取在“global”范围中声明的变量的值,但是如果您想修改它,则必须使用 global 关键字。尝试将 test() 更改为:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

此外,您收到此错误的原因是因为您还可以在该函数内声明一个与“全局”变量同名的新变量,并且它会完全分开。解释器认为您正在尝试在此范围内创建一个名为 c 的新变量,并在一次操作中修改它,这在 Python 中是不允许的,因为这个新的 c 是没有初始化。

Python has rather interesting behavior when you try traditional global variable semantics. I don't remember the details, but you can read the value of a variable declared in 'global' scope just fine, but if you want to modify it, you have to use the global keyword. Try changing test() to this:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

Also, the reason you are getting this error is because you can also declare a new variable inside that function with the same name as a 'global' one, and it would be completely separate. The interpreter thinks you are trying to make a new variable in this scope called c and modify it all in one operation, which isn't allowed in Python because this new c wasn't initialized.

凑诗 2025-01-16 23:57:13

清楚地说明这一点的最佳示例是:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

当调用 foo() 时,这也会引发 UnboundLocalError 尽管我们永远不会到达行 bar=0,因此逻辑上不应创建局部变量。

谜底在于“Python是一种解释性语言”,函数foo的声明被解释为单个语句(即复合语句),它只是愚蠢地解释它并创建本地和全局范围。因此 bar 在执行之前会在本地范围内被识别。

有关更多示例,请阅读这篇文章:http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

这篇文章提供了Python 变量作用域的完整描述和分析:

The best example that makes it clear is:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

when calling foo() , this also raises UnboundLocalError although we will never reach to line bar=0, so logically local variable should never be created.

The mystery lies in "Python is an Interpreted Language" and the declaration of the function foo is interpreted as a single statement (i.e. a compound statement), it just interprets it dumbly and creates local and global scopes. So bar is recognized in local scope before execution.

For more examples like this Read this post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

This post provides a Complete Description and Analyses of the Python Scoping of variables:

源来凯始玺欢你 2025-01-16 23:57:13

摘要

Python提前决定变量的范围。 除非使用 global 显式覆盖 nonlocal(在 3.x 中)关键字,变量将根据是否存在会更改的操作被识别为本地的绑定 姓名。其中包括普通赋值、增强赋值(例如 +=)、各种不太明显的赋值形式(for 构造、嵌套函数和类、import 语句...)以及un绑定(使用del)。此类代码的实际执行是无关的。

这也得到了解释 文档中

讨论

与流行的看法相反,Python 在任何有意义的意义上都不是一种“解释型”语言。 (现在这些已经很少见了。)Python 的参考实现以与 Java 或 C# 大致相同的方式编译 Python 代码:它被转换为虚拟机的操作码(“字节码”),即然后效仿。其他实现也必须编译代码 - 以便可以在不实际运行代码的情况下检测到 SyntaxError,并实现标准库的“编译服务”部分。

Python 如何确定变量作用域

在编译期间(无论是否在参考实现上),Python 遵循简单规则来决定变量作用域函数:

  • 如果函数包含全局nonlocal 名称声明,该名称被视为引用全局范围或第一个封闭范围分别包含名称。

  • 否则,如果它包含任何用于更改名称绑定(赋值或删除)的语法,即使代码实际上不会在运行时更改绑定,则名称为 本地

  • 否则,它指的是包含该名称的第一个封闭范围,否则指的是全局范围。

重要的是,作用域是在编译时确定的。生成的字节码将直接指示要查找的位置。例如,在 CPython 3.8 中,有单独的操作码 LOAD_CONST (编译时已知的常量)、LOAD_FAST (局部变量)、LOAD_DEREF (实现 nonlocal 通过查找闭包进行查找,闭包是作为“cell”对象的元组实现的),LOAD_CLOSURE(在闭包对象中查找局部变量,该变量是为嵌套函数创建)和 LOAD_GLOBAL(在全局命名空间或内置命名空间中查找内容)。

这些名称没有“默认”值。如果在查找之前尚未分配它们,则会发生 NameError。具体来说,对于本地查找,会发生UnboundLocalError;这是 NameError 的子类型。

特殊(和非特殊)情况

这里有一些重要的考虑因素,请记住语法规则是在编译时实现的,没有静态分析

  • 没关系 > 如果全局变量是内置函数等,而不是显式创建的全局变量:
    <前><代码>def x():
    int = int('1') # `int` 是本地的!

    (当然,无论如何,隐藏这样的内置名称都是一个坏主意,并且 global 也无济于事 - 就像在函数外部使用相同的代码一样 仍然会导致问题。)

  • 如果永远无法访问该代码没关系
    <前><代码>y = 1
    定义 x():
    返回 y # 本地!
    如果为假:
    y = 0

  • 如果分配被优化为就地修改(例如扩展列表),并不重要 - 从概念上讲,值仍然被分配,这在参考实现的字节码中反映为将名称重新分配给同一个对象是无用的:
    <前><代码>y = []
    定义 x():
    y += [1] # 本地的,即使它会用 `global` 就地修改 `y`

  • 然而,如果我们进行索引/切片分配,确实很重要。 (这在编译时转换为不同的操作码,依次调用 __setitem__。)
    <前><代码>y = [0]
    定义 x():
    print(y) # 现在全局了!没有错误发生。
    y[0] = 1

  • 还有其他形式的赋值,例如 for 循环和 import
    导入系统
    
    y = 1
    定义 x():
        返回 y # 本地!
        对于 [] 中的 y:
            经过
    
    定义 z():
        print(sys.path) # `sys` 是本地的!
        导入系统
    
  • 导致 import 出现问题的另一种常见方法是尝试将模块名称重用为局部变量,如下所示:
    随机导入
    
    定义 x():
        随机 = random.choice(['头', '尾'])
    

    同样,import 是赋值,因此有一个全局变量random。但这个全局变量并不特殊;它很容易被本地随机所掩盖。

  • 删除某些内容也会更改名称绑定,例如:
    <前><代码>y = 1
    定义 x():
    返回 y # 本地!
    德尔伊

鼓励感兴趣的读者使用参考实现来使用 dis 标准库模块检查每个示例。

封闭作用域和 nonlocal 关键字(在 3.x 中)

对于 global ,问题的工作方式相同,已作必要的修改非本地 关键字。 (Python 2.x 没有 nonlocal。)无论哪种方式,都需要使用关键字来赋值从外部作用域到变量,但是不需要仅仅查找它,也不需要改变查找的对象。 (再次强调:列表上的 += 会改变列表,但然后还会将名称重新分配到同一个列表。)

关于全局变量和内置函数的特别说明

如上所示,Python 确实不将任何名称视为“内置范围内”。相反,内置函数是全局范围查找使用的后备。分配给这些变量只会更新全局范围,而不是内置范围。然而,在参考实现中,内置作用域可以修改:它由名为__builtins__的全局命名空间中的变量表示,该变量保存一个模块对象(内置函数被实现在 C 中,但作为称为 builtins 的标准库模块提供,它是预先导入并分配给该全局名称的)。奇怪的是,与许多其他内置对象不同,该模块对象可以修改和删除其属性。 (据我了解,所有这些都应该被视为不可靠的实现细节;但这种方式已经工作了相当长一段时间了。)

Summary

Python decides the scope of the variable ahead of time. Unless explicitly overridden using the global or nonlocal (in 3.x) keywords, variables will be recognized as local based on the existence of any operation that would change the binding of a name. That includes ordinary assignments, augmented assignments like +=, various less obvious forms of assignment (the for construct, nested functions and classes, import statements...) as well as unbinding (using del). The actual execution of such code is irrelevant.

This is also explained in the documentation.

Discussion

Contrary to popular belief, Python is not an "interpreted" language in any meaningful sense. (Those are vanishingly rare now.) The reference implementation of Python compiles Python code in much the same way as Java or C#: it is translated into opcodes ("bytecode") for a virtual machine, which is then emulated. Other implementations must also compile the code - so that SyntaxErrors can be detected without actually running the code, and in order to implement the "compilation services" portion of the standard library.

How Python determines variable scope

During compilation (whether on the reference implementation or not), Python follows simple rules for decisions about variable scope in a function:

  • If the function contains a global or nonlocal declaration for a name, that name is treated as referring to the global scope or the first enclosing scope that contains the name, respectively.

  • Otherwise, if it contains any syntax for changing the binding (either assignment or deletion) of the name, even if the code would not actually change the binding at runtime, the name is local.

  • Otherwise, it refers to either the first enclosing scope that contains the name, or the global scope otherwise.

Importantly, the scope is resolved at compile time. The generated bytecode will directly indicate where to look. In CPython 3.8 for example, there are separate opcodes LOAD_CONST (constants known at compile time), LOAD_FAST (locals), LOAD_DEREF (implement nonlocal lookup by looking in a closure, which is implemented as a tuple of "cell" objects), LOAD_CLOSURE (look for a local variable in the closure object that was created for a nested function), and LOAD_GLOBAL (look something up in either the global namespace or the builtin namespace).

There is no "default" value for these names. If they haven't been assigned before they're looked up, a NameError occurs. Specifically, for local lookups, UnboundLocalError occurs; this is a subtype of NameError.

Special (and not-special) cases

There are some important considerations here, keeping in mind that the syntax rule is implemented at compile time, with no static analysis:

  • It does not matter if the global variable is a builtin function etc., rather than an explicitly created global:
    def x():
        int = int('1') # `int` is local!
    

    (Of course, it is a bad idea to shadow builtin names like this anyway, and global cannot help - just like using the same code outside of a function will still cause problems.)

  • It does not matter if the code could never be reached:
    y = 1
    def x():
        return y # local!
        if False:
            y = 0
    
  • It does not matter if the assignment would be optimized into an in-place modification (e.g. extending a list) - conceptually, the value is still assigned, and this is reflected in the bytecode in the reference implementation as a useless reassignment of the name to the same object:
    y = []
    def x():
        y += [1] # local, even though it would modify `y` in-place with `global`
    
  • However, it does matter if we do an indexed/slice assignment instead. (This is transformed into a different opcode at compile time, which will in turn call __setitem__.)
    y = [0]
    def x():
        print(y) # global now! No error occurs.
        y[0] = 1
    
  • There are other forms of assignment, e.g. for loops and imports:
    import sys
    
    y = 1
    def x():
        return y # local!
        for y in []:
            pass
    
    def z():
        print(sys.path) # `sys` is local!
        import sys
    
  • Another common way to cause problems with import is trying to reuse the module name as a local variable, like so:
    import random
    
    def x():
        random = random.choice(['heads', 'tails'])
    

    Again, import is assignment, so there is a global variable random. But this global variable is not special; it can just as easily be shadowed by the local random.

  • Deleting something also changes the name binding, e.g.:
    y = 1
    def x():
        return y # local!
        del y
    

The interested reader, using the reference implementation, is encouraged to inspect each of these examples using the dis standard library module.

Enclosing scopes and the nonlocal keyword (in 3.x)

The problem works the same way, mutatis mutandis, for both global and nonlocal keywords. (Python 2.x does not have nonlocal.) Either way, the keyword is necessary to assign to the variable from the outer scope, but is not necessary to merely look it up, nor to mutate the looked-up object. (Again: += on a list mutates the list, but then also reassigns the name to the same list.)

Special note about globals and builtins

As seen above, Python does not treat any names as being "in builtin scope". Instead, the builtins are a fallback used by global-scope lookups. Assigning to these variables will only ever update the global scope, not the builtin scope. However, in the reference implementation, the builtin scope can be modified: it's represented by a variable in the global namespace named __builtins__, which holds a module object (the builtins are implemented in C, but made available as a standard library module called builtins, which is pre-imported and assigned to that global name). Curiously, unlike many other built-in objects, this module object can have its attributes modified and deld. (All of this is, to my understanding, supposed to be considered an unreliable implementation detail; but it has worked this way for quite some time now.)

〆一缕阳光ご 2025-01-16 23:57:13

以下两个链接可能会有所帮助

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2: <一href="http://docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference" rel ="nofollow noreferrer">docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

链接一描述了错误 UnboundLocalError。链接二可以帮助您重写测试函数。根据链接二,原来的问题可以重写为:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

Here are two links that may help

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

link one describes the error UnboundLocalError. Link two can help with with re-writing your test function. Based on link two, the original problem could be rewritten as:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)
北方的韩爷 2025-01-16 23:57:13

这不是对您的问题的直接答案,但它是密切相关的,因为它是由增强赋值和函数作用域之间的关系引起的另一个问题。

在大多数情况下,您倾向于认为增强赋值 (a += b) 与简单赋值 (a = a + b) 完全相同。不过,在一种特殊情况下,这可能会遇到一些麻烦。让我解释一下:

Python 简单赋值的工作方式意味着如果将 a 传递到函数中(如 func(a);请注意,Python 始终是按引用传递),那么a = a + b不会修改传入的a,而是只会修改指向a的本地指针。

但是,如果您使用 a += b,那么它有时会实现为:

a = a + b

或有时(如果该方法存在)为:

a.__iadd__(b)

在第一种情况下(只要 a 是未声明为全局),在本地范围之外没有副作用,因为对 a 的赋值只是一个指针更新。

在第二种情况下,a 实际上会修改自身,因此对 a 的所有引用都将指向修改后的版本。以下代码演示了这一点:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

因此,技巧是避免对函数参数进行扩充赋值(我尝试仅将其用于局部/循环变量)。使用简单的赋值,你就可以避免模棱两可的行为。

This is not a direct answer to your question, but it is closely related, as it's another gotcha caused by the relationship between augmented assignment and function scopes.

In most cases, you tend to think of augmented assignment (a += b) as exactly equivalent to simple assignment (a = a + b). It is possible to get into some trouble with this though, in one corner case. Let me explain:

The way Python's simple assignment works means that if a is passed into a function (like func(a); note that Python is always pass-by-reference), then a = a + b will not modify the a that is passed in. Instead, it will just modify the local pointer to a.

But if you use a += b, then it is sometimes implemented as:

a = a + b

or sometimes (if the method exists) as:

a.__iadd__(b)

In the first case (as long as a is not declared global), there are no side-effects outside local scope, as the assignment to a is just a pointer update.

In the second case, a will actually modify itself, so all references to a will point to the modified version. This is demonstrated by the following code:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

So the trick is to avoid augmented assignment on function arguments (I try to only use it for local/loop variables). Use simple assignment, and you will be safe from ambiguous behaviour.

陌若浮生 2025-01-16 23:57:13

Python 解释器会将函数作为一个完整的单元来读取。我认为它分两次读取,一次收集其闭包(局部变量),然后再次将其转换为字节码。

我确信您已经知道,“=”左侧使用的任何名称都隐式是局部变量。我不止一次因将变量访问权限更改为+=而陷入困境,它突然变成了一个不同的变量。

我还想指出,这实际上与全球范围没有任何关系。使用嵌套函数可以获得相同的行为。

The Python interpreter will read a function as a complete unit. I think of it as reading it in two passes, once to gather its closure (the local variables), then again to turn it into byte-code.

As I'm sure you were already aware, any name used on the left of a '=' is implicitly a local variable. More than once I've been caught out by changing a variable access to a += and it's suddenly a different variable.

I also wanted to point out it's not really anything to do with global scope specifically. You get the same behaviour with nested functions.

银河中√捞星星 2025-01-16 23:57:13

c+=1 分配 c,python 假定分配的变量是本地的,但在本例中它尚未在本地声明。

使用 globalnonlocal 关键字。

nonlocal 仅适用于 python 3,因此如果您使用 python 2 并且不想使变量成为全局变量,则可以使用可变对象:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

c+=1 assigns c, python assumes assigned variables are local, but in this case it hasn't been declared locally.

Either use the global or nonlocal keywords.

nonlocal works only in python 3, so if you're using python 2 and don't want to make your variable global, you can use a mutable object:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文