范围规则的简短描述?

Cha*_*iam 451 python scope dynamic-languages

Python范围规则究竟是什么?

如果我有一些代码:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()
Run Code Online (Sandbox Code Playgroud)

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

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

在执行期间,当函数spam在其他地方传递时,也存在上下文.也许lambda函数的传递方式有点不同?

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

Riz*_*sim 404

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

LEGB规则.

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

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

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

,内置(Python)的-名内置名称模块预先分配:open,range,SyntaxError,...

所以,在这种情况下

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()
Run Code Online (Sandbox Code Playgroud)

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

L:本地,在for(中def spam,code3,code4).

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

G:全球.def在模块(x)中是否有全局声明?

B:code1Python中的任何内置函数.

x永远不会被发现x(即使在您可能期望的情况下,请参阅Antti的答案此处).

  • 作为全局访问的警告 - 读取全局变量可以在没有显式声明的情况下发生,但是在不声明全局(var_name)的情况下写入它将改为创建新的本地实例. (42认同)
  • 实际上@Peter,`global(var_name)`在语法上是不正确的.正确的语法是`global var_name`,没有括号.你有一个有效的观点. (10认同)
  • @Jonathan:因为每个`y`都被写入并且没有`global y`声明 - 请参阅@ Peter的评论. (2认同)

Bri*_*ian 149

从本质上讲,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
Run Code Online (Sandbox Code Playgroud)

限制:

可以访问除本地函数变量之外的范围中的变量,但如果没有进一步的语法,则无法将其重新转换为新参数.相反,赋值将创建一个新的局部变量,而不是影响父范围中的变量.例如:

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
Run Code Online (Sandbox Code Playgroud)

为了从函数范围内实际修改全局变量的绑定,您需要使用global关键字指定变量是全局变量.例如:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1
Run Code Online (Sandbox Code Playgroud)

目前没有办法对包含函数作用域的变量做同样的事情,但是Python 3引入了一个新的关键字" nonlocal",它将以类似于全局的方式起作用,但对于嵌套的函数作用域.


Ant*_*ala 105

关于Python3时间没有彻底的答案,所以我在这里给出了答案.

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

特别是,除了和之外,没有块语句创建变量范围.在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)
Run Code Online (Sandbox Code Playgroud)

因此,与函数体不同,您可以将变量重新分配给类体中的相同名称,以获取具有相同名称的类变量; 此名称的进一步查找将解析为类变量.


对许多Python新手来说,最大的惊喜之一是for循环不会创建变量范围.在Python 2中,列表推导也不会创建范围(而生成器和字典理解也会这样做!)而是泄漏函数或全局范围中的值:

>>> [ i for i in range(5) ]
>>> i
4
Run Code Online (Sandbox Code Playgroud)

理解可以用作狡猾(或者如果你愿意的话)可以在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()
Run Code Online (Sandbox Code Playgroud)

范围可以声明它明确想要使用global关键字修改全局(模块范围)变量:

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

foobar() # -> 5
print(x) # -> 6
Run Code Online (Sandbox Code Playgroud)

即使它在封闭范围内被遮蔽,这也是可能的:

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
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

然而在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.
Run Code Online (Sandbox Code Playgroud)

任何不被认为是当前范围的本地变量或任何封闭范围的变量都是全局变量.在模块全局字典中查找全局名称; 如果没有找到,则从内置模块中查找全局; 模块的名称从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']
Run Code Online (Sandbox Code Playgroud)

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


Jer*_*ell 21

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

编辑:这是PEP,有关于此的更多信息.


bri*_*ray 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()
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案.但是,我认为应该突出显示`method`和`method_local_ref`之间的区别.`method`能够访问全局变量并按照`5打印它.全球x`.但是`method_local_ref`不能,因为后来它定义了一个具有相同名称的局部变量.您可以通过删除`x = 200`行来测试这一点并查看差异 (5认同)

S.L*_*ott 11

Python通常使用三个命名空间来解析变量.

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

有两个功能:globalslocals您的内容显示两个这样的命名空间.

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

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

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

全球化 - 全球化.

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

没有其他范围.该for声明(以及其他复合语句像iftry)不产生新的嵌套的作用域.只有定义(包,模块,函数,类和对象实例.)

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

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

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

请参阅Python范围规则,Python范围,可变范围.

  • 这个已经过期了.从2.1(7年前)开始,有两个以上的范围,因为嵌套函数引入了新范围,因此函数内的函数可以访问其本地范围,封闭函数范围和全局范围(也是内置函数). (4认同)

bob*_*nce 7

找到x在哪里?

未找到x,因为您尚未定义它.:-)如果你把它放在那里,它可以在code1(全局)或code3(本地)中找到.

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
Run Code Online (Sandbox Code Playgroud)

fun3从最近的包含范围看到实例x,这是与fun2关联的函数范围.但是,在fun1和global中定义的其他x实例不受影响.

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

0 0
Run Code Online (Sandbox Code Playgroud)


Mis*_*agi 7

Python名称解析仅知道以下类型的范围:

