范围规则的简短描述?


472

Python范围规则到底是什么?

如果我有一些代码:

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

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

  1. 在随附的源文件中
  2. 在类命名空间中
  3. 在函数定义中
  4. 在for循环中,索引变量
  5. 在for循环内

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

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


2
在Python文档:docs.python.org/3/reference/…中相当简洁地(但也完整地)描述了范围规则。
jefe2000 '19

Answers:


420

实际上,这是学习Python的第3条关于Python范围解析的简明规则埃德 。(这些规则特定于变量名,而不是属性。如果不加句点引用,则适用这些规则。)

LEGB规则

  • L ocal —在函数(deflambda)中以任何方式分配的名称,但未在该函数中声明为全局

  • E nclosing-function —在任何和所有静态封装函数(deflambda)的本地范围内从内部到外部分配的名称

  • ģ叶形(模块) -在模块文件的顶层分配名称,或通过执行global在声明def文件中

  • : - uilt式(Python)的名称在内置模块的名称预先分配的openrangeSyntaxError,等

因此,在

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

for循环中没有自己的名字空间。按照LEGB顺序,范围为

  • L:本地的def spam(在code3code4code5
  • E:任何封闭函数(如果整个示例都在另一个示例中def
  • G:x模块中的全局中是否有任何声明code1
  • B:任何内置x的Python。

x永远不会被发现code2(即使您可能会期望,也请参阅Antti的答案此处)。


45
警告:全局访问-无需显式声明即可读取全局变量,但在不声明global(var_name)的情况下写入全局变量将创建一个新的本地实例。
彼得·吉布森

12
实际上@Peter global(var_name)在语法上是不正确的。正确的语法应该global var_name没有括号。不过,您有一个正确的观点。
martineau 2012年

如果是这样,那么下面的“ bar”为什么看不到foo的“ y”变量: >>> def foo(x): ... y = x ... def bar(z): ... y = z ... bar(5) ... print x,y ... >>> foo(3) 3 3
Jonathan Mayer

3
@Jonathan:因为每个对象y都被写入并且没有global y声明-请参阅@Peter 的注释。
martineau 2013年

@LakshmanPrasad它属于“ E”,但是有一个值得一提的特殊行为:它是一个类变量,因此它是对象中的“全局”。分配给它导致不可预测和难以调试的问题,如果你不知道自己在做什么。
Ctrl-C

157

本质上,Python中唯一引入新作用域的就是函数定义。类是一种特殊情况,因为直接在主体中定义的所有内容都放置在类的名称空间中,但是不能从它们包含的方法(或嵌套类)中直接访问它们。

在您的示例中,只有3个范围可以在其中搜索x:

  • 垃圾邮件的范围-包含在code3和code5(以及code4,循环变量)中定义的所有内容

  • 全局范围-包含code1中定义的所有内容以及Foo(及其后的所有更改)

  • 内置名称空间。有点特殊的情况-它包含各种Python内置函数和类型,例如len()和str()。通常,不应由任何用户代码对此进行修改,因此希望它包含标准功能,而不包含其他任何功能。

仅当您在图片中引入嵌套函数(或lambda)时,才会出现更多作用域。但是,它们的行为几乎与您期望的一样。嵌套函数可以访问本地作用域中的所有内容以及封闭函数的作用域中的任何内容。例如。

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

限制条件:

可以访问除局部函数的变量之外的范围中的变量,但是如果没有进一步的语法,则不能将其反弹到新参数。相反,赋值将创建一个新的局部变量,而不是影响父作用域中的变量。例如:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

为了在功能范围内实际修改全局变量的绑定,需要使用global关键字指定变量是全局变量。例如:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

当前,对于封闭函数范围中的变量,没有任何方法可以做到,但是Python 3引入了一个新关键字“ nonlocal”,它的作用与全局变量类似,但对于嵌套函数范围而言。


111

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

如其他答案中所提供的,本地,封闭,全局和内置有4个基本范围,即LEGB。除此以外,还有一个特殊的范围,即类主体,它不包含该类中定义的方法的封闭范围;类主体内的任何赋值都会使变量从此绑定到类主体中。

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

证明班级机构的特殊性

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)

因此,与函数主体不同,您可以在类主体中将变量重新分配给相同的名称,以获得具有相同名称的类变量。对该名称的进一步查找将解析为class变量。


对于Python的许多新手来说,最大的惊喜之一就是for循环不会创建变量作用域。在Python 2中,列表推导也不创建作用域(而generator和dict则创建!)而是泄漏函数或全局范围中的值:

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

理解可以用作在Python 2中的lambda表达式内创建可修改变量的一种狡猾(或者如果您愿意的话)的方法-lambda表达式确实会创建变量作用域,就像该def语句那样,但是在lambda中不允许使用任何语句。赋值是Python中的语句,表示不允许在lambda中进行变量赋值,但列表推导是一个表达式...

此行为已在Python 3中修复-没有理解表达式或生成器会泄漏变量。


全局实际上意味着模块范围;主要的python模块是__main__; 所有导入的模块都可以通过该sys.modules变量访问;获得__main__可以使用的权限sys.modules['__main__'],或import __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

但是,在python 3中,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文件

与在全局语句中列出的名称不同,非本地语句中列出的名称必须引用封闭范围内的现有绑定(不能明确确定应在其中创建新绑定的范围)。

即,nonlocal始终是指已绑定名称的最内层外部非全局范围(即,分配给该全局for变量,包括在with子句中或用作函数参数,包括用作目标变量)。


任何不被认为是当前作用域或任何封闭作用域局部变量的变量都是全局变量。在模块全局词典中查找全局名称;如果找不到,则从内建模块中查找全局变量;模块的名称从python 2更改为python 3; 在python 2中曾经是__builtin__,在python 3中现在被称为builtins。如果分配给buildins模块的属性,此后它将以可读的全局变量的形式对任何模块可见,除非该模块用自己的同名全局变量来遮盖它们。


读取内置模块也很有用;假设您希望在文件的某些部分使用python 3样式的打印功能,但文件的其他部分仍使用该print语句。在Python 2.6-2.7中,您可以使用以下命令来掌握Python 3 print函数:

import __builtin__

print3 = __builtin__.__dict__['print']

from __future__ import print_function实际上不会导入print功能,随时随地在Python 2 -而不是它只是禁止解析规则,print在当前模块中的语句,处理print像任何其他变量标识符,从而使print功能的内建进行查找。


23

其他答案中已经概述了Python 2.x的范围规则。我唯一要补充的是,在Python 3.0中,还有一个非本地范围的概念(由'nonlocal'关键字指示)。这使您可以直接访问外部作用域,并可以进行一些巧妙的技巧,包括词法关闭(没有涉及可变对象的难看的技巧)。

编辑:这是PEP,对此有更多信息。


23

范围的更完整示例:

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

6
这是一个很好的答案。但是,我认为method和之间的区别method_local_ref应予以强调。method能够访问全局变量并将其打印为5. Global x。但是method_local_ref不能,因为稍后会用相同的名称定义一个局部变量。您可以通过删除该x = 200行来测试这一点,然后观察一下区别
kiril

@brianray:z呢?
马里克·A·鲁米

@kiril我对此添加了注释
brianray

@ MalikA.Rumi我删除了z,因为它并不有趣
brianray

令人惊讶的是,这是我在所有SO中都能找到的Python范围的唯一清晰解释。简单地使用一个非常基本的示例。谢谢!
not2qubit

13

Python通常使用三个可用的名称空间来解析变量。

在执行过程中的任何时候,至少有三个嵌套作用域可以直接访问其名称空间:最先搜索的最内部作用域包含本地名称;任何封闭函数的名称空间,它们从最近的封闭范围开始搜索;接下来搜索的中间范围包含当前模块的全局名称;最外面的作用域(最后搜索)是包含内置名称的名称空间。

有两个函数:globalslocals,它们向您显示这些命名空间中的两个。

命名空间是由包,模块,类,对象构造和函数创建的。没有其他类型的名称空间。

在这种情况下,x必须在本地名称空间或全局名称空间中解析对名为的函数的调用。

在这种情况下,局部变量是方法函数的主体Foo.spam

全球是-很好-全球。

规则是搜索由方法函数(和嵌套函数定义)创建的嵌套局部空间,然后全局搜索。而已。

没有其他范围。该for语句(以及其他复合语句,如iftry)不会创建新的嵌套作用域。仅定义(包,模块,函数,类和对象实例。)

在类定义中,名称是类名称空间的一部分。 code2,例如,必须由类名限定。一般而言Foo.code2。但是,self.code2由于Python对象会将包含的类视为备用类,因此也可以使用。

对象(类的实例)具有实例变量。这些名称位于对象的名称空间中。它们必须由对象限定。(variable.instance。)

在类方法中,您具有局部变量和全局变量。您说self.variable选择实例作为名称空间。您会注意到,这self是每个类成员函数的参数,使其成为本地名称空间的一部分。

请参见Python作用域规则Python作用域变量作用域


5
这个已经过期了。自2.1(7年前)以来,有两个以上的作用域,因为嵌套函数引入了新的作用域,所以函数内的函数将可以访问其本地作用域,封闭函数作用域和全局作用域(也包括内置函数)。
布赖恩

抱歉,现在不再如此。Python has two namespaces available. Global and local-to-something.
Rizwan Kassim

9

在哪里找到x?

找不到x,因为您尚未定义x。:-)如果将其放在代码1(全局)或代码3(本地)中,则可以找到它。

code2(类成员)对于相同类的方法内部的代码不可见-您通常可以使用self来访问它们。code4 / code5(循环)的作用域与code3相同,因此,如果您在其中写入x,则将更改code3中定义的x实例,而不创建新的x。

Python是静态作用域的,因此,如果您将“垃圾邮件”传递给另一个函数,则垃圾邮件仍可访问其来源模块(在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从最近的包含范围(与fun2关联的函数范围)中看到实例x。但是在fun1和全局中定义的其他x实例不受影响。

在nested_scopes之前(在Python 2.1之前的版本中以及在2.1中,除非您专门使用from-future-import要求功能),fun3不可见fun1和fun2的作用域,因此S.Lott的答案成立,您将获得全局x :

0 0

1

在Python中,

分配了值的任何变量对于分配在其中出现的块都是局部的。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.