Python:更改导入文件类型的优先级(.py .so 之前)



如果我从包含A.pyA.so的目录中import A,则将导入.so文件。我对更改导入文件类型的顺序感兴趣,以便.py优先于.so,尽管只是暂时的,即在代码行ij之间。这肯定可以通过一些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.pyB2.pyC2.pyB2.soC2.so,它们以与原始测试文件相同的方式相互导入。

人们可以通过在进行更改之前进行完整的备份copy.deepcopy(sys.path_importer_cache),并在完成后将此备份粘贴到sys上,从而摆脱涉及edits的有些复杂的簿记。它在上面的有限测试中确实有效,但由于导入机制的各个部分可能包含对不同嵌套对象的引用,我认为仅使用突变更安全。

最新更新