在 python 中,详解 import 机制(1)

230 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

一门语言如果想要做大做强,就需要一套模块管理系统,例如 js 的流形少不了 npm 这个 package 管理工具,rust 有 cargo 而 golang 语言之前一直被吐槽,就是没有一个官方 package 管理工具。

python 程序通常需要引入一些标准库或者第三方的库,同时 project 也需要以 module 方式进行组织管理。所以理解 python 的 import 机制来以及如何管理 module 是开发人员面临的一个问题,sys 包提供 import 机制帮助导入module。

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

在之前分享过几篇关于 python 中导入 module 背后都发生了什么的几篇文章,文章内容稍微有点零散,今天尝试整理一下思路。

import socket
import numpy as np
from tqdm import tqdm

加载模块过程是由 __import__() 来完成,

  • 首先会在 sys.modules去搜索要加载 module 如果 module 已经加载过,那么就可以直接从 sys.modules 缓存中获取这个 module
  • 通常我们要导入 module 的路径是一个层次化,有类似文件路径,所以也就是根据 module 路径先从最顶层目录开始找起,那么在加载过程中,首先会遍历 sys.meta_path 的 MetaPathFinder 来选择一个合适的 Finder 可以认为是搜索器,选择好 finder 后,可以调用 finder 的 find_module 方法,并将 path 传入到这个函数,这个函数会返回一个 loader 对象,这个 loader 对象有 load_module 方法来加载想要加载 module
  • 加载 module 还会将 module 存在 sys.modules 这个缓存,当下一次再次加载该 module 就直接从 sys.modules 获取这个模块,避免重复加载同一模块。
>>> print(sys.meta_path)
[<class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]
  • BuiltinImporter 用于加载内核级别的 module 例如 sys,os 这样 module
  • FrozenImporter 用于导入 frozen bytecode 模块,其实这些 module 已经被编译到 python 的解释器中,这个 finder 通常只用于初始化 Python 的导入机制。直接加载不 python 解释器就可以运行的模块
  • PathFinder 从名字上我们就知道这是一个支持路径检索。这是一种基于文件系统的资源加载来实现导入,可以用来导入.py、.pyc 和 .zip 文件。这是一个 meta path finder,大多数 module 的导入都是这种方式
class CustomFinder(object):
    def find_module(self, fullname,path=None):
        print(f"Looking for {fullname},{path}")
        return None


import sys
sys.meta_path.insert(0,CustomFinder())

import math
import xml.etree.ElementTree

可以自己定义一个 CustomFinder 需要在 class 内部实现一个 find_module 方法, 这方法应该会被解释器调用,调用函数时候会注入两个参数。然后在函数内部简单输出一下这两参数.然后在 sys.meta_path对其进行注册,然后我们 import 语句来尝试导入两个模块分别是 math 和 xml.etree.ElementTree。

print(sys.meta_path)

可以通过 sys.meta_path 命令来搜索到 CustomFinder 已经存在于 meta_path 列表中。

[<__main__.CustomFinder object at 0x10c0d8430>, <class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]

我们发现都能够搜索 xml 路径返回 None 为什么还会继续向下查找呢?这是因为如果 CustomFinder 就继续向下通过其他 finder 去查找。

Looking for xml,None
Looking for xml.etree,['/anaconda3/envs/py38/lib/python3.8/xml']
Looking for xml.etree.ElementTree,['/anaconda3/envs/py38/lib/python3.8/xml/etree']