Python的作用域规则究竟是什么?

如果我有一些代码:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

x在哪?一些可能的选择包括以下列表:

在封闭的源文件中 在类名称空间中 在函数定义中 在for循环的索引变量中 在for循环中

还有执行期间的上下文,当函数spam被传递到其他地方时。也许lambda函数传递有点不同?

在某个地方一定有一个简单的参考或算法。对于中级Python程序员来说,这是一个令人困惑的世界。


当前回答

关于Python3时间没有彻底的答案,所以我在这里做了一个回答。这里描述的大部分内容在Python 3文档的4.2.2名称解析中详细描述。

正如在其他回答中提供的,有4个基本范围,即LEGB,分别表示本地、封闭、全局和内置。除此之外,还有一个特殊的作用域,即类体,它不包含类内定义的方法的封闭作用域;类体中的任何赋值都将使该变量从此被绑定到类体中。

特别是,除了def和class,没有块语句创建变量作用域。在Python 2中,列表推导式不创建变量作用域,但在Python 3中,列表推导式中的循环变量是在新的作用域中创建的。

为了证明阶级主体的特殊性

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

因此,与在函数体中不同的是,你可以将变量重新赋值给类体中的相同名称,以获得具有相同名称的类变量;进一步查找此名称将解析 改为类变量。


对于许多Python新手来说,一个更大的惊喜是for循环不创建变量作用域。在Python 2中,列表推导式也不创建作用域(而生成器和字典推导式可以!)相反,它们会泄漏函数或全局作用域中的值:

>>> [ i for i in range(5) ]
>>> i
4

在Python 2中,推导式可以用作在lambda表达式中创建可修改变量的狡猾(如果你愿意,也可以是可怕的)方法——lambda表达式确实创建了一个变量作用域,就像def语句一样,但在lambda中不允许任何语句。赋值在Python中是一个语句,这意味着lambda中不允许变量赋值,但列表理解式是一个表达式…

这种行为在Python 3中已经修复——没有理解表达式或生成器泄漏变量。


全局指的是模块作用域;python的主要模块是__main__;所有导入的模块都可以通过sys. sys访问。模块变量;要访问__main__,可以使用sys. js。模块['__main__'],或导入__main__;在那里访问和赋值属性是完全可以接受的;它们将作为变量显示在主模块的全局作用域中。


如果一个名称在当前作用域中被赋值(类作用域除外),它将被认为属于该作用域,否则它将被认为属于赋值给该变量的任何封闭作用域(它可能还没有被赋值,或者根本没有赋值),或者最终属于全局作用域。如果变量被认为是本地的,但尚未设置或已删除,读取变量值将导致UnboundLocalError,它是NameError的子类。

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

作用域可以声明它显式地想要修改全局(模块作用域)变量,使用global关键字:

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

这也是可能的,即使它是封闭范围的阴影:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

在python 2中,没有简单的方法来修改封闭范围内的值;通常这是通过一个可变值来模拟的,比如一个长度为1的列表:

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

然而在python3中,nonlocal来拯救:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

非本地文档是这么说的

与全局语句中列出的名称不同,非局部语句中列出的名称必须引用封闭作用域中的预先存在的绑定(不能明确确定应该在其中创建新绑定的作用域)。

即nonlocal总是指名称被绑定的最内外部的非全局作用域(即赋值给,包括用作for目标变量,在with子句中,或作为函数参数)。


Any variable that is not deemed to be local to the current scope, or any enclosing scope, is a global variable. A global name is looked up in the module global dictionary; if not found, the global is then looked up from the builtins module; the name of the module was changed from python 2 to python 3; in python 2 it was __builtin__ and in python 3 it is now called builtins. If you assign to an attribute of builtins module, it will be visible thereafter to any module as a readable global variable, unless that module shadows them with its own global variable with the same name.


读取内置模块也很有用;假设您希望在文件的某些部分使用python 3样式的print函数,但文件的其他部分仍然使用print语句。在Python 2.6-2.7中,你可以通过以下方法获得Python 3 print函数:

import __builtin__

print3 = __builtin__.__dict__['print']

from __future__ import print_function实际上不会在Python 2中的任何地方导入print函数——相反,它只是禁用当前模块中print语句的解析规则,像处理任何其他变量标识符一样处理print,从而允许在内置中查找print函数。

其他回答

实际上,Python作用域解析的一个简明规则,见《学习Python》第3期。Ed . .(这些规则是特定于变量名的,而不是属性。如果你引用它时没有句号,则适用这些规则。)

LEGB规则

Local -在函数(def或lambda)中以任何方式赋值的名称,并且在该函数中未声明为全局的名称 封闭函数——在任何和所有静态封闭函数(def或lambda)的局部作用域内赋值的名称,从内部到外部 Global (module)——在模块文件的顶层赋值的名称,或者通过在文件的def中执行全局语句赋值 内置(Python) -在内置名称模块中预先分配的名称:open, range, SyntaxError等

在这种情况下

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

for循环没有自己的名称空间。按照LEGB的顺序,作用域是

L: Local in def spam(在code3, code4,和code5中) E:任何外围函数(如果整个例子在另一个def中) G:在模块中(在代码1中)是否有任何全局声明的x ? B: Python中的任何内置x。

x永远不会在代码2中找到(即使在您可能期望它会出现的情况下,参见Antti的答案或这里)。

x在哪?

X没有被找到,因为你没有定义它。:-)它可以在code1(全局)或code3(本地)中找到,如果你把它放在那里。

Code2(类成员)对于同一类的方法中的代码是不可见的——您通常会使用self访问它们。Code4 /code5(循环)与code3存在于相同的作用域中,因此如果你在那里写入x,你将改变code3中定义的x实例,而不是创建一个新的x。

Python是静态作用域的,所以如果你将' spam '传递给另一个函数,spam仍然可以访问它来自的模块中的全局变量(在code1中定义),以及任何其他包含作用域的范围(见下文)。Code2成员将再次通过self访问。

lambda与def没有区别。如果在函数中使用lambda,则与定义嵌套函数相同。在Python 2.2以后,可以使用嵌套作用域。在这种情况下,你可以在函数嵌套的任何级别绑定x, Python将会获取最内层的实例:

x= 0
def fun1():
    x= 1
    def fun2():
        x= 2
        def fun3():
            return x
        return fun3()
    return fun2()
print fun1(), x

2 0

Fun3看到来自最近的包含作用域的实例x,该作用域是与fun2关联的函数作用域。但是在fun1和全局中定义的其他x实例不受影响。

在Python 2.1之前的nested_scopes之前,以及2.1中,除非你特别要求使用from-future-import功能,否则fun1和fun2的作用域对fun3是不可见的,所以S.Lott的答案成立,你将得到全局x:

0 0

一个稍微完整一点的范围示例:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

输出:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200

在Python中,

任何被赋值的变量都是所在块的局部变量 作业出现了。

如果在当前范围内找不到变量,请参考LEGB顺序。

Python 2的作用域规则。X已经在其他答案中概述了。我唯一要补充的是,在Python 3.0中,还有一个非本地作用域的概念(由'nonlocal'关键字表示)。这允许您直接访问外部作用域,并提供了执行一些巧妙技巧的能力,包括词法闭包(没有涉及可变对象的丑陋操作)。

编辑:这里是PEP的更多信息。