Python模块维护

模块

在Python中,一个.py文件,就是一个模块。

  CPython虚拟机自己模拟了进程和线程的概念。在Cpython初始化运行环境的时候,会创建一个空的进程,进而在进程中创建一个空的线程。CPython对模块维护的策略是:

Python中所有的模块都被维护在进程中,而所有的线程共享进程中的模块资源。

  CPython用一个结构体PyInterpreterState来表示虚拟进程,这里只列出比较重要的几行,若要查看完整代码,请参阅CPython源码中的Include/pystate.h文件:

typedef struct _is {

    //......
    PyObject *modules;              //维护所有的模块
    PyObject *modules_by_index;     
    PyObject *sysdict;              //sys
    PyObject *builtins;             //builtins
    //......
} PyInterpreterState;
  • modules:维护了进程中所有的modules(包括内建和用户自定义模块),维护在一个字典中。
  • sysdict:我们最熟悉的sys内置模块,维护在一个字典中。
  • builtins:builtins内置模块,维护在一个字典中。

  它们三者之间的关系,可以用如下图来描述(点击查看大图:模块维护图):

  默认情况下,CPython只会将builtins模块添加到名字空间中,就是__builtins__。我们无法直接访问modules,但是从图中可以看出,sys中维护了一个字段,叫modules,该字段正好指向了modules

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>>
>>> import sys
>>> type(sys.modules)
<class 'dict'>
>>> sys.modules.keys()
dict_keys(['_collections', 'importlib', '_heapq', 'os.path', 'warnings', 'importlib._bootstrap_external', 'weakref', '_sitebuiltins', 'site', 'types', 'itertools', 'ntpath', 'encodings', 'marshal', 'importlib._bootstrap', '_warnings', '_frozen_importlib', 'abc', 'os', '_codecs_cn', '_stat', 'operator', 'encodings.latin_1', 'collections', '_collections_abc', 'mpl_toolkits', '_imp', 'contextlib', '__main__', 'reprlib', 'nt', 'sysconfig', '_multibytecodec', 'builtins', 'codecs', 'importlib.abc', 'io', 'heapq', 'zipimport', 'collections.abc', 'genericpath', '_io', '_codecs', '_thread', '_locale', 'encodings.gbk', '_bootlocale', 'encodings.utf_8', 'functools', 'atexit', '_functools', 'sys', 'importlib.machinery', '_weakrefset', 'importlib.util', '_frozen_importlib_external', 'keyword', 'errno', 'winreg', '_signal', '_operator', 'encodings.aliases', 'stat', 'encodings.mbcs', '_weakref'])
>>>

  从sys.modules.keys()可以看到进程中维护的所有模块,比如,__main__模块,sys模块(由此可见,modules中也维护了一个字段,该字段指向了syssys模块和modules模块互指)。
  CPython中的模块是维护在一个PyModuleObject结构体中的,该结构体的md_dict指向一个字典,该字典中维护了该模块下的所有符号。当然,CPython还会在md_dict中添加两个字段:__name____doc__,分别用来表示该模块的名字和文档说明:

typedef struct {
    PyObject_HEAD
    PyObject *md_dict;
} PyModuleObject;

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,调用用户自定义函数:

>>> def dir():
...     print("this is my dir function")
...
>>> dir()
this is my dir function
>>>

  那么,到底什么是builtins模块呢?我们需要深入去探讨一下。CPython在创建完进程和线程后,所做的第一步事情就是创建builtins模块,所做的操作就是将所有的内建函数添加到该模块中,最后,将builtins模块中的__name__赋值为builtins。我们可以在sys.modules中看到这个模块:

>>>import sys
>>>
>>> sys.modules['builtins']
<module 'builtins' (built-in)>
>>> sys.modules['builtins'].__name__
'builtins'

默认情况,builtins模块并没有被加载到globals名字空间中。但是,Python定义了一个魔术属性__builtins__来访问该模块。我想这么做是为了保持模块操作的一致性。

>>> import sys
>>> id(sys.modules['builtins'])
22373696
>>> id(__builtins__)
22373696
>>>

  最后,需要注意的是,当模块不被当做主模块的时候,其globals名字空间中的__builtins__不再指向builtins模块,而是指向了builtins模块的md_dict

小结

  • __builtin__在Python3中被更名为builtins,该模块被维护在进程结构体的modules中;
  • __builtins__作为一个魔法属性,可以访问到内建builtins模块;
  • 若是在主模块中,__builtins____builtin__(builtins)都指向同一个内建模块;若是被importmodule,其__builtins__则等价于__builtin__.__dict__(builtins.__dict)。原因不详;

__main__模块

在Python中,当前运行的模块,被叫做主模块。

  __main__模块是指向自身的模块,默认情况下,该module也未被import。但是,我们可以通过sys.modules访问到它:

>>> import sys
>>> sys.modules['__main__']
<module '__main__' (built-in)>
>>>

  当然,我们也可以将其import

>>> import __main__
>>> __main__.__name__
'__main__'
>>> id(__name__)
30132128
>>> id(__main__.__name__)
30132128
>>>

  由此可见,__name____main__.__name__就是指向的同一个字符串。因为主模块的名字属性被改为了'__main__',所以,如果我们想知道主模块的文件名字,可以使用如下方法:

# hello.py
import __main__
print(__main__.__file__)

'''
>python hello.py
hello.py
'''