范围规则的简短描述?

时间:2008-11-15 01:48:38

标签: python scope dynamic-languages

Python的范围规则完全是什么?

如果我有一些代码:

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

找到x的位置?一些可能的选择包括以下列表:

  1. 在附带的源文件中
  2. 在类名称空间
  3. 在功能定义
  4. 在for循环索引变量
  5. 在for循环中
  6. 在执行期间,当函数spam在其他地方传递时,也存在上下文。也许lambda functions传递的方式有点不同?

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

9 个答案:

答案 0 :(得分:385)

实际上,来自Learning Python, 3rd. Ed.的Python范围解析的简明规则。 (这些规则特定于变量名称,而不是属性。如果您在没有句点的情况下引用它,则适用这些规则)

LEGB规则。

L ,Local - 在函数(deflambda)内以任何方式分配的名称,并且未在该函数中声明为全局。

E ,Enclosing-function locals - 从内到外的任何和所有静态封闭函数(deflambda)的本地范围内的名称。

G ,全局(模块) - 在模块文件的顶级分配的名称,或在文件中的global中执行def语句。

B ,内置(Python) - 在内置名称模块中预先指定的名称:openrangeSyntaxError,...

所以,在

的情况下
code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

for循环没有自己的命名空间。在LEGB顺序中,范围将是

L:本地,位于def spamcode3code 4code5)。

E:封闭函数,任何封闭函数(如果整个示例都在另一个def中)

G:全球。在模块(x)中是否全局声明了code1

B:Python中的任何内置x

永远不会在x中找到

code2(即使在您可能期望的情况下,请参阅Antti's answerhere)。

答案 1 :(得分:148)

基本上,Python中唯一引入新范围的是函数定义。类是一种特殊情况,因为在正文中直接定义的任何东西都放在类的命名空间中,但它们不能直接从它们包含的方法(或嵌套类)中访问。

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

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

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

  • builtins命名空间。一些特殊情况 - 它包含各种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

目前没有办法对包含 function 范围的变量做同样的事情,但是Python 3引入了一个新关键字“nonlocal”,它将以类似于全局的方式运行,但是对于嵌套的函数范围。

答案 2 :(得分:101)

关于Python3时间没有彻底的答案,所以我在这里做了一个答案。

正如其他答案所提供的,有4个基本范围,LEGB,用于Local,Enclosing,Global和Builtin。除此之外,还有一个特殊的范围,类主体,它不包含在类中定义的方法的封闭范围;类体内的任何赋值都会使那里的变量在类体中绑定。

特别是,除了defclass之外,没有块语句创建了一个变量范围。在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.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.

任何不被视为当前范围的本地变量或任何封闭范围的变量都是全局变量。在模块全局字典中查找全局名称;如果没有找到,则从内置模块中查找全局;模块的名称从python 2更改为python 3;在python 2中它是__builtin__而在python 3中它现在被称为builtins。如果您分配给builtins模块的属性,那么它将作为可读的全局变量显示在任何模块之后,除非该模块使用自己的同名全局变量来隐藏它们。


读取内置模块也很有用;假设你想在文件的某些部分使用python 3样式打印函数,但文件的其他部分仍然使用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函数。

答案 3 :(得分:21)

Python 2.x的范围规则已在其他答案中概述。我唯一要补充的是,在Python 3.0中,还有非局部范围的概念(由'nonlocal'关键字表示)。这允许您直接访问外部作用域,并且可以执行一些巧妙的技巧,包括词法闭包(没有涉及可变对象的丑陋黑客)。

编辑:这是PEP,其中包含更多相关信息。

答案 4 :(得分:20)

稍微更完整的范围示例:

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

答案 5 :(得分:11)

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

  

在执行期间的任何时候,都有   至少有三个嵌套的范围   名称空间可以直接访问:   搜索到的最里面的范围   首先,包含本地名称;该   任何封闭函数的名称空间,   从...开始搜索   最近的封闭范围;中间   范围,搜索下一个,包含   当前模块的全球名称;和   最外面的范围(最后搜索)是   包含内置名称的命名空间。

有两个函数:globalslocals,它们显示了这两个名称空间中的内容。

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

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

本例中为Local,是方法函数Foo.spam的主体。

全球化 - 全球化。

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

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

在类定义中,名称是类名称空间的一部分。例如,code2必须由类名限定。一般Foo.code2。但是,self.code2也可以工作,因为Python对象将包含类视为后退。

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

在类方法中,您有locals和globals。您说self.variable选择实例作为命名空间。您将注意到self是每个类成员函数的参数,使其成为本地名称空间的一部分。

请参阅Python Scope RulesPython ScopeVariable Scope

答案 6 :(得分:7)

  

x找到了哪里?

由于您尚未定义,因此找不到

。 :-)如果你把它放在那里,它可以在code1(全局)或code3(本地)中找到。

code2(类成员)对于同一个类的方法内的代码是不可见的 - 您通常会使用self访问它们。 code4 / code5(循环)与code3位于相同的范围内,所以如果你在那里写x,你将改变在code3中定义的x实例,而不是创建一个新的x。

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

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实例不受影响。

在nested_scopes之前 - 在Python 2.1之前和2.1中,除非你使用from-future-import特别要求该功能 - fun1和fun2的范围对fun3是不可见的,所以S.Lott的答案成立并且你会得到全球x:

0 0

答案 7 :(得分:2)

Python name resolution仅了解以下范围:

    提供了Builtin Functions
  1. builtins范围,例如printintzip
  2. module 全局范围,始终是当前模块的顶层,
  3. 三个可以相互嵌套的用户定义范围,即
    1. function 闭包范围,来自任何封闭的def块,lambda表达式或理解范围。
    2. def块,lambda表达式或理解范围内的
    3. function 局部范围
    4. class块内的
    5. class 范围。