\n
    \n
  1. 提供内置函数的内置作用域,例如print, int, 或zip,
  2. \n
  3. 模块全局作用域始终是当前模块的顶层,
  4. \n
  5. 三个可以相互嵌套的用户定义作用域,即\n
      \n
    1. 函数闭包作用域,来自任何封闭def块、lambda表达式或推导式。
    2. \n
    3. 函数局部作用域,在def块、lambda表达式或推导式内,
    4. \n
    5. 范围,在class块内。
    6. \n
    \n
  6. \n
\n

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

\n

范围 TLDR:名称的查找从使用名称的范围开始,然后是任何封闭范围(不包括类范围),到模块全局变量,最后是内置命令 \xe2\x80\x93 中的第一个匹配项使用搜索顺序。\n对范围的分配默认为当前范围 \xe2\x80\x93 特殊形式nonlocal,并且global必须用于从外部范围分配名称。

\n

最后,推导式和生成器表达式以及:=赋值表达式在组合时有一个特殊的规则。

\n
\n

嵌套范围和名称解析

\n

这些不同的作用域构建了一个层次结构,内置函数然后全局始终形成基础,闭包、局部变量和类作用域按照词法定义进行嵌套。也就是说,只有源代码中的嵌套很重要,而不是调用堆栈等。

\n
print("builtins are available without definition")\n\nsome_global = "1"  # global variables are at module scope\n\ndef outer_function():\n    some_closure = "3.1"  # locals and closure are defined the same, at function scope\n    some_local = "3.2"    # a variable becomes a closure if a nested scope uses it\n\n    class InnerClass:\n         some_classvar = "3.3"   # class variables exist *only* at class scope\n\n         def inner_function(self):\n             some_local = "3.2"   # locals can replace outer names\n             print(some_closure)  # closures are always readable\n    return InnerClass\n
Run Code Online (Sandbox Code Playgroud)\n

尽管class创建了一个作用域并且可能具有嵌套的类、函数和推导式,但该作用域的名称class对于封闭的作用域是不可见的。这将创建以下层次结构:

\n
\xe2\x94\x8e builtins           [print, ...]\n\xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 globals            [some_global]\n  \xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 outer_function     [some_local, some_closure]\n    \xe2\x94\xa3\xe2\x94\x81\xe2\x95\xbe InnerClass         [some_classvar]\n    \xe2\x94\x97\xe2\x94\x81\xe2\x95\xbe inner_function     [some_local]\n
Run Code Online (Sandbox Code Playgroud)\n

名称解析始终从访问名称的当前范围开始,然后沿层次结构向上,直到找到匹配项。例如,some_local在内部查找outer_functioninner_function从相应的函数开始 - 并立即分别找到在和some_local中定义的内容。当名称不是本地名称时,将从定义它的最近的封闭范围中获取 \xe2\x80\x93 分别查找和内部搜索,直到和内置。outer_functioninner_functionsome_closureprintinner_functionouter_function

\n
\n

范围声明和名称绑定

\n

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

\n

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

\n
\xe2\x94\x8e builtins           [print, ...]\n\xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 globals            [some_global]\n  \xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 outer_function     [some_local, some_closure]\n    \xe2\x94\xa3\xe2\x94\x81\xe2\x95\xbe InnerClass         [some_classvar]\n    \xe2\x94\x97\xe2\x94\x81\xe2\x95\xbe inner_function     [some_local]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

推导式和赋值表达式

\n

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

\n

推导式和生成器表达式的范围与函数范围相同。作用域中绑定的所有名称(即迭代变量)都是推导式/生成器和嵌套作用域的局部变量或闭包。所有名称(包括可迭代对象)均使用函数内部适用的名称解析进行解析。

\n
\nsome_global = "1"\n\ndef outer_function():\n    some_closure = "3.2"\n    some_global = "this is ignored by a nested global declaration"\n    \n    def inner_function():\n        global some_global     # declare variable from global scope\n        nonlocal some_closure  # declare variable from enclosing scope\n        message = " bound by an inner scope"\n        some_global = some_global + message\n        some_closure = some_closure + message\n    return inner_function\n
Run Code Online (Sandbox Code Playgroud)\n

赋值表达式:=适用于最近的函数、类或全局范围。值得注意的是,如果赋值表达式的目标已声明nonlocalglobal位于最近的范围内,则赋值表达式会像常规赋值一样遵循这一点。

\n
some_global = "global"\n\ndef outer_function():\n    some_closure = "closure"\n    return [            # new function-like scope started by comprehension\n        comp_local      # names resolved using regular name resolution\n        for comp_local  # iteration targets are local\n        in "iterable"\n        if comp_local in some_global and comp_local in some_global\n    ]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
print(some_global := "global")\n\ndef outer_function():\n    print(some_closure := "closure")\n
Run Code Online (Sandbox Code Playgroud)\n

虽然迭代变量对于其所绑定的推导式来说是局部的,但赋值表达式的目标不会创建局部变量,而是从外部作用域读取:

\n
\xe2\x94\x8e builtins           [print, ...]\n\xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 globals            [some_global]\n  \xe2\x94\x97\xe2\x94\x81\xe2\x94\xb1 outer_function     [some_closure]\n    \xe2\x94\x97\xe2\x94\x81\xe2\x95\xbe <listcomp>         [comp_local]\n
Run Code Online (Sandbox Code Playgroud)\n