Python: изменение приоритета типов файлов импорта (.py перед .so) - PullRequest
5 голосов
/ 11 февраля 2020

Если я введу import A из каталога, содержащего A.py и A.so, файл .so будет импортирован. Я заинтересован в изменении порядка типов файлов импорта, чтобы .py имел приоритет над .so, но только временно, то есть между строкой кода i и j. Конечно, это может быть достигнуто с помощью some importlib magi c?

В настоящее время я решаю проблему, копируя .py в отдельный каталог, добавив в этот каталог sys.path и , затем выполните импорт, что просто ужасно.

Зачем это нужно?

Файл (ы) .so - это cython -компилированные версии файлов .py. Я делаю некоторые пользовательские преобразования кода поверх 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 - это современный способ работы, я пока знаю, как импортировать указанный файл c напрямую, используя устаревший модуль 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.

1 Ответ

1 голос
/ 12 февраля 2020

У меня теперь есть рабочее решение. Это несколько странно, но я думаю, что это надежно.

Оказывается, что sys.path_importer_cache хранит различные искатели , которые в свою очередь хранят list из загрузчиков , которые добываются на import по порядку. Эти загрузчики хранятся в виде 2-х кортежей, причем первым элементом является именно то расширение файла, которое обрабатывает данный загрузчик.

Я просто пересекаю все list загрузчиков и pu sh те, у которых .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, которые импортируют друг друга так же, как исходные тестовые файлы.

Можно избавиться от несколько сложной бухгалтерской отчетности, связанной с edits, просто взяв полное резервное копирование copy.deepcopy(sys.path_importer_cache) перед внесением изменений и прикрепление этой резервной копии к sys после выполнения. Он работает в ограниченном тесте, описанном выше, но поскольку различные части механизма импорта могут содержать ссылки на разные вложенные объекты, я подумал, что безопаснее использовать только мутации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...