值得注意的是,其他构造如ifforwith语句没有自己的作用域。

作用域TLDR :名称的 lookup 从使用该名称的作用域开始,然后是模块的所有封闭作用域(不包括类作用域)全局变量,最后是内建函数–使用此搜索顺序中的第一个匹配项。 范围的赋值默认情况下是当前范围–必须使用特殊格式nonlocalglobal赋值为来自一个外部范围。

最后,综合起来,理解和生成器表达式以及:=赋值表达式具有一条特殊的规则。


嵌套范围和名称解析

这些不同的作用域构建了一个层次结构,然后内置的变量始终是全局的基础,而闭包,局部变量和类作用域则按 lexical 的定义嵌套。也就是说,仅源代码中的嵌套很重要,例如调用堆栈则无关紧要。

print("builtins are available without definition")

some_global = "1"  # global variables are at module scope

def outer_function():
    some_closure = "3.1"  # locals and closure are defined the same, at function scope
    some_local = "3.2"    # a variable becomes a closure if a nested scope uses it

    class InnerClass:
         some_classvar = "3.3"   # class variables exist *only* at class scope

         def nested_function(self):
             some_local = "3.2"   # locals can replace outer names
             print(some_closure)  # closures are always readable
    return InnerClass

即使class创建了一个范围并且可能具有嵌套的类,函数和理解,但是class范围的名称对于封闭的范围是不可见的。这将创建以下层次结构:

┎ builtins           [print, ...]
┗━┱ globals            [some_global]
  ┗━┱ outer_function     [some_local, some_closure]
    ┣━╾ InnerClass         [some_classvar]
    ┗━╾ inner_function     [some_local]

名称解析始终从访问名称的当前范围开始,然后沿层次结构向上移动,直到找到匹配项。例如,在some_localouter_function内部查找inner_function从相应的功能开始-并立即找到在some_localouter_function中定义的inner_function , 分别。如果名称不是本地名称,则从定义该名称的最近封闭范围中获取该名称–在some_closure内查找printinner_function,直到分别搜索outer_function和内置。


范围声明和名称绑定

默认情况下,名称属于将名称绑定到值的任何范围。在内部范围内再次绑定相同的名称会创建一个具有相同名称的新变量-例如,some_localouter_function中分别存在inner_function。就范围而言,绑定包括设置名称值的任何语句–赋值语句,还包括for循环的迭代变量或with上下文管理器的名称。值得注意的是,del也算作名称绑定。

当名称必须引用外部变量 并在内部范围内绑定时,该名称必须声明为非本地。对于不同种类的封闭范围,存在单独的声明:nonlocal始终引用最近的封闭符,而global始终引用全局名称。值得注意的是,nonlocal从不引用全局名称,global会忽略所有相同名称的闭包。没有声明引用内置作用域。


some_global = "1"

def outer_function():
    some_closure = "3.2"
    some_global = "this is ignored by a nested global declaration"
    
    def inner_function():
        global some_global     # declare variable from global scope
        nonlocal some_closure  # declare variable from enclosing scope
        message = " bound by an inner scope"
        some_global = some_global + message
        some_closure = some_closure + message
    return inner_function

值得注意的是,函数local和nonlocal在编译时已解析。 nonlocal名称​​必须存在于某些外部范围内。相反,global名称可以动态定义,可以随时在全局范围内添加或删除。


理解和分配表达式

列表,集合和字典理解以及生成器表达式的作用域规则与函数几乎相同。同样,赋值表达式的作用域规则与常规名称绑定几乎相同。

理解范围和生成器表达式的范围与函数范围相同。范围内绑定的所有名称,即迭代变量,都是对comprehensions / generator和嵌套范围的局部或闭包。所有名称(包括可迭代名称)都使用名称解析(如适用于函数内部)进行解析。

some_global = "global"

def outer_function():
    some_closure = "closure"
    return [            # new function-like scope started by comprehension
        comp_local      # names resolved using regular name resolution
        for comp_local  # iteration targets are local
        in "iterable"
        if comp_local in some_global and comp_local in some_global
    ]

:=赋值表达式适用于最近的函数,类或全局范围。值得注意的是,如果赋值表达式的目标已在最近的范围内声明为nonlocalglobal,则赋值表达式会像常规赋值一样兑现它。

print(some_global := "global")

def outer_function():
    print(some_closure := "closure")

但是,理解/生成器内部的赋值表达式在该理解/生成器的最近的包围范围上起作用,而不是在理解/生成器本身的范围内起作用。当嵌套多个推导/生成器时,将使用最接近的函数或全局范围。由于理解/生成器作用域可以读取闭包和全局变量,因此赋值变量在理解中也是可读的。从理解分配到类范围是无效的。

print(some_global := "global")

def outer_function():
    print(some_closure := "closure")
    steps = [
        # v write to variable in containing scope
        (some_closure := some_closure + comp_local)
        #                 ^ read from variable in containing scope
        for comp_local in some_global
    ]
    return some_closure, steps

虽然迭代变量对于所绑定的理解是局部的,但是赋值表达式的目标不会创建局部变量,而是从外部范围读取

┎ builtins           [print, ...]
┗━┱ globals            [some_global]
  ┗━┱ outer_function     [some_closure]
    ┗━╾ <listcomp>         [comp_local]

答案 8 :(得分:0)

在Python中,

  

任何分配了值的变量都位于其中的块的本地   作业就会出现。

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