范围规则的简短描述?

完全是什么Python作用域规则?

如果我有一些代码:

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

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

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

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

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

258712 次浏览

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

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

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

命名空间是由包、模块、类、对象结构和函数创建的。没有其他类型的名称空间。

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

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

全球就是——嗯——全球。

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

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

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

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

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

参见Python作用域规则Python范围变量作用域

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作用域解析的一个简洁规则,来自学习Python,第三。艾德。。(这些规则是特定于变量名的,而不是属性。如果你引用它时没有句号,则适用这些规则。)

LEGB规则

  • Local -在函数(deflambda)中以任何方式赋值的名称,并且在该函数中未声明为全局

  • E nclose -function -在任何和所有静态封闭函数(deflambda)的局部作用域中赋值的名称,从内到外

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

  • Built-in (Python) -内置名称模块中预先分配的名称:openrangeSyntaxError

在这种情况下

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

for循环没有自己的命名空间。按照LEGB的顺序,作用域是

  • L:本地def spam(在code3code4,和code5)
  • E:任何外围函数(如果整个例子在另一个def中)
  • G:在模块中(在code1中)是否全局声明了任何x ?
  • B: Python中任何内置x

x永远不会在code2中找到(即使在你可能期望它会出现的情况下,参见管理的回答在这里)。

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

编辑:这里有鼓舞士气的的更多信息。

本质上,Python中唯一引入新作用域的是函数定义。类有点特殊,因为在主体中直接定义的任何东西都放在类的名称空间中,但它们不能从它们所包含的方法(或嵌套类)中直接访问。

在你的例子中,只有3个范围x将被搜索:

  • 垃圾邮件的作用域——包含code3和code5(以及循环变量code4)中定义的所有内容

  • 全局作用域——包含代码1中定义的所有内容,以及Foo(以及其后的任何更改)

  • 内置命名空间。有点特殊的情况-它包含各种Python内置函数和类型,如len()和str()。一般来说,任何用户代码都不应该修改它,所以期望它只包含标准函数。

只有在图片中引入嵌套函数(或lambda)时,才会出现更多作用域。 不过,它们的表现和你预期的差不多。嵌套函数可以访问局部作用域中的所有内容,也可以访问封闭函数作用域中的任何内容。如:< / p >
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”,它将以与全局类似的方式起作用,但用于嵌套函数作用域。

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

正如在其他回答中提供的,有4个基本范围,即LEGB,分别表示本地、封闭、全局和内置。除此之外,还有一个特殊的作用域类的身体,它不包含类中定义的方法的封闭作用域;类体中的任何赋值都将使该变量从此被绑定到类体中。

特别是没有块语句,除了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文档是这么说的

与全局语句中列出的名称不同,非局部语句中列出的名称必须引用封闭作用域中的预先存在的绑定(不能明确确定应该在其中创建新绑定的作用域)。

nonlocal总是指名称被绑定的最内层的外部非全局作用域(即赋值给,包括用作for目标变量,在with子句中,或作为函数参数)。


对于当前作用域或任何封闭作用域,任何不被视为局部的变量都是全局变量。全局名称在模块全局字典中查找;如果未找到,则从builtins模块中查找全局变量;模块名称从python2更改为python3;在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作为任何其他变量标识符处理,从而允许在内置中查找print函数。

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

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顺序。

Python名称解析只知道以下类型的作用域:

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

值得注意的是,其他结构,如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 inner_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_function0开始,然后沿着层次结构往上走,直到找到匹配的名称。例如,在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名称可以动态定义,并且可以随时从全局作用域添加或删除。


推导式和赋值表达式

列表、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]