如何使用__import__()导入本地模块?修改代码实现指定路径导入
一、先搞懂requests/package.py里那段代码的逻辑
那段代码完全是为了向后兼容——说白了就是让老代码里写from requests.packages import urllib3这种写法还能正常跑,不用修改。咱们一步步拆:
- 第一部分:把第三方模块导入到当前模块的命名空间
for package in ('urllib3', 'idna', 'chardet'): locals()[package] = __import__(package)
这循环做的事,相当于手动写了import urllib3、import idna、import chardet,然后把这些模块对象放到当前requests.packages模块的本地命名空间里,这样在这个模块里直接写urllib3就能访问到对应的模块了。
- 第二部分:给这些模块及其子模块创建别名到requests.packages下
for mod in list(sys.modules): if mod == package or mod.startswith(package + '.'): sys.modules['requests.packages.' + mod] = sys.modules[mod]
sys.modules是Python保存所有已导入模块的字典。这段代码遍历所有已加载的模块,找到属于urllib3(或idna、chardet)及其子模块的条目,然后给它们在sys.modules里新增一个以requests.packages.开头的别名。比如原来的urllib3.connectionpool模块,现在会有一个requests.packages.urllib3.connectionpool的别名指向同一个模块对象。
这么做的核心目的是:老代码里如果写了from requests.packages.urllib3 import PoolManager,现在依然能正常工作——因为这个别名指向的就是真实的urllib3模块,身份完全一致(同一个对象),不会出现因路径不同导致的模块重复加载、状态不一致等奇怪问题。
二、用__import__()导入本地模块
__import__是Pythonimport语句背后实际调用的内置函数,导入本地模块要注意两个关键点:
基础用法(导入顶级模块)
如果只是导入最顶层的模块,直接传模块名就行:
# 等价于 import msazurensg msazurensg = __import__('msazurensg')
导入子模块(重点)
如果要导入深层子模块(比如msazurensg.lib.urllib),必须指定fromlist参数,否则只会返回最顶层的模块:
# 等价于 import msazurensg.lib.urllib urllib_mod = __import__('msazurensg.lib.urllib', fromlist=['']) # 现在urllib_mod就是你封装的msazurensg.lib.urllib模块对象
要是想实现from msazurensg.lib import urllib的效果,可以这么写:
# 等价于 from msazurensg.lib import urllib lib_module = __import__('msazurensg.lib', fromlist=['urllib']) urllib = lib_module.urllib
简单说:fromlist参数会告诉__import__要返回的是子模块所在的父模块(或子模块本身),这样你才能拿到深层的子模块对象,而不是只拿到最顶层的包。
三、实现from msazurensg.lib import urllib的导入效果
要让用户能直接这么导入,最规范的做法是利用Python包的__init__.py文件:
- 确保你的目录结构正确
你的项目结构应该是这样的:
msazurensg/ __init__.py lib/ __init__.py urllib.py # 或者是一个urllib目录,里面有__init__.py
- 在msazurensg/lib/init.py里暴露urllib模块
打开lib/__init__.py,添加一行:
from . import urllib
这样,当用户执行from msazurensg.lib import urllib时,Python会从lib包的命名空间里找到urllib这个模块对象——也就是你封装的那个版本。
如果你的urllib是在子目录里(比如lib/urllib/__init__.py),那同样在lib/__init__.py里写from .urllib import *或者from . import urllib就行,具体看你想暴露哪些内容。
内容的提问来源于stack exchange,提问作者Ahtesham Akhtar

