Загрузить пакет, но пакет с таким именем уже загружен

1

У меня две версии одного и того же пакета Python. Мне нужно от модуля в субпакете в текущей версии, чтобы иметь возможность вызвать функцию внутри старой версии пакета (который копировал себя в прошлом)

Где я сейчас:

now/
  package/
    __init__.py
    subpackage/
      __init__.py
      module.py -> "import package.subpackage.... <HERE>"
    subpackage2/
      ...
    ...

Старая версия:

past/
  package/
    __init__.py
    subpackage/
      __init__.py
      module.py -> "import package.subpackage; from . import module2; .... def f(x) ..."
      module2.py
    subpackage2/
      ...
    ...

Мне нужно импортировать в <HERE> "старый" f и запустить его.

в идеале

  • функция f должна жить своей жизнью внутри старого пакета, не зная ничего о новой версии пакета
  • модуль в новом пакете следует назвать, пусть он живет свою жизнь, получить результаты, а потом забыть вообще о существовании старого пакета (так называя "импортом package.subpackage2" после того, как позволить f сделать ее вещь должна работать "новая "версия)
  • это не должно быть ужасно сложным

Основная идея состоит в том, чтобы улучшить воспроизводимость, сохранив код, который я использовал для какой-либо задачи, вместе с выходными данными, а затем смог запустить ее части.

К сожалению, я понял, что это не простая задача с Python 3, поэтому я готов принять какой-то компромисс. Я готов принять, например, что после запуска старого f(x) package имен в "новом" коде будет привязан к старому.

РЕДАКТИРОВАТЬ

Я попытался двумя способами с помощью importlib. Идея заключалась в создании mod объекта, а затем выполнении f = getattr(mod, "f"), но это не работает

  1. Изменение sys.path на ['.../past/package/subpackage'] а затем вызов importlib.import_module('package.subpackage.module'). Проблема в том, что он будет загружать один из "сейчас" даже с измененным sys.path, возможно, потому, что package имен уже находится в sys.modules
  2. spec = importlib.util.spec_from_file_location("module", "path..to..past..module.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) В этом случае относительный импорт (from. import module2.py) не будет работать, что приведет к ошибке "попытка относительного импорта без известного родительского пакета"
  • 1
    это обычно не рекомендуется, поскольку старые версии пакетов обычно имеют взаимозависимости от других пакетов и версии python, с которой они работают. Я хотел бы предложить что - то вроде параметров травильных, спиннинг нового интерпретатора в другом virtualenv , то unpicking и работаю в том , что
  • 0
    Почему в этом случае у вас не работает регулярный импорт? Я не думаю, что есть какой-то пуленепробиваемый способ сделать это и гарантировать, что между двумя версиями пакета не будет столкновений в пространстве имен импорта. Я не знаю ни одной библиотеки, которая предназначена для сосуществования с другими версиями той же библиотеки.
Показать ещё 9 комментариев
Теги:
python-3.x
python-import
binary-reproducibility

1 ответ

1

Есть один способ, которым это может работать довольно просто, но вам придется внести несколько изменений в свой старый пакет.

Вы можете просто создать файл в now/package/old/__init__.py содержащий:

__path__ = ['/absolute/path/to/old/package']

В новом пакете вы можете:

from package.old.package.subpackage.module import f as old_f

Улов здесь заключается в том, что старый пакет пытается импортировать свои собственные пакеты с использованием абсолютного импорта, вместо этого он загружает материал из новых пакетов. Таким образом, старый пакет должен будет использовать только относительный импорт при импорте продуктов из своего собственного пакета или вам придется добавлять пакет package.old ко всем абсолютным импортам, которые использовался старым пакетом.

Если вы в порядке с модификацией старых пакетов таким образом, то это должно быть хорошо. Если это ограничение не сработает для вас, тогда прочитайте.

Если вы действительно уверены, что по некоторым причинам не хотите изменять старые пакеты. Затем сделайте черную магию, вы хотите заменить builtins.__import__ своей собственной версией, которая возвращает разные модули в зависимости от того, кто выполняет импорт. Вы можете выяснить, кто выполняет импорт, проверив стек вызовов.

Например, так вы можете это сделать (тестируется на Python 3.6):

import builtins
import inspect
import package.old

old_package_path = package.old.__path__[0]

OUR_PACKAGE_NAME = 'package'
OUR_PACKAGE_NAME_WITH_DOT = OUR_PACKAGE_NAME + '.'


def import_module(name, globs=None, locs=None, fromlist=(), level=0):
    # only intercept imports for our own package from our old module
    if not name.startswith(OUR_PACKAGE_NAME_WITH_DOT) or \
            not inspect.stack()[1].filename.startswith(old_package_path):
        return real_import(name, globs, locs, fromlist, level)

    new_name = OUR_PACKAGE_NAME + '.old.' + name[len(OUR_PACKAGE_NAME_WITH_DOT):]
    mod = real_import(new_name, globs, locs, fromlist, level)
    return mod.old

# save the original __import__ since we'll need it to do the actual import
real_import = builtins.__import__
builtins.__import__ = import_module

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


Ниже мой старый ответ, здесь только для исторических целей

Я не совсем понимаю, что вы пытаетесь сделать, но это, вероятно, можно сделать в Python 3 с помощью importlib.

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

Также есть функция invalidate_caches() и reload() которая может быть полезна, хотя вам могут и не понадобиться.

  • 0
    Спасибо, но этого недостаточно. Я отредактировал вопрос с более подробной информацией о том, что происходит с importlib
  • 0
    @FiatLux: я обновил свой ответ, как вы могли бы сделать это

Ещё вопросы

Сообщество Overcoder
Наверх
Меню