范围规则的简短描述?

Python范围规则到底是什么?

如果我有一些代码:

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

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

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

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

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


#1楼

关于Python3时间,还没有详尽的答案,所以我在这里做了一个答案。

如其他答案中所提供的,本地,封闭,全局和内置有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)

因此,与函数主体不同,您可以在类主体中将变量重新分配给相同的名称,以获得具有相同名称的类变量。 对该名称的进一步查找将解析为class变量。


对于许多使用Python的新手来说,最大的惊喜之一是for循环不会创建变量范围。 在Python 2中,列表推导也不创建作用域(而generator和dict则创建!)而是泄漏函数或全局范围中的值:

>>> [ 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.

任何不被认为是当前作用域或任何封闭作用域局部变量的变量都是全局变量。 在模块全局词典中查找全局名称; 如果找不到,则从内建模块中查找全局变量; 模块的名称从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 ,从而允许查找print函数在内置函数中。


#2楼

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

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

有两个函数: globals函数和locals函数,它们向您显示这些命名空间中的两个内容。

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

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

在这种情况下,本地方法方法函数Foo.spam

全球是-很好-全球。

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

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

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

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

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

请参见Python作用域规则Python作用域变量作用域


#3楼

在哪里找到x?

找不到x,因为您尚未定义x。 :-)如果将其放在代码1(全局)或代码3(本地)中,则可以找到它。

code2(类成员)对于相同类的方法内部的代码不可见-您通常使用self来访问它们。 code4 / code5(循环)的作用域与code3相同,因此,如果在其中写入x,则将更改code3中定义的x实例,而不创建新的x。

Python是静态作用域的,因此,如果您将“垃圾邮件”传递给另一个函数,则垃圾邮件仍将访问它来自的模块(在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和全局中定义的其他x实例不受影响。

在nested_scopes之前(在Python 2.1之前的版本中以及在2.1中,除非您专门使用from-future-import要求功能),fun1和fun2的作用域对fun3不可见,因此S.Lott的答案成立,您将获得全局x :

0 0

#4楼

实际上,这是学习Python的第3条关于Python范围解析的简明规则 埃德 。 (这些规则特定于变量名,而不是属性。如果不加句点引用,则适用这些规则。)

LEGB规则

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

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

  • ģ叶形(模块) -在模块文件的顶层分配名称,或通过执行一global在声明def文件中的

  • uilt式(Python)的-名内置名称模块预先分配: openrangeSyntaxError

因此,在

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

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

  • L:本地def spam (在code3code4code5
  • E:任何封闭函数(如果整个示例在另一个def
  • G:模块(在code1 )是否全局声明了x
  • B:Python中的任何内置x

x永远不会在code2中找到(即使在您可能期望的情况下,请参阅Antti的答案此处 )。


#5楼

其他答案中已经概述了Python 2.x的范围规则。 我唯一要补充的是,在Python 3.0中,还有非本地范围的概念(由'nonlocal'关键字指示)。 这使您可以直接访问外部作用域,并可以进行一些巧妙的技巧,包括词法关闭(没有涉及可变对象的难看的技巧)。

编辑:这是PEP ,对此有更多信息。


#6楼

本质上,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 ”,它的作用与全局变量类似,但适用于嵌套函数范围。


#7楼

范围的更完整示例:

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

#8楼

在Python中,

分配了值的任何变量对于分配在其中出现的块都是局部的。

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

发布了0 篇原创文章 · 获赞 2 · 访问量 7091

猜你喜欢

转载自blog.csdn.net/asdfgh0077/article/details/104083467
今日推荐