如果我这样做 import A
从包含 A.py
的目录中和 A.so
, .so
文件将被导入。我有兴趣更改导入文件类型的顺序,以便 .py
优先于 .so
,虽然只是暂时的,即在代码行 i
之间和 j
.当然,这可以通过一些 importlib
来实现魔法?
目前我通过复制 .py
来解决这个问题。到一个单独的目录中,将此目录添加到 sys.path
然后进行导入,这太糟糕了。
为什么需要?.so
文件是 .py
的 cython 编译版本文件。我正在 cython 之上进行一些自定义代码转换,为此我需要导入 .py
来源即使“等效”.so
存在。
测试设置
下面是一个简单的测试设置。
# A.py
import B
# B.py
import C
print('hello from B')
# C.py
pass
运行
python A.py
成功打印出来自 B.py
的消息.现在添加 B.so
(因为 .so
文件的内容无关紧要,所以 B.so
真的是一个文本文件就可以了):# B.so
this is a fake binary
现在
python A.py
失败。虽然 importlib
是现代的做事方式,到目前为止我只知道如何使用已弃用的 imp
直接导入特定文件模块。更新A.py
到# A.py
import imp
B = imp.load_source('B', 'B.py')
让它再次工作。但是,引入
C.so
再次打破它,因为查找 .py
而不是 .so
未在导入机制中全局注册:# C.so
this is a fake binary
请注意,在此示例中,我只允许编辑
A.py
.我需要 Python 3.8 的解决方案,但我怀疑 3.x 的任何解决方案也适用于 3.8。
最佳答案
我现在有一个可行的解决方案。它有点hacky,但我认为它很健壮。
事实证明,sys.path_importer_cache
存储了各种查找器,这些查找器又存储了 list
的加载程序,这些加载程序按顺序由 import
挖掘。这些加载器存储为 2 元组,第一个元素正是给定加载器处理的文件扩展名。
我只是遍历所有 list
的加载器,并将扩展名为 .so
的加载器推到 list
的后面,实现尽可能低的优先级(我可以完全删除它们,但是我不能导入任何 .so
文件)。我跟踪对 sys.path_importer_cache
的更改,并在完成特殊导入后撤消它们。所有这些都整齐地包含在上下文管理器中:
import collections, contextlib, sys
@contextlib.contextmanager
def disable_loader(ext):
ext = '.' + ext.lstrip('.')
# Push any loaders for the ext extension to the back
edits = collections.defaultdict(list)
path_importer_cache = list(sys.path_importer_cache.values())
for i, finder in enumerate(path_importer_cache):
loaders = getattr(finder, '_loaders', None)
if loaders is None:
continue
for j, loader in enumerate(loaders):
if j + len(edits[i]) == len(loaders):
break
if loader[0] != ext:
continue
# Loader for the ext extension found.
# Push to the back.
loaders.append(loaders.pop(j))
edits[i].append(j)
try:
# Yield control back to the caller
yield
finally:
# Undo changes to path importer cache
for i, edit in edits.items():
loaders = path_importer_cache[i]._loaders
for j in reversed(edit):
loaders.insert(j, loaders.pop())
# Demonstrate import failure
try:
import A
except Exception as e:
print(e)
# Demonstrate solution
with disable_loader('.so'):
import A
# Demonstrate (wanted) failure outside with statement
import A2
请注意,要使
import A2
正确失败,您需要复制测试设置,以便您还有 A2.py
、 B2.py
、 C2.py
、 B2.so
和 C2.so
,它们以与原始测试文件相同的方式相互导入。只需在进行更改之前进行完整的备份
edits
并在完成后将此备份粘贴到 copy.deepcopy(sys.path_importer_cache)
即可摆脱涉及 sys
的有些复杂的簿记。它在上面的有限测试中确实有效,但由于导入机制的各个部分可能包含对不同嵌套对象的引用,我认为只使用突变更安全。
关于Python:更改导入文件类型的优先级(.py 在 .so 之前),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60174908/