Загрузить пакет, но пакет с таким именем уже загружен - PullRequest
0 голосов
/ 15 октября 2018

У меня есть две версии одного и того же пакета 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 должна прожить свою жизнь внутри старого пакета, ничего не зная о новой версии пакета
  • модуль в новомпакет должен вызвать его, позволить ему прожить свою жизнь, получить результаты, а затем вообще забыть о существовании старого пакета (так что вызов «import package.subpackage2» после того, как f сделает ее, должен запустить «новую» версию)
  • выполнение этого не должно быть ужасно сложным

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

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

EDIT

Я пытался вдва способа использования 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 Ответ

0 голосов
/ 15 октября 2018

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

Вы можете просто создать файл в 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__вызывается для любых операторов импорта, с которыми сталкивается интерпретатор, и этот вызов не кэшируется, поэтому вы можете возвращать разные вещи при каждом вызове, даже если они используют одно и то же имя.


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

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

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

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

...