12月28, 2020

62. Python 是如何实现作用域的(2)

我们已经找到了一些特定的语句,它们会调用 symtable_enter_block 函数,创建新的 Symbol Table Entry:

  • FunctionDef_kind,函数定义语句;
  • ClassDef_kind,类定义语句;
  • AsyncFunctionDef_kind,Async 函数定义语句;
  • Lambda_kind, Lambda 定义语句;
  • symtable_handle_comprehension函数,作用尚不清楚,待研究。

它们开启了独立的代码 block,用来做什么呢?每一个 ste 都有一个 symbols 成员,类型为 Dict,每创建一个独立的 ste 就同时创建了对应的 symbols,从名字也看的出来,这里面保存的就是这个 block 或者说 scope 的所有名称。

最顶层 module 的 Symbols

symtable_enter_block 函数凭借一己之力管理进入代码块的准备工作,所有的进入所有 block 的流程差别不大:

image.png

当 st->st_cur 被更新时,之后的工作就发生在这个新的 ste 中了,新发现的名称就都归这个 set->symbols 了。

需要注意的是,每一个 ste 注定了都有各自的 Symbols Dict,但有一个 ste 比较特殊,就是最顶层的 Module。在 Python 中,每个的最高作用域就是文件本身,Python 也将整个文件解析为 Module 类型节点,那么 Module 节点的 Symbols 必然就是整个作用域的 global。

所以,如果当前 block 是 Module,说明 Python 源码的解析工作刚开始,新创建的 ste->symbols 同时也被作为整个 st 的 global 使用,它们俩就是同一个对象。

换言之,python 源码文件最顶层作用域的 local 就是 该文件的 global,它俩是一回事。

进入 block 之后

正如前面所提到的,当 st->st_cur 被更新的那一刻起,所有的局部名称都会加入新的 symbols Dict。这个操作实际上由:

// Python/symtable.c:1018

static int
symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _symtable_entry *ste)

函数执行。它的工作流程也很简单,将名称插入到 ste->ste_symbols 字典中,并且根据命名的类型,还会更新下面两个字典:

  • 名称是参数,则插入 ste->ste_varnames 字典;
  • 名称被声明为 global 的,插入 st->st_global 字典;

可以看出一个 ste 的 Symbols 中记录着当前 block 中定义的所有名称,包括局部变量的名称、全局变量名称和参数名称在内。

意外收获:类的私有成员实现

symtable_add_def_helper 函数的实现决定了 Python 中所有名称的面貌,在 symtable_add_def_helper 的最开始,传入的命名都必须先通过下面函数的休整:

PyObject *
_Py_Mangle(PyObject *privateobj, PyObject *ident)
{
    /* Name mangling: __private becomes _classname__private.
       This is independent from how the name is used. */
    // ··· ···
}

这里就不再展开它的实现,从注释可以知道,该函数会改写类私有成员的命名。

Python 中通过类成员的命名以双下划线 (_ _,实际上是连在一起的,这里分开打印,便于识别) 开头的约定实现 private 属性,如果试图访问类中的 Class.__attr 会抛出属性不存在异常。

image.png

可以看到私有成员的名称被改写为 "_Class__attr" 这样,比如上图中的 “_A__private”。这个工作就是由 _Py_Mangle 完成的。

知道这一切,我们实际上可以通过这样的语句访问类的私有成员,接着上面的例子:

>>> a._A__private
'Private attr'

看起来很魔法的特性,实现方案却非常简单直接,就是最简单的字符串操作,意不意外!

本文链接:http://www.thinkinpython.com/post/deep_python_vm_62.html

-- EOF --