问题
标准库清楚地记录 了如何直接导入源文件(给定源文件的绝对文件路径),但如果该源文件使用隐式兄弟导入,则此方法不起作用,如下例所述。
该示例如何适用于存在隐式同级导入的情况?
我已经检查了 这个 和 这个关于该主题的其他 Stackoverflow 问题,但它们没有解决手动导入文件 中 的隐式同级导入。
设置/示例
这是一个说明性的例子
目录结构:
root/
- directory/
- app.py
- folder/
- implicit_sibling_import.py
- lib.py
app.py
:
import os
import importlib.util
# construct absolute paths
root = os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
isi_path = os.path.join(root, 'folder', 'implicit_sibling_import.py')
def path_import(absolute_path):
'''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''
spec = importlib.util.spec_from_file_location(absolute_path, absolute_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
isi = path_import(isi_path)
print(isi.hello_wrapper())
lib.py
:
def hello():
return 'world'
implicit_sibling_import.py
:
import lib # this is the implicit sibling import. grabs root/folder/lib.py
def hello_wrapper():
return "ISI says: " + lib.hello()
#if __name__ == '__main__':
# print(hello_wrapper())
运行 python folder/implicit_sibling_import.py
与 if __name__ == '__main__':
块注释掉产量 ISI says: world
在 Python 3.6 中。
但是运行 python directory/app.py
产生:
Traceback (most recent call last):
File "directory/app.py", line 10, in <module>
spec.loader.exec_module(module)
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
File "/Users/pedro/test/folder/implicit_sibling_import.py", line 1, in <module>
import lib
ModuleNotFoundError: No module named 'lib'
解决方法
If I add import sys; sys.path.insert(0, os.path.dirname(isi_path))
to app.py
, python app.py
yields world
as intended, but I would like to avoid munging the sys.path
如果可能的话。
回答要求
我想 python app.py
打印 ISI says: world
我想通过修改 path_import
函数来实现。
我不确定 mangling sys.path
的含义。例如。 if there was directory/requests.py
and I added the path to directory
to the sys.path
, I wouldn’t want import requests
to start importing directory/requests.py
而不是导入我用 pip install requests
安装的 请求库。
该解决方案 必须 作为 python 函数实现,该函数接受所需模块的绝对文件路径并返回 模块对象。
理想情况下,解决方案不应引入副作用(例如,如果它确实修改了 sys.path
,它应该返回 sys.path
到其原始状态)。如果解决方案确实引入了副作用,它应该解释为什么不引入副作用就无法实现解决方案。
PYTHONPATH
如果我有多个项目这样做,我不想每次在它们之间切换时都必须记住设置 PYTHONPATH
。用户应该能够 pip install
我的项目并在没有任何额外设置的情况下运行它。
-m
-m
标志 是推荐的/pythonic 方法,但标准库也明确记录 了如何直接导入源文件。我想知道如何调整这种方法来处理隐式相对导入。显然,Python 的内部结构必须这样做,那么内部结构与“直接导入源文件”文档有何不同?
原文由 Pedro Cattori 发布,翻译遵循 CC BY-SA 4.0 许可协议
我能想到的最简单的解决方案是在执行导入的函数中临时修改
sys.path
:除非您同时在另一个线程中导入,否则这不会导致任何问题。否则,由于
sys.path
已恢复到以前的状态,因此应该没有不需要的副作用。编辑:
我意识到我的回答有些不尽如人意,但是,深入研究代码表明,行
spec.loader.exec_module(module)
基本上导致exec(spec.loader.get_code(module.__name__),module.__dict__)
被调用。这里spec.loader.get_code(module.__name__)
只是lib.py中包含的代码。因此,该问题的更好答案必须找到一种方法,使
import
语句的行为有所不同,只需通过 exec 语句的第二个参数注入一个或多个全局变量。然而,“无论你做什么来使导入机制在该文件的文件夹中查找,它都必须在初始导入的持续时间之后继续存在,因为当你调用它们时,来自该文件的函数可能会执行进一步的导入”,如@所述user2357112 在问题评论中。不幸的是,改变
import
语句行为的唯一方法似乎是改变sys.path
或在一个包中__path__
。module.__dict__
已经包含__path__
所以这似乎不起作用留下sys.path
(或者试图弄清楚为什么代码执行包甚至不处理虽然它有__path__
和__package__
… - 但我不知道从哪里开始 - 也许它与没有__init__.py
文件有关)cb– .此外,这个问题似乎并不特定于
importlib
而是 同级导入 的一般问题。Edit2: 如果您不希望模块以
sys.modules
结束,则以下应该有效(请注意,在导入过程中添加到sys.modules
的任何模块都将被 _删除_):