503

Python 作用域规则到底是什么?

如果我有一些代码:

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

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

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

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

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

4

9 回答 9

438

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

LEGB 规则

  • 本地def- 在函数(或)内以任何方式分配的名称lambda,并且未在该函数中声明为全局的

  • 封闭函数 - 在任何和所有静态封闭函数(deflambda)的局部范围内分配的名称,从内部到外部

  • 全局(模块)- 在模块文件的顶层分配的名称,或通过在文件中执行global语句def

  • 内置(Python) — 在内置名称模块中预先分配的名称:openrangeSyntaxError

所以,在

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

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

  • L: 局部于def spam(in code3, code4, and code5)
  • E:任何封闭函数(如果整个例子在另一个def
  • G:x模块(in code1)中是否有任何全局声明?
  • xB: Python 中的任何内置函数。

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

于 2008-11-15T12:47:22.537 回答
158

本质上,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”,它的作用类似于全局,但用于嵌套函数作用域。

于 2008-11-15T21:51:09.800 回答
114

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

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

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

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


对于 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.

nonlocal文件说_

与 global 语句中列出的名称不同,在 nonlocal 语句中列出的名称必须引用封闭范围中的预先存在的绑定(无法明确确定应创建新绑定的范围)。

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


任何不被视为当前作用域或任何封闭作用域的局部变量都是全局变量。在模块全局字典中查找全局名称;如果未找到,则从内置模块中查找全局;模块名称从 python 2 更改为 python 3;在 python 2 中它是__builtin__,在 python 3 中它现在被称为builtins. 如果您分配给内置模块的属性,则此后任何模块都可以将其作为可读的全局变量可见,除非该模块使用其自己的同名全局变量来隐藏它们。


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

import __builtin__

print3 = __builtin__.__dict__['print']

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

于 2014-05-05T11:08:54.970 回答
25

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

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
于 2015-12-04T17:38:37.673 回答
23

Python 2.x 的范围规则已经在其他答案中进行了概述。我唯一要补充的是,在 Python 3.0 中,还有一个非本地范围的概念(由“非本地”关键字表示)。这允许您直接访问外部范围,并打开了执行一些巧妙技巧的能力,包括词法闭包(没有涉及可变对象的丑陋黑客)。

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

于 2008-11-15T18:52:49.257 回答
15

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

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

有两个函数:它们向您显示globals其中locals两个命名空间的内容。

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

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

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

全球是——嗯——是全球性的。

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

没有其他范围。该for语句(和其他复合语句,如ifand try)不会创建新的嵌套范围。只有定义(包、模块、函数、类和对象实例。)

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

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

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

请参阅Python 范围规则Python 范围变量范围

于 2008-11-15T02:03:38.967 回答
9

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 从最近的包含范围(即与 fun2 关联的函数范围)中看到实例 x。但是在 fun1 和 global 中定义的其他 x 实例不受影响。

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

0 0
于 2008-11-15T12:44:59.920 回答
3

Python 名称解析只知道以下几种范围:

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

值得注意的是,诸如 、 或 语句之类的其他结构if没有for自己with的范围。

作用域 TLDR:名称的查找从使用名称的作用域开始,然后是任何封闭作用域(不包括类作用域),到模块全局变量,最后是内置函数——使用此搜索顺序中的第一个匹配项。默认情况下,对范围的分配是对当前范围的特殊形式nonlocal,并且global必须用于从外部范围分配给名称。

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


嵌套范围和名称解析

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

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_local内部outer_functioninner_function从相应的函数开始 - 并立即分别找到some_local定义在outer_functioninner_function中。当名称不是本地名称时,它会从定义它的最近的封闭范围中获取 - 分别查找some_closureprint内部inner_function搜索直到outer_function和内置。


范围声明和名称绑定

默认情况下,名称属于它绑定到值的任何范围。在内部范围内再次绑定相同的名称会创建一个具有相同名称的新变量 - 例如,some_local分别存在于outer_functioninner_function中。就作用域而言,绑定包括任何设置名称值的语句——赋值语句,还包括循环的迭代变量forwith上下文管理器的名称。值得注意的是,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

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


理解和赋值表达式

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

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

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]
于 2020-06-14T22:49:32.620 回答
1

在 Python 中,

任何被赋值的变量对于出现赋值的块都是局部的。

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

于 2019-05-12T22:27:43.957 回答