【问题标题】:Python: Force import to prefer .py over .soPython:强制导入更喜欢 .py 而不是 .so
【发布时间】:2019-09-28 04:40:53
【问题描述】:

我有一种情况,同一个 Python 模块在两个不同版本的同一个目录中存在; mymodule.py 和 mymodule.so (我通过 Cython 从第一个获得后者,但这与我的问题无关)。当我使用 Python 时

import mymodule

它总是选择mymodule.so。有时我真的很想导入mymodule.py。我可以暂时将mymodule.so 移动到另一个位置,但如果我同时运行另一个需要导入mymodule.so 的Python 实例,则效果不佳。

问题是如何使import 更喜欢.py 文件而不是.so,而不是相反?

以下是我对解决方案的想法: 我想象使用importlib 执行一些魔术,并可能编辑sys.meta_path。具体来说,我看到sys.meta_path[2] 包含用于导入外部模块的_frozen_importlib_external.PathFinder,即它同时用于mymodule.py 和mymodule.so。如果我可以用类似的PathFinder 替换它,它对文件类型使用反向排序,我会有一个解决方案。

我正在使用 Python 3.7,如果这会影响解决方案。

编辑

请注意,仅读取 mymodule.py 和 exec'ing 它们的源代码行是行不通的,因为 mymodule.py 本身可能会导入其他模块,这些模块再次存在于 .py 和 .so 版本中(我也想导入这些的.py 版本)。

【问题讨论】:

    标签: python python-3.x import cython python-import


    【解决方案1】:

    使用these notes 我想出了这个。它不是太漂亮,但它似乎工作。

    import glob, importlib, sys
    
    def hook(name):
        if name != '.':
            raise ImportError()
        modnames = set(f.rstrip('.py') for f in glob.glob('*.py'))
        return Finder(modnames)
    sys.path_hooks.insert(1, hook)
    sys.path.insert(0, '.')
    
    class Finder(object):
        def __init__(self, modnames):
            self.modnames = modnames
        def find_spec(self, modname, target=None):
            if modname in self.modnames:
                origin = './' + modname + '.py'
                loader = Loader()
                return importlib.util.spec_from_loader(modname, loader, origin=origin)
            else:
                return None
    
    class Loader(object):
        def create_module(self, target):
            return None
        def exec_module(self, module):
            with open(module.__spec__.origin, 'r', encoding='utf-8') as f:
                code = f.read()
            compile(code, module.__spec__.origin, 'exec')
            exec(code, module.__dict__)
    

    【讨论】:

      【解决方案2】:

      这是另一种解决方案,只需调整运行时默认生成的查找器即可。这使用了隐藏的实现细节 (FileFinder._loaders),但我已经在 CPython 3.7、3.8 和 3.9 上进行了测试。

      from contextlib import contextmanager
      from dataclasses import dataclass
      from importlib.machinery import FileFinder
      from importlib.abc import Finder
      import sys
      from typing import Callable
      
      
      @dataclass
      class PreferPureLoaderHook:
          orig_hook: Callable[[str], Finder]
      
          def __call__(self, path: str) -> Finder:
              finder = self.orig_hook(path)
              if isinstance(finder, FileFinder):
                  # Move pure python file loaders to the front
                  finder._loaders.sort(key=lambda pair: 0 if pair[0] in (".py", ".pyc") else 1)  # type: ignore
              return finder
      
      
      @contextmanager
      def prefer_pure_python_imports():
          sys.path_hooks = [PreferPureLoaderHook(h) for h in sys.path_hooks]
          sys.path_importer_cache.clear()
          yield
          assert all(isinstance(h, PreferPureLoaderHook) for h in sys.path_hooks)
          sys.path_hooks = [h.orig_hook for h in sys.path_hooks]
          sys.path_importer_cache.clear()
      
      with prefer_pure_python_imports():
          ...
      

      【讨论】:

        猜你喜欢
        • 2020-02-29
        • 2022-12-14
        • 1970-01-01
        • 2023-01-16
        • 2022-01-03
        • 2016-09-24
        • 2010-09-24
        • 2011-05-13
        • 1970-01-01
        相关资源
        最近更新 更多