范围界定规则的简要说明?

2024-03-28 13:46:30 发布

您现在位置: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.。(这些规则特定于变量名,而不是属性。如果引用时没有句点,则适用这些规则。)

法律规则

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

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

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

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

所以,在这种情况下

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

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

  • 五十: 局部在def spam(在code3code4,和code5
  • E: 任何封闭函数(如果整个示例位于另一个def
  • G: 模块中是否有全局声明的x(在code1中)?
  • B: Python中的任何内置x

code2中永远找不到x(即使在您可能希望的情况下,请参阅Antti's answerhere)。

实际上,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”,它的作用方式与global类似,但对于嵌套的函数作用域。

关于Python的第三次没有一个彻底的答案,所以我在这里做了一个回答。这里描述的大部分内容在Python 3文档的4.2.2 Resolution of names中有详细说明。

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

特别是,noblock语句,除了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中,列表理解也不创建作用域(而生成器和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()

作用域可以声明它明确希望修改全局(模块作用域)变量,并使用全局关键字:

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.

^{} documentation

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;在python 2中,它是__builtin__,在python 3中,它现在被称为builtins。如果指定给buil的属性tins模块,此后它将作为可读的全局变量对任何模块可见,除非该模块使用自己的同名全局变量隐藏它们。


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

import __builtin__

print3 = __builtin__.__dict__['print']

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

相关问题 更多 >