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

标签 python python-3.x import python-import python-importlib

如果我这样做 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.pyB2.pyC2.pyB2.soC2.so ,它们以与原始测试文件相同的方式相互导入。

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

关于Python:更改导入文件类型的优先级(.py 在 .so 之前),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60174908/

相关文章:

python - 使用 BeautifulSoup 从网页中提取某些内容时遇到问题

python - 面临属性错误: for 'tag_' using Spacy in Python

python-3.x - Python3中的CSV文件比较算法

import - 将 Plastic SCM 与公共(public)代码托管服务结合使用的工作流程

python - 导入错误 : No module named statsmodels

python - 如何每周从 pandas 数据框中提取唯一值

基于libevent或类似技术的Python http代理库?

python - 使用 python Spark 映射另一个文件

csv - 如何将密码和登录数据导入 firefox?

python - Django 不收集静态文件