范围界定规则的简短描述?

2024-10-01 04:46:46 发布

您现在位置:Python中文网/ 问答频道 /正文

Python的作用域规则是什么

如果我有一些代码:

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

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

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

在执行过程中,当函数spam被传递到其他地方时,还有上下文。也许lambda functions的传递方式有点不同

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


Tags: 函数代码列表forfoo规则defspam
3条回答

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

立法局规则

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

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

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

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

那么,在

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

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

  • L:localindef spam(incode3code4code5
  • E:任何封闭函数(如果整个示例位于另一个def
  • G:模块中是否有全局声明的x(在code1
  • B:Python中的任何内置{}

x将永远不会在code2中找到(即使在您可能期望的情况下,请参见Antti's answerhere

关于Python3号的时间还没有完全的答案,所以我在这里做了一个回答。这里描述的大部分内容在Python3文档的4.2.2 Resolution of names中有详细说明

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

特别是,noblock语句,除了defclass之外,还创建了一个变量作用域。在Python2中,列表理解不创建变量作用域,但是在Python3中,列表理解中的循环变量是在新的作用域中创建的

展示班级成员的特点

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中,列表理解也不创建作用域(而生成器和dict理解创建作用域!),而是在函数或全局作用域中泄漏值:

>>> [ i for i in range(5) ]
>>> i
4

这些理解可以作为一种巧妙的(或者糟糕的)方法,在Python 2中的lambda表达式中生成可修改的变量-lambda表达式确实会创建变量范围,就像def语句一样,但在lambda中不允许任何语句。赋值是Python中的语句,这意味着lambda中不允许变量赋值,但列表理解是一个表达式

这种行为在Python3中已得到修复-没有理解表达式或生成器泄漏变量


全局实际上是指模块范围;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

在Python2中,没有简单的方法修改封闭范围中的值;通常,这是通过具有可变值来模拟的,例如长度为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.

报告说,

Names listed in a nonlocal statement, unlike those listed in a global statement, must refer to pre-existing bindings in an enclosing scope (the scope in which a new binding should be created cannot be determined unambiguously).

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


任何不被认为是当前作用域或任何封闭作用域的局部变量都是全局变量。在模块全局字典中查找全局名称;如果未找到,则从内置模块中查找全局;模块名称从python 2更改为python 3;在Python2中它是__builtin__,在Python3中它现在被称为builtins。如果指定给buil的属性tins模块之后,它将作为可读的全局变量对任何模块可见,除非该模块使用其自己的同名全局变量对其进行阴影处理


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

import __builtin__

print3 = __builtin__.__dict__['print']

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

从本质上讲,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”,它将以类似于全局的方式运行,但用于嵌套函数作用域

相关问题 更多 >