如果我从包含A.py
和A.so
的目录中import A
,则将导入.so
文件。我对更改导入文件类型的顺序感兴趣,以便.py
优先于.so
,尽管只是暂时的,即在代码行i
和j
之间。这肯定可以通过一些importlib
魔法来实现吗?
目前,我通过将.py
复制到一个单独的目录中来解决此问题,在此目录前面sys.path
然后进行导入,这太糟糕了。
为什么需要?
.so
文件是.py
文件的cython编译版本。我正在 cython 之上进行一些自定义代码转换,即使存在"等效".so
,我也需要导入.py
源。
测试设置
下面是一个简单的测试设置。
# 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。
我现在有一个可行的解决方案。它有点笨拙,但我认为它很强大。
事实证明,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
,它们以与原始测试文件相同的方式相互导入。
人们可以通过在进行更改之前进行完整的备份copy.deepcopy(sys.path_importer_cache)
,并在完成后将此备份粘贴到sys
上,从而摆脱涉及edits
的有些复杂的簿记。它在上面的有限测试中确实有效,但由于导入机制的各个部分可能包含对不同嵌套对象的引用,我认为仅使用突变更安全。