Python: принудительно импортировать, чтобы предпочтение - PullRequest
3 голосов
/ 10 мая 2019

У меня есть ситуация, когда один и тот же модуль Python присутствует в одном и том же каталоге в двух разных версиях; mymodule.py и mymodule.so (я получаю последнее из первого через Cython, но это не имеет отношения к моему вопросу). Когда из Python я делаю

import mymodule

всегда выбирает mymodule.so. Иногда я действительно хочу импортировать mymodule.py вместо этого. Я мог бы временно переместить mymodule.so в другое место, но это не очень хорошо, если у меня одновременно работает другой экземпляр Python, которому нужно импортировать mymodule.so.

Вопрос в том, как заставить 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 их не подойдет, поскольку mymodule.py может сам импортировать другие модули, которые снова существуют в версиях .py и .so. (Я хочу импортировать .py версию этих версий).

1 Ответ

1 голос
/ 10 мая 2019

Используя эти заметки Я придумал это.Это не слишком красиво, но, кажется, работает.

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__)
...