Python的作用域规则究竟是什么?

如果我有一些代码:

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

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

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

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

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


当前回答

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

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

其他回答

在Python中,

任何被赋值的变量都是所在块的局部变量 作业出现了。

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

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

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

Python 2的作用域规则。X已经在其他答案中概述了。我唯一要补充的是,在Python 3.0中,还有一个非本地作用域的概念(由'nonlocal'关键字表示)。这允许您直接访问外部作用域,并提供了执行一些巧妙技巧的能力,包括词法闭包(没有涉及可变对象的丑陋操作)。

编辑:这里是PEP的更多信息。

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看到来自最近的包含作用域的实例x,该作用域是与fun2关联的函数作用域。但是在fun1和全局中定义的其他x实例不受影响。

在Python 2.1之前的nested_scopes之前,以及2.1中,除非你特别要求使用from-future-import功能,否则fun1和fun2的作用域对fun3是不可见的,所以S.Lott的答案成立,你将得到全局x:

0 0

Python名称解析只知道以下几种作用域:

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

值得注意的是,其他结构,如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 inner_function(self):
             some_local = "3.2"   # locals can replace outer names
             print(some_closure)  # closures are always readable
    return InnerClass

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

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

Name resolution always starts at the current scope in which a name is accessed, then goes up the hierarchy until a match is found. For example, looking up some_local inside outer_function and inner_function starts at the respective function - and immediately finds the some_local defined in outer_function and inner_function, respectively. When a name is not local, it is fetched from the nearest enclosing scope that defines it – looking up some_closure and print inside inner_function searches until outer_function and builtins, respectively.


作用域声明和名称绑定

默认情况下,名称属于将其绑定到值的任何范围。在内部作用域中再次绑定相同的名称会创建一个具有相同名称的新变量——例如,some_local分别存在于outer_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

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


推导式和赋值表达式

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

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

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
    ]

赋值表达式适用于最近的函数、类或全局作用域。值得注意的是,如果赋值表达式的目标在最近的作用域中被声明为非局部或全局,则赋值表达式将像常规赋值一样执行此操作。

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]