Python 作用域(scope) 和 LEGB

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

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

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

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

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

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

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

那么frameobejct里面究竟有什么?

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

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

同样也是print help大法

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

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

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

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

 

LEGB

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

LEGB 分别是:

  • locals 是函数内的名字空间,包括局部变量和形参
  • enclosing 外部嵌套函数的名字空间(闭包中常见)
  • globals 全局变量,函数定义所在模块的名字空间
  • builtins 内置模块的名字空间

而查找的优先顺序从左到右以此是: L -> E -> G -> B

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

来些简单代码吧:

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

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

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

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

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部