32

如果我运行以下代码:

x = 1

class Incr:
    print(x)
    x = x + 1
    print(x)

print(x)

它打印:

1
2
1

好的,没问题,这正是我所期望的。如果我执行以下操作:

x = 1

class Incr:
    global x
    print(x)
    x = x + 1
    print(x)

print(x)

它打印:

1
2
2

也是我所期望的。那里没有问题。

现在,如果我开始按如下方式创建增量函数:

x = 1

def incr():
    print(x)

incr()

正如我预期的那样打印 1 。我假设它这样做是因为它无法x在其本地范围内找到,因此它搜索其封闭范围并在x那里找到。到目前为止没有问题。

现在,如果我这样做:

x = 1

def incr():
    print(x)
    x = x + 1

incr()

这在回溯中给了我以下错误:

UnboundLocalError:分配前引用的局部变量“x”。

为什么 Python 不能像我一样在x找不到x用于赋值的值时搜索封闭空间class Incr?请注意,我不是在问如何使此功能起作用。我知道如果我执行以下操作,该功能将起作用:

x = 1

def incr():
    global x
    print(x)
    x = x + 1
    print(x)

incr()

这将正确打印:

1
2

正如我所料。我要问的是为什么x当关键字不存在时它不只是从封闭范围中拉出来,global就像我在上面的课程中所做的那样。为什么口译员觉得有必要报告这一点,因为UnboundLocalError它清楚地知道有些x存在。由于该函数能够读取x打印的值,我知道它具有x作为其封闭范围的一部分......那么为什么这不像类示例那样工作?

为什么使用xfor print 的值与使用它的值进行赋值如此不同?我只是不明白。

4

7 回答 7

45

类和函数是不同的,类内部的变量实际上是作为类的属性分配给类的命名空间的,而函数内部的变量只是普通变量,不能在外部访问。

函数内部的局部变量实际上是在第一次解析函数时决定的,python 不会在全局范围内搜索它们,因为它知道您将其声明为局部变量。

因此,一旦 python 看到一个x = x + 1(赋值)并且没有global为该变量声明,那么 python 就不会在全局或其他范围内查找该变量。

>>> x = 'outer'
>>> def func():
...     x = 'inner'  #x is a local variable now
...     print x
...     
>>> func()
inner

常见问题:

>>> x = 'outer'
>>> def func():
...     print x       #this won't access the global `x`
...     x = 'inner'   #`x` is a local variable
...     print x
...     
>>> func()
...
UnboundLocalError: local variable 'x' referenced before assignment

但是,当您使用global语句时,python for 会在global范围内查找该变量。

阅读:为什么当变量有值时我会收到 UnboundLocalError?

nonlocal:对于嵌套函数,您可以使用nonlocalpy3.x 中的语句来修改在封闭函数中声明的变量。


但是类的工作方式不同,x在类中声明的变量A实际上变成了A.x

>>> x = 'outer'
>>> class A:
...    x += 'inside'  #use the value of global `x` to create a new attribute `A.x`
...    print x        #prints `A.x`
...     
outerinside
>>> print x
outer

您也可以直接从全局范围访问类属性:

>>> A.x
'outerinside'

global在课堂上使用:

>>> x = 'outer'
>>> class A:
...     global x
...     x += 'inner' #now x is not a class attribute, you just modified the global x
...     print x
...     
outerinner
>>> x
'outerinner'
>>> A.x
AttributeError: class A has no attribute 'x'

函数的陷阱不会在类中引发错误:

>>> x = 'outer'
>>> class A:
...     print x                      #fetch from globals or builitns
...     x = 'I am a class attribute' #declare a class attribute
...     print x                      #print class attribute, i.e `A.x`
...     
outer
I am a class attribute
>>> x
'outer'
>>> A.x
'I am a class attribute'

LEGB规则:如果没有使用globalandnonlocal则 python 按此顺序搜索。

>>> outer = 'global'
>>> def func():
        enclosing = 'enclosing'
        def inner():
                inner = 'inner'
                print inner           #fetch from (L)ocal scope
                print enclosing       #fetch from (E)nclosing scope
                print outer           #fetch from (G)lobal scope
                print any             #fetch from (B)uilt-ins
        inner()
...         
>>> func()
inner
enclosing
global
<built-in function any>
于 2013-09-18T05:02:29.143 回答
5

从 Python范围和命名空间:

重要的是要认识到范围是由文本确定的:在模块中定义的函数的全局范围是该模块的命名空间,无论从何处调用该函数或通过什么别名调用该函数。另一方面,名称的实际搜索是在运行时动态完成的——然而,语言定义正在向“编译”时的静态名称解析发展,所以不要依赖动态名称解析!(实际上,局部变量已经是静态确定的。)

这意味着,x = x + 1在调用函数之前,它的范围是静态确定的。由于这是一个赋值,所以 'x' 成为一个局部变量,而不是全局查找。

from mod import *这也是函数中不允许的原因。因为解释器不会在编译时为您导入模块以了解您在函数中使用的名称。即,它必须知道编译时函数中引用的所有名称。

于 2013-09-18T05:04:04.540 回答
4

这是 Python 遵循的规则——习惯它;-) 有一个实际的原因:编译器和人类读者都可以通过查看函数来确定哪些变量是本地的。哪些名称是本地名称与函数出现的上下文无关,并且遵循限制您必须盯着的源代码数量以回答问题的规则通常是一个非常好的主意。

关于:

我假设它这样做是因为它在其本地范围内找不到 x,因此它搜索其封闭范围并找到 x。

不完全是:编译器在编译时确定哪些名称是本地的,哪些不是本地的。没有动态的“嗯——这是本地的还是全球的?” 搜索在运行时进行。精确的规则在这里阐明

至于为什么你不需要声明一个名字global来引用它的值,我喜欢Fredrik Lundh 的旧答案global在实践中,一条语句提醒代码阅读者一个函数可能正在重新绑定一个全局名称确实很有价值。

于 2013-09-18T05:04:49.283 回答
1

因为这就是它设计的工作方式。

基本上,如果您在函数的任何地方都有赋值,那么该变量将成为该函数的本地变量(当然,除非您使用过global)。

于 2013-09-18T04:48:46.583 回答
1

因为这将导致非常难以追踪错误!

当您键入 x = x + 1 时,您可能打算在封闭范围内增加一个 x ......或者您可能只是忘记了您已经在其他地方使用了 x 并试图声明一个局部变量。

我希望解释器只允许您更改父命名空间,如果您打算这样做 - 这样您就不会意外地做到这一点。

于 2013-09-18T05:03:30.793 回答
0

我可以尝试做出有根据的猜测,为什么它会这样工作。

当 Python 在函数中遇到字符串x = x + 1时,它必须决定在哪里查找x.

它可以说“第一次出现x是全局的,第二次出现是本地的”,但这是非常模棱两可的(因此违反了 Python 哲学)。这可以作为语法的一部分,但它可能会导致棘手的错误。因此,决定对此保持一致,并将所有出现的事件视为全局变量或局部变量。

有一个赋值,因此如果x应该是全局的,就会有一个global语句,但没有找到。

因此,x是局部的,但它不绑定任何东西,但它被用在表达式中x + 1。投掷UnboundLocalError

于 2013-09-18T04:56:18.743 回答
-1

作为在类中创建的新创建的 Ax 的一个额外示例。在类中将 x 重新分配给“inner”不会更新 x 的全局值,因为它现在是一个类变量。

x = 'outer'
class A:
    x = x
    print(x)
    x = 'inner'
    print(x)

print(x)
print(A.x)
于 2016-04-04T16:18:35.490 回答