为什么Python中的函数可以在封闭范围内打印变量但不能在赋值中使用它们?

时间:2013-09-18 04:46:04

标签: python scope

如果我运行以下代码:

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作为其封闭范围的一部分...所以为什么这不像类示例那样有效?

为什么使用x的值进行打印与使用其值进行分配有什么不同?我只是不明白。

7 个答案:

答案 0 :(得分:33)

类和函数是不同的,类中的变量实际上被赋予类的名称空间作为其属性,而在函数内部,变量只是不能在其外部访问的普通变量。

函数内部的局部变量实际上是在第一次解析函数时决定的,而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会在global范围内查找该变量。

阅读:Why am I getting an UnboundLocalError when the variable has a value?

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


但是类的工作方式不同,在类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 规则:如果没有使用globalnonlocal,则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>

答案 1 :(得分:3)

来自Python scopes and namespaces:

  

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

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

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

答案 2 :(得分:3)

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

关于:

  

我认为这样做是因为它无法在本地范围内找到x,所以   它搜索其封闭范围并找到x。

不完全:编译器在编译时确定 哪些名称是本地名称,哪些名称不是本地名称。没有动态的“嗯 - 这是本地还是全球?”搜索在运行时继续。 precise rules are spelled out here

至于为什么你不需要声明名称global只是为了引用它的值,我喜欢Fredrik Lundh's old answer here。在实践中,global语句警告代码读者警告函数可能重新绑定全局名称确实很有价值。

答案 3 :(得分:1)

因为这是它的设计方式。

基本上,如果你的函数中有任意位置,那么该变量将成为该函数的本地变量(当然,除非你使用了global)。

答案 4 :(得分:1)

因为它会导致非常难以追踪错误!

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

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

答案 5 :(得分:0)

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

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

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

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

因此,x是本地的,但它没有绑定任何东西,但它在表达式x + 1中使用。抛出UnboundLocalError

答案 6 :(得分:0)

作为在类中创建的新创建的A.x的额外示例。将x重新分配给内部&#39;在类中没有更新x的全局值,因为它现在是一个类变量。

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

print(x)
print(A.x)