资讯专栏INFORMATION COLUMN

Python: 作用域(scope) 和 LEGB

JayChen / 2895人阅读

摘要:约束名字空间作用域之间的那些事不管在什么编程语言都有作用域这个概念作用域控制在它范围内代码的生存周期包括名字和实体的绑定名字和实体的绑定我们可以理解成赋值当我们执行这句代码时实际上我们已经得到一个的关联关系我们也能将称之为约束这个约束也将存

约束 名字空间 作用域 之间的那些事

不管在什么编程语言, 都有作用域这个概念.作用域控制在它范围内代码的生存周期, 包括名字和实体的绑定.

名字和实体的绑定, 我们可以理解成赋值. num = int_obj, 当我们执行这句代码时, 实际上我们已经得到一个("num", int_obj)的关联关系, 我们也能将称之为约束, 这个约束也将存在名字空间(name space)里面, 名字空间也将是LEGB查找的依据.

而每个名字空间, 也将对应一个作用域, 作用域是代码正文中的一段代码区域, 作用域的有效范围更多是这段代码区域去衡量,一个作用域可以有多个名字空间, 一个名字空间也能有多个约束(多个赋值语句)

可以通过sys._getframe().f_code.co_name 查看代码所处的作用域, 先来看下sys._getframe是什么鬼吧?

# sys module
def _getframe(depth=None): # real signature unknown; restored from __doc__
    """
    _getframe([depth]) -> frameobject
    
    Return a frame object from the call stack.  If optional integer depth is
    given, return the frame object that many calls below the top of the stack.
    If that is deeper than the call stack, ValueError is raised.  The default
    for depth is zero, returning the frame at the top of the call stack.
    
    This function should be used for internal and specialized
    purposes only.
    """
    pass

从函数的定义可以看到, sys._getframe将返回一个frameobject对象, 那其实frameobject是什么对象? 为什么它能决定作用域?

frameobjec实际上就是python虚拟机上所维护的每个栈帧, 这和我们常规理解的栈帧多点差别, 因为python在原有栈帧的基础上, 在封装一层形成自己的栈帧. 虽然是有些不同, 但是我们还是能近似看成常规理解的栈帧, 包括入栈,出栈 局部变量等等

那么frameobejct里面究竟有什么?

# help(sys._getframe())
# Output:
class frame(object)
 .....            # 省略
 |  Data descriptors defined here:
 |  f_back        # 上一个栈帧对象(谁调用自己)
 |  f_builtins    # 内置名字空间
 |  f_locals      # 全局名字空间
 |  f_globals     # 全局名字空间
 |  f_code        # 帧指向的 codeObject对象
 .....            # 省略

我们现在已经知道frameobject的来历呢, 那么再回顾上面提到的: sys._getframe().f_code.co_name

毫无疑问, 我们还是得看下codeobject是什么东西, 才能知道name的意思:

同样也是print help大法

# print help(sys._getframe().f_code)
# Output:
class code(object)
 ......        # 省略
 |  Data descriptors defined here:
 |  
 |  co_name    # code block的名字, 通常是类名或者函数名 /* string (name, for reference) */ 
 |  
 |  co_names   # code block中所有的名字 /* list of strings (names used) */
 |
 ......        # 省略

虽然 sys._getframe().f_code.co_name 顶多也只能说明, 这段代码是在哪个code block里面, 并没有直接证明就是作用域, 但是从上面也已经谈到, 作用域是从代码正文的代码片段的决定, So, 也能近似看成算是作用域的名字了~

作用域话题似乎聊得有点深入了, 让我们暂告一段落, 继续讲讲 约束 和 作用域的关系吧

每个约束一旦创建, 将会持续的影响后面代码的执行, 但是约束也只能在名字空间内生效, 也就是说,一旦出了名字空间/作用域. 约束也将失效

a = 3
def f():
    a = 6
    print a    # 输出 6
f()
print a        # 输出 3

在上面例子可以看到, 变量a在模块层和函数f层都有赋值, 在执行函数f时,输出6, 但是在下面却输出了3, 也就是因为函数f 中的 a=3 约束只有在函数f的作用域中生效,函数结束,a的值, 应该是最开始的a=3来控制, 我们现在应该隐约有种感觉, 为什么赋值语句会被称为约束? 我们完全可以理解成, 一个变量名, 可能有多次改变其绑定的实体对象的机会, 但是最终显示是哪个实体, 完全就是从作用域->名字空间->约束 来决定

LEGB

从上面我们已经清楚 约束,名字空间, 作用域之间微妙的关系, 那么我们接下来就应该探讨下变量查找的方式了.

LEGB 分别是:

locals 是函数内的名字空间,包括局部变量和形参

enclosing 外部嵌套函数的名字空间(闭包中常见)

globals 全局变量,函数定义所在模块的名字空间

builtins 内置模块的名字空间
而查找的优先顺序从左到右以此是: L -> E -> G -> B

从上面我们已经知道, 约束, 是受作用域和名字空间的影响, 所以查找肯定也是只能在名字空间去进行

来些简单代码吧:

a = 3
def f():
    print a     # 输出 3
    print open  # 输出 
f()

print "----------------------分割线----------------"

a = 3
def f():
    def v():
        print a
    return v
test = f()
test()          # 输出 3

这段相信大家都知道为什么能够输出3, 当在函数内部的名字空间找不到关于变量a的约束时, 将会去全局变量的名字空间查到, OK, 已经找到了 (a,3)的约束, 返回 3., test()也是同理

同样的, 在函数内部和模块内部都不能找到open的约束, 那么只能去Bulitin(内置名字空间)去查找了, 找到了open了, 并且还是个函数, 所以返回

简单的演示完, 来些神奇的代码:

a = 3
def f():
    a = 4
    def v():
        print a
    return v
test = f()
test()     # 输出 4 Why?

有没有觉得很奇怪, a=4是在函数f里面定义的, 但是返回v的时候, 函数已经退出,理应释放了, 为什么test()还能输出4呢? 其实原因很简单, 首先这个已经是闭包函数了, 同样的还是遵循LEGB的原则, 函数v已经能够在外层嵌套作用域找到a的定义, 又因为闭包函数有个特点, 在构建的时候, 能够将需要的约束也一并绑定到自身里头, 所以即使函数f退出了, 变量a释放了, 但是不要紧, 函数v已经绑定好了相应的约束了, 自然而然也就能输出4
欢迎各位大神指点交流,转载请注明: https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38573.html

相关文章

  • Python 的命名空间

    摘要:真正管理这些名子的事物就是本文的主角命名空间。闭包命名空间闭包函数的名称空间引入。函数调用时产生新的局部命名空间函数返回结果抛出异常时释放命名空间,每一次递归都生成一个命名空间。标识符产生地点决定标识符所处的命名空间。 懒得扫全文的童鞋,可以直接跳到最后看总结。我们先从一个简单的栗子说起: 栗子 a 文件中有变量 va 以及类 A,b 文件导入 a 中class A ,并打印出 A: ...

    ralap 评论0 收藏0
  • 理解 PythonLEGB

    摘要:理解的名字空间的名字空间是一个非常核心的内容。在中提供了一个关键字来修改外部嵌套函数的名字空间,但是要使用才有,我等使用的只能眼馋一下。 理解 Python 的 LEGB 名字空间 Python 的名字空间是 Python 一个非常核心的内容。 其他语言中如 C 中,变量名是内存地址的别名,而在 Python 中,名字是一个字符串对象,它与他指向的对象构成一个{name:obje...

    FrozenMap 评论0 收藏0
  • 由一个例子到python的名字空间

    摘要:当程序引用某个变量的名字时,就会从当前名字空间开始搜索。对于可以看出已经被导入到自己的名字空间了,而不是在里面。因此并没有涉及到修改名字空间。按照原则,搜到有变量并且是个然后将其加入到自己的后面的就开始读取的元素,并没有影响的名字空间。 源自我的博客 前言 python里面最核心的内容就是:名字空间(namespace) 例子引入 例1 #!/usr/bin/env python #...

    XiNGRZ 评论0 收藏0
  • 一道题看PythonLEGB规则

    摘要:例题核心编程第二版变量作用域和命名空间一节有以下一道题目请问输出结果是什么要想解这道题,必须先了解中的一些概念的变量名解析机制有时称为。 例题 《核心编程(第二版)》变量作用域和命名空间一节有以下一道题目 # coding=utf-8 #!/usr/bin/env python def proc1(): j,k = 3,4 print j == %d and k ==...

    iamyoung001 评论0 收藏0
  • python变量与变量作用

    摘要:在函数中执行赋值操作时,会创建一个局部变量,如果想在函数中通过赋值改变一个全局变量,则需要用关键字申明,只要出现了操作符,则这个变量就是局部变量,除非显示申明为。 python变量与变量作用域 c语言中,变量的定义会为变量分配一块内存,变量的内存地址不会发生改变,当变量的值发生改变时,改变的是对应内存地址中的值。 python中,给变量赋值时,变量保存的是一个对象的引用,如果想改变变...

    chavesgu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<