gpt4 book ai didi

Python 3.5+ : How to dynamically import a module given the full file path (in the presence of implicit sibling imports)?

转载 作者:IT老高 更新时间:2023-10-28 22:09:27 27 4
gpt4 key购买 nike

问题

标准库明确记录how to import source files directly (给定源文件的绝对文件路径),但如果源文件使用下面示例中描述的隐式同级导入,则此方法不起作用。

如果存在隐式同级导入,该示例如何适应工作?

我已经 checkout thisthis other 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__': block 产生 ISI says: world in 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'

解决方法

如果我添加 import sys; sys.path.insert(0, os.path.dirname(isi_path))app.py, python app.py 产生 world 符合预期,但我想尽可能避免修改 sys.path

回答要求

我希望 python app.py 打印 ISI says: world 并且我想通过修改 path_import 来完成此操作功能。

我不确定修改 sys.path 的含义。例如。如果有 directory/requests.py 并且我将 directory 的路径添加到 sys.path,我不会想要 import requests 开始导入 directory/requests.py 而不是导入 requests library我使用 pip install requests 安装的。

解决方案必须实现为接受所需模块的绝对文件路径并返回 module object 的 python 函数。 .

理想情况下,解决方案不应引入副作用(例如,如果它确实修改了 sys.path,它应该将 sys.path 返回到其原始状态)。如果解决方案确实引入了副作用,它应该解释为什么不引入副作用就无法实现解决方案。


PYTHONPATH

如果我有多个项目这样做,我不想每次在它们之间切换时都必须记住设置 PYTHONPATH。用户应该能够pip install 我的项目并运行它而无需任何额外的设置。

-m

-m flag是推荐的/pythonic 方法,但标准库也明确记录了 How to import source files directly .我想知道如何调整这种方法来应对隐式相对导入。显然,Python 的内部必须这样做,那么内部和“直接导入源文件”文档有何不同?

最佳答案

我能想到的最简单的解决方案是在执行导入的函数中临时修改 sys.path:

from contextlib import contextmanager

@contextmanager
def add_to_path(p):
import sys
old_path = sys.path
sys.path = sys.path[:]
sys.path.insert(0, p)
try:
yield
finally:
sys.path = old_path

def path_import(absolute_path):
'''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''
with add_to_path(os.path.dirname(absolute_path)):
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

除非您同时在另一个线程中进行导入,否则这不会导致任何问题。否则,由于 sys.path 已恢复到以前的状态,因此应该不会有任何副作用。

编辑:

我意识到我的回答有些不令人满意,但是,深入研究代码表明,spec.loader.exec_module(module) 行基本上会导致 exec(spec.loader.get_code (module.__name__),module.__dict__) 被调用。这里的 spec.loader.get_code(module.__name__) 就是 lib.py 中包含的代码。

因此,要想更好地回答这个问题,就必须找到一种方法,通过简单地通过 exec 语句的第二个参数注入(inject)一个或多个全局变量来使 import 语句的行为有所不同。但是,“无论您如何使导入机制在该文件的文件夹中查找,它都必须在初始导入的持续时间之外徘徊,因为该文件中的函数可能会在您调用它们时执行进一步的导入”,如 @ 所述user2357112 在问题评论中。

不幸的是,改变 import 语句行为的唯一方法似乎是改变 sys.path 或包中的 __path__module.__dict__ 已经包含 __path__ 所以这似乎不起作用留下 sys.path (或试图找出为什么 exec 不将代码视为一个包,即使它具有 __path____package__ ... - 但我不知道从哪里开始 - 也许它与没有__init__.py 文件)。

此外,这个问题似乎不是 importlib 所特有的,而是 sibling imports 的一般问题。 .

Edit2: 如果您不希望模块以 sys.modules 结尾,以下应该可以工作(请注意,添加到 sys.modules 的任何模块)导入期间的模块移除):

from contextlib import contextmanager

@contextmanager
def add_to_path(p):
import sys
old_path = sys.path
old_modules = sys.modules
sys.modules = old_modules.copy()
sys.path = sys.path[:]
sys.path.insert(0, p)
try:
yield
finally:
sys.path = old_path
sys.modules = old_modules

关于Python 3.5+ : How to dynamically import a module given the full file path (in the presence of implicit sibling imports)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41861427/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com