7 votes

Comment rediriger les importations de modules avec Python moderne ?

Je maintiens un paquetage python dans lequel j'ai fait quelques restructurations. Maintenant, je veux soutenir les clients qui font encore du from my_package.old_subpackage.foo import Foo au lieu de la nouvelle from my_package.new_subpackage.foo import Foo sans réintroduire explicitement de nombreux fichiers qui effectuent le transfert. ( old_subpackage existe toujours, mais ne contient plus de foo.py .)

J'ai appris qu'il existe des "loaders" et des "finders", et j'ai eu l'impression que je devais mettre en œuvre un "loader". chargeur pour mes besoins, mais je n'ai réussi à mettre en œuvre qu'un chercheur jusqu'à présent :

RENAMED_PACKAGES = {
    'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}

# TODO: ideally, we would not just implement a "finder", but also a "loader"
# (using the importlib.util.module_for_loader decorator); this would enable us
# to get module contents that also pass identity checks
class RenamedFinder:

    @classmethod
    def find_spec(cls, fullname, path, target=None):
        renamed = RENAMED_PACKAGES.get(fullname)
        if renamed is not None:
            sys.stderr.write(
                f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
            return importlib.util.find_spec(renamed)
        return None

sys.meta_path.append(RenamedFinder())

https://docs.python.org/3.5/library/importlib.html#importlib.util.module_for_loader et les fonctionnalités qui y sont liées semblent toutefois être obsolètes. Je sais que ce n'est pas très pythonique ce que j'essaie de faire, mais je serais heureux d'apprendre que c'est réalisable.

6voto

Robin De Schepper Points 769

Lors de l'importation du fichier __init__.py vous pouvez placer tous les objets que vous souhaitez dans sys.modules les valeurs que vous y mettez seront renvoyées par import déclarations :

from . import new_package
from .new_package import module1, module2
import sys

sys.modules["my_lib.old_package"] = new_package
sys.modules["my_lib.old_package.module1"] = module1
sys.modules["my_lib.old_package.module2"] = module2

Si quelqu'un utilise maintenant import my_lib.old_package o import my_lib.old_package.module1 ils obtiendront une référence à my_lib.new_package.module1 . Puisque la machine d'importation trouve déjà les clés dans le fichier sys.modules dictionnaire, il ne commence même pas à rechercher les anciens fichiers.

Si vous voulez éviter d'importer tous les sous-modules immédiatement, vous pouvez émuler un peu de chargement paresseux en plaçant un module avec une balise __getattr__ en sys.modules :

from types import ModuleType
import importlib
import sys

class LazyModule(ModuleType):
 def __init__(self, name, mod_name):
  super().__init__(name)
  self.__mod_name = name

 def __getattr__(self, attr):
  if "_lazy_module" not in self.__dict__:
    self._lazy_module = importlib.import(self.__mod_name, package="my_lib")
  return self._lazy_module.__getattr__(attr)

sys.modules["my_lib.old_package"] = LazyModule("my_lib.old_package", "my_lib.new_package")

3voto

Seth Edwards Points 41

Dans le fichier init de l'ancien module, faire en sorte qu'il importe des modules plus récents
Ancien (package.oldpkg) :

foo = __import__("Path to new module")

Nouveau (package.newpkg) :

class foo:
  bar = "thing"

donc
package.oldpkg.foo.bar est identique à package.newpkg.foo.bar

J'espère que cela vous aidera !

2voto

radekholy24 Points 113

Je pense que c'est ce que vous recherchez :

RENAMED_PACKAGES = {
    'my_package.old_subpackage.foo': 'my_package.new_subpackage.foo',
}

class RenamedFinder:

    @classmethod
    def find_spec(cls, fullname, path, target=None):
        renamed = RENAMED_PACKAGES.get(fullname)
        if renamed is not None:
            sys.stderr.write(
                f'WARNING: {fullname} was renamed to {renamed}; please adapt import accordingly!\n')
            spec = importlib.util.find_spec(renamed)
            spec.loader = cls
            return spec
        return None

    @staticmethod
    def create_module(spec):
        return importlib.import_module(spec.name)

    @staticmethod
    def exec_module(module):
        pass

sys.meta_path.append(RenamedFinder())

Pourtant, l'OMI l'approche qui manipule sys.modules est préférable car elle est plus lisible, plus explicite et vous offre un meilleur contrôle. Elle peut s'avérer utile, en particulier dans les versions ultérieures de votre paquetage, lorsque my_package.new_subpackage.foo commence à diverger de my_package.old_subpackage.foo tout en conservant l'ancienne pour des raisons de rétrocompatibilité. Pour cette raison, il serait peut-être nécessaire de conserver le code des deux.

0voto

Seth Edwards Points 41

Consolider tous les anciens noms de paquets en my_package .
Anciens paquets (old_package) :

  • image_processing (classe) Sera supprimé et remplacé par better_image_processing
  • text_recognition (classe) Sera supprimée et remplacée par better_t
  • foo (variable) Sera déplacé vers better_text_recognition
  • still_there (classe) Ne bouge pas

Nouveaux paquets :

  • super_image_processing
  • meilleure reconnaissance du texte

Redirector (classe de mon_package) :

class old_package:
   image_processing = super_image_processing # Will be replaced
   text_recognition = better_text_recognition # Will be replaced

Votre nouveau module principal (mon_paquet) :

#imports here
class super_image_processing:
  def its(gets,even,better):
    pass
class better_text_recognition:
  def now(better,than,ever):
    pass
class old_package:
   #Links
   image_processing = super_image_processing
   text_recognition = better_text_recognition
   still_there = __import__("path to unchanged module")

Cela vous permet de supprimer certaines dossiers et garder le reste. Si vous souhaitez réorienter les variables, vous pouvez le faire :

class super_image_processing:
  def its(gets,even,better):
    pass
class better_text_recognition:
  def now(better,than,ever):
    pass
class old_package:
   #Links
   image_processing = super_image_processing
   text_recognition = better_text_recognition
   foo = text_recognition.foo
   still_there = __import__("path to unchanged module")

Cela fonctionnerait-il ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X