模块
在Python中,一个.py文件,就是一个模块。
CPython虚拟机自己模拟了进程和线程的概念。在Cpython初始化运行环境的时候,会创建一个空的进程,进而在进程中创建一个空的线程。CPython对模块维护的策略是:
Python中所有的模块都被维护在进程中,而所有的线程共享进程中的模块资源。
CPython用一个结构体PyInterpreterState来表示虚拟进程,这里只列出比较重要的几行,若要查看完整代码,请参阅CPython源码中的Include/pystate.h文件:
modules:维护了进程中所有的modules(包括内建和用户自定义模块),维护在一个字典中。
sysdict:我们最熟悉的sys内置模块,维护在一个字典中。
builtins:builtins内置模块,维护在一个字典中。
它们三者之间的关系,可以用如下图来描述(点击查看大图:模块维护图):
默认情况下,CPython只会将builtins模块添加到名字空间中,就是__builtins__。我们无法直接访问modules,但是从图中可以看出,sys中维护了一个字段,叫modules,该字段正好指向了modules:
从sys.modules.keys()可以看到进程中维护的所有模块,比如,__main__模块,sys模块(由此可见,modules中也维护了一个字段,该字段指向了sys,sys模块和modules模块互指)。
CPython中的模块是维护在一个PyModuleObject结构体中的,该结构体的md_dict指向一个字典,该字典中维护了该模块下的所有符号。当然,CPython还会在md_dict中添加两个字段:__name__和__doc__,分别用来表示该模块的名字和文档说明:
builtins模块
在Python2中,builtins模块叫__builtin__(不加s)。以下叙述,我们使用builtins。
不论是在交互式命令行中,还是在文件中,我们都可以直接使用一些函数,比如dir()。当输入dir()的时候,Python会在名字空间中寻找dir这个符号,Python的名字空间分为:
locals名字空间
globals名字空间
builtin名字空间
Python会沿着locals->globals->builtin路径寻找符号。使用__builtins__.__dict__.keys()就可以看到builtin名字空间中维护的符号。
最后,在builtin名字空间中找到了符号dir。当然,我还可以重定义dir函数,这样在locals名字空间中,就会维护一个符号dir,因为Python会沿着locals->globals->builtin路径进行寻找,所以,最先在locals中找到dir,调用用户自定义函数:
那么,到底什么是builtins模块呢?我们需要深入去探讨一下。CPython在创建完进程和线程后,所做的第一步事情就是创建builtins模块,所做的操作就是将所有的内建函数添加到该模块中,最后,将builtins模块中的__name__赋值为builtins。我们可以在sys.modules中看到这个模块:
默认情况,builtins模块并没有被加载到globals名字空间中。但是,Python定义了一个魔术属性__builtins__来访问该模块。我想这么做是为了保持模块操作的一致性。
最后,需要注意的是,当模块不被当做主模块的时候,其globals名字空间中的__builtins__不再指向builtins模块,而是指向了builtins模块的md_dict。
小结:
__builtin__在Python3中被更名为builtins,该模块被维护在进程结构体的modules中;
__builtins__作为一个魔法属性,可以访问到内建builtins模块;
若是在主模块中,__builtins__和__builtin__(builtins)都指向同一个内建模块;若是被import的module,其__builtins__则等价于__builtin__.__dict__(builtins.__dict)。原因不详;
__main__模块
在Python中,当前运行的模块,被叫做主模块。
__main__模块是指向自身的模块,默认情况下,该module也未被import。但是,我们可以通过sys.modules访问到它:
当然,我们也可以将其import:
由此可见,__name__和__main__.__name__就是指向的同一个字符串。因为主模块的名字属性被改为了'__main__',所以,如果我们想知道主模块的文件名字,可以使用如下方法: