一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
今天我们的选择越来越多,不过由于每个人精力的有限,仿佛我们选择又不是那么多,所以现在如何选择变得更为重要,今天在编程领域,可用语言非常多,如何选择一个适合自己领域的语言,以及如何选择一个更有价值语言,所谓价值对于自己来就是带来更高收益。
module 对象
python 这门语言看起来比较简单,容易上手,特别是对于已有其他语言编程基础的人,会觉得 python 这门语言并不复杂,其实不然。今天我们就来说一说 python 中 module,我们创建一个文件 module_one.py 代码如下
print("starting module one")
x = 21
def say_hi():
print("hello world")
在 python 中, module 也是一个对象(object), 引入一个模块其实主要做了 2 件事,首先在内存中为模块开辟一段空间,模块名称做键值保存在当前 global namespace dictionary 也就是全局命名空间字典里, 值这是指向这个模块的内存地址。这样行为和使用赋值符号为变量进行赋值并没有什么区别。
{'__builtins__': <module '__builtin__' (built-in)>, 'module_one': <module 'module_one' from 'module_one.pyc'>, 'x': 27, '__name__': '__main__', '__package__': None, '__doc__': None}
这里 module_one 是模块名称,对应键值为模块的地址
'module_one': <module 'module_one' from 'module_one.pyc'>,
可以 type 来查看一下模块类型,显示类型为 module 如下
>>> type(module_one)
<type 'module'>
在 python 中,module object 是 types.ModuleType 类的一个实例
>>> import types
>>> isinstance(module_one,types.ModuleType)
True
和其他 python 对象(object)一样,可以通过 id(module_one) 来获取存放 module_one 对象的地址。
hex(id(module_one))
既然我们知道了 module 是 types.ModuleType 类来创建一个 module,第一参数是 module 名称,二个参数 module 的类型
>>> mod = types.ModuleType('mod','this is a test module')
>>> type(mod)
<type 'module'>
也可以为模块起一个别名,这里对于 module_one 的别名都是指向同一块内存地址的变量
import module_one
mod_one = module_one
print(mod_one)
print(hex(id(module_one)))
print(hex(id(mod_one)))
module 属性
我们创建一个 module_two.py 文件,然后在文件中,创建一个全局变量 x 和一个方法 my_func ,然后在方法 my_func 中输出一个变量 __file__,不难发现我们并没有定义 __file__ 这个变量
x = 21
def my_func():
print(__file__)
当我们在其他文件中引入 module_two 这个模块,然后我们用 python 提供 dir 查看 module_two 的属性。
>>> import module_two
>>> dir(module_two)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'my_func', 'x']
>>>
通过查看 module_two 属性,我们发现当一个文件做 module 导入到另一个文件作为 module 对象使用,其全局定义变量和方法称为这个 module 对象的属性。
同时这里 python 还为这个 module 对象创建了例如 __name__ 和 __file__ 这样属性。这些属性可以作为全局变量来访问,
我们可以通过 module_two.__dict__ 可以方法模块的一些属性值
{... '__file__': 'module_two.pyc', '__package__': None, 'my_func': <function my_func at 0x107fa1398>, 'x': 21, '__name__': 'module_two', '__doc__': None}
>>> module_two.__dict__['x']
21
>>> module_two.__dict__['__name__']
'module_two'
>>>
python 还提供了更直接方法来访问这些属性就是module_two.x 来直接访问这些属性值。
>>> import module_two
>>> module_two.x
21
>>> module_two.__file__
'绝对路径/module_two.py'
>>> module_two.my_func()
绝对路径/module_two.py
>>> module_two.__dict__['my_func']
<function my_func at 0x10b7b04c0>
>>> module_two.__dict__['my_func']()
绝对路径/module_two.py
这里值得注意,导入 module 并不一定都是 python 编写的 module, 其中还包括 c 或者 c++ 编译好的文件
>>> import math
>>> math.__file__
'路径/lib/python3.8/lib-dynload/math.cpython-38-darwin.so'
Finder 和 Loader
在 python 中,sys 模块的 meta_path 提供了一些导入 module 的方法,接下里我们就来看一看。
>>> import sys
>>> sys.meta_path
[<class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]
sys 的 meta_path 有一系列对象(object) 在 python 导入系统中,有finder 和 loader 两个对象,所以想要理解这些对象,我们首先需要清楚什么是 finder 和 loader,从名字不难看出,finder 是负责定位 module 而 loader 则是负责加载 module。
finder 是一个类,具有一个实例方法 find_spec(fullname,path,target=None) 而 loder 是一个具有实例方法load_module(fullname,)
在 meta_path 中所有对象都是实现了 find_spec 方法,所以他们都是 finder 的具体实现。有的对象既实现了 finder 又实现了 loader 例如 Importer
通过名字不看看出 BuiltinImporter 和 FrozenImporter 是 Importer 而 PathFinder 是 finder
>>> importer = sys.meta_path[0]
>>> importer
<class '_frozen_importlib.BuiltinImporter'>
>>> sys.builtin_module_names
('_abc', '_ast', '_codecs', '_collections', ... 'builtins', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'posix', 'pwd', 'sys', ... )
>>> spec = importer.find_spec('itertools')
>>> spec
ModuleSpec(name='itertools', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in')
>>>
首先我们将 sys.meta_path[0] 用变量 importer 来引用,查看类型这是内置模块,内置模块不同于其他 python 库,内置模块是会编译到解释器中。我们可以通过 sys.builtin_module_names 来查看内置模块。
importer 是一个 finder 的实例,所以可以调用 find_spec 来定位要加载模块,我们输出 spec 这里给出使用了哪一个 finder _frozen_importlib.BuiltinImporter
>>> spec.loader.load_module('itertools')
<module 'itertools' (built-in)>
我们回头看一看 find_spec 返回对象 spec 是一个 ModuleSpec 实例,其中一个属性为 loader ,所以我们可以直接使用 loader 属性来对 itertools 进行加载
这里有点 confusing,也就是 spec.loader 和 importer 其实是一个
>>> spec.loader == importer
True
>>> importer.load_module('itertools')
<module 'itertools' (built-in)>
pathFinder
pathFinder 可以不同目录下来搜索 module ,都包括哪些路径,可以通过 sys.path 来查看其搜索范围。
>>> importer = sys.meta_path[-1]
>>> importer
<class '_frozen_importlib_external.PathFinder'>
>>> sys.path
['', '路径/py38/lib/python38.zip',
'路径/py38/lib/python3.8',
'路径/py38/lib/python3.8/lib-dynload',
'路径/py38/lib/python3.8/site-packages']
>>> spec = importer.find_spec('socket')
>>> spec
ModuleSpec(name='socket', loader=<_frozen_importlib_external.SourceFileLoader object at 0x10b76ba00>, origin='/路径/lib/python3.8/socket.py')
>>>
module 加载过程
当执行 import 语句来导入一个 module 时,首先 python 会在全局缓存中搜索是否已经加载过了该 module ,这个全局缓存是一个名字为 module 名,值为 module 对象的键值对的字典。如果 module 已经存在于缓存中,则立即返回。 如果在 cache 中找不到 python 就会通过 finder 和 loader 来定位加载该 module,loader 在内存中创建一个 module 对象
>>> sys.modules
通过 sys.module 可以来查看已经加载的模块,
>>> del module_two
就是在全局命名空间将 module_two 移除指向 module_two 对象内存地址的变量而已,当我们再次调用 import module_two 不会重新对 module_two 对象进行编译。
我们可以在 sys.modules 添加一个模块,然后将模块赋值给函数,随后导入这个模块,输出其 type 发现这个不再是 module 类而是一个 function 然后就可以直接调用