ChatGPT解决这个技术问题 Extra ChatGPT

范围规则的简短描述?

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

如果我有一些代码:

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

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

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

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

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

Python 文档:docs.python.org/3/reference/… 中对范围规则进行了相当简洁(但也很完整)的描述。

m
martineau

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

LEGB 规则

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

封闭函数 - 在任何和所有静态封闭函数(def 或 lambda)的本地范围内分配的名称,从内部到外部

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

内置 (Python) - 内置名称模块中预分配的名称:open、range、SyntaxError 等

所以,在

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

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

L:本地垃圾邮件(在代码 3、代码 4 和代码 5 中)

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

G:模块中(在代码1中)是否有全局声明的x?

B:Python 中的任何内置 x。

x 永远不会在 code2 中找到(即使在您预期会出现的情况下,请参阅 Antti's answerhere)。


作为全局访问的警告 - 读取全局变量可以在没有显式声明的情况下发生,但是在没有声明 global(var_name) 的情况下写入它会创建一个新的本地实例。
实际上@Peter,global(var_name) 在语法上是不正确的。正确的语法是不带括号的 global var_name。你有一个有效的观点。
如果是这样,那么为什么 foo 的“y”变量对下面的“bar”不可见:>>> def foo(x): ... y = x ... def bar(z): ... y = z ... bar(5) ... print x,y ... >>> foo(3) 3 3
@Jonathan:因为每个 y 都被写入并且没有 global y 声明 - 请参阅@Peter 的评论。
@Ctrl-C 不是真的;就范围而言,类属性没有什么特别之处。它们是共享的,无论 self 引用哪个实例,self.someClassAttribute 都将引用同一个对象,但名称本身必须用作实例或类本身的属性。实际的特殊行为是,在评估类主体中的语句in 时,类属性将隐藏包含范围中存在的任何变量。例如 j = 0; class Foo: j = 3; print(j); # end of class; print(j) 将输出 3,然后输出 0。
B
Brian

本质上,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

目前没有办法对封闭 function 范围内的变量执行相同的操作,但 Python 3 引入了一个新关键字“nonlocal”,其作用类似于全局,但适用于嵌套函数范围。


A
Antti Haapala -- Слава Україні

关于Python3时间没有彻底的答案,所以我在这里做了一个回答。 Python 3 文档的 4.2.2 Resolution of names 详细介绍了此处描述的大部分内容。

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

特别是 no 块语句,除了 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.

nonlocal documentation 表示

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

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

任何不被视为当前作用域或任何封闭作用域的局部变量都是全局变量。在模块全局字典中查找全局名称;如果未找到,则从内置模块中查找全局;模块名称从 python 2 更改为 python 3;在 python 2 中它是 __builtin__,而在 python 3 中它现在称为 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,从而允许 { 2} 在内置函数中查找函数。


很高兴终于看到一个答案,提到了相当知名的 LEGB 规则未涵盖的特殊类主体范围。
m
martineau

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

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

这是一个很好的答案。但是,我认为应该强调 methodmethod_local_ref 之间的差异。 method 能够访问全局变量并像在 5. Global x 中一样打印它。但是 method_local_ref 不能,因为稍后它定义了一个具有相同名称的局部变量。您可以通过删除 x = 200 行来测试它并查看差异
@brianray:z呢?
@kiril 我添加了一个注释
@MalikA.Rumi 我删除了 z 因为它不有趣
令人惊讶的是,这是我在所有 SO 上都能找到的对 Python 作用域的唯一清晰解释。只需使用一个非常基本的示例。谢谢!
J
Jeremy Cantrell

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

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


C
Community

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

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

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

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

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

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

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

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

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

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

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

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

请参阅 Python Scope RulesPython ScopeVariable Scope


这个已经过期了。从 2.1(7 年前)开始,有两个以上的作用域,因为嵌套函数引入了新的作用域,所以函数内的函数将可以访问其本地作用域、封闭函数作用域和全局作用域(也是内置函数)。
很抱歉,现在已经不是这样了。 Python has two namespaces available. Global and local-to-something.
b
bobince

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

M
MisterMiyagi

Python name resolution 只知道以下几种范围:

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

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

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

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

嵌套范围和名称解析

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

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]

名称解析总是从访问名称的当前作用域开始,然后沿着层次结构向上移动,直到找到匹配项。例如,在 outer_functioninner_function 中查找 some_local 从各自的函数开始 - 并立即找到分别在 outer_functioninner_function 中定义的 some_local。当名称不是本地名称时,它会从定义它的最近的封闭范围中获取 - 在 inner_function 中查找 some_closureprint 分别搜索直到 outer_function 和 builtins。

范围声明和名称绑定

默认情况下,名称属于它绑定到值的任何范围。在内部范围内再次绑定相同名称会创建一个具有相同名称的新变量 - 例如,some_local 分别存在于 outer_functioninner_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 名称可以动态定义,并且可以随时从全局范围添加或删除。

理解和赋值表达式

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]

我认为你的答案不完整。 except..as 语句还将创建一个新范围。例如,如果你把 try: raise ValueError('x'); except ValueError as v: pass 你不能访问 v 超出 except 子句的范围
@JohnHenckel 这不是一个新的范围。 except 完成后将其目标从其范围中删除。目标遵循常规范围规则,例如,它甚至可以声明为 global 并且在这种情况下将从全局范围中删除。 Demonstrator code
天哪,这很奇怪。谢谢你向我解释。
G
GraceMeng

在 Python 中,

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

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