2 votes

Python `pkgutil.get_data` perturbe les importations futures

Considérons la structure de paquet suivante :

.
 module
    __init__.py
    submodule
        attribute.py
        data.txt
        __init__.py
 test.py

et le morceau de code suivant :

import pkgutil
data = pkgutil.get_data('module.submodule', 'data.txt')
import module.submodule.attribute
retval = module.submodule.attribute.hello()

L'exécution de cette opération entraînera l'apparition de l'erreur :

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    retval = module.submodule.attribute.hello()
AttributeError: module 'module' has no attribute 'submodule'

Cependant, si vous exécutez la commande suivante :

import pkgutil
import module.submodule.attribute
data = pkgutil.get_data('module.submodule', 'data.txt')
retval = module.submodule.attribute.hello()

ou

import pkgutil
import module.submodule.attribute
retval = module.submodule.attribute.hello()

cela fonctionne bien.

Pourquoi la course à pied pkgutil.get_data perturber l'importation future ?

1voto

mehdy Points 926

Tout d'abord, c'était une excellente question et une excellente occasion d'apprendre quelque chose de nouveau sur le système d'importation de Python. Alors creusons un peu !

Si l'on considère la mise en œuvre de pkgutil.get_data nous voyons quelque chose comme ceci :

def get_data(package, resource):
    spec = importlib.util.find_spec(package)
    if spec is None:
        return None
    loader = spec.loader
    if loader is None or not hasattr(loader, 'get_data'):
        return None
    # XXX needs test
    mod = (sys.modules.get(package) or
           importlib._bootstrap._load(spec))
    if mod is None or not hasattr(mod, '__file__'):
        return None

    # Modify the resource name to be compatible with the loader.get_data
    # signature - an os.path format "filename" starting with the dirname of
    # the package's __file__
    parts = resource.split('/')
    parts.insert(0, os.path.dirname(mod.__file__))
    resource_name = os.path.join(*parts)
    return loader.get_data(resource_name)

La réponse à votre question se trouve dans cette partie du code :

    mod = (sys.modules.get(package) or
           importlib._bootstrap._load(spec))

Il examine les paquets déjà chargés et si le paquet que nous recherchons ( module.submodule dans cet exemple) existe, il l'utilise et si ce n'est pas le cas, il essaie de charger le paquet à l'aide de la fonction importlib._bootstrap._load .

Examinons donc les la mise en œuvre de importlib._bootstrap._load pour voir ce qui se passe.

def _load(spec):
    """Return a new module object, loaded by the spec's loader.
    The module is not added to its parent.
    If a module is already in sys.modules, that existing module gets
    clobbered.
    """
    with _ModuleLockManager(spec.name):
        return _load_unlocked(spec)

Eh bien, il est juste là ! La doc dit "Le module n'est pas ajouté à son parent".

Cela signifie que le submodule est chargé mais il n'est pas ajouté au module module module. Ainsi, lorsque nous essayons d'accéder au module submodule via module il n'y a pas de connexion, d'où le AtrributeError .

Il est logique que la get_data pour utiliser cette fonction car elle ne demande qu'un autre fichier dans le paquet et il n'est pas nécessaire d'importer tout le paquet et de l'ajouter à son parent et au parent de ses parents, etc.

Pour le voir vous-même, je vous suggère d'utiliser un débogueur et de placer des points d'arrêt. Vous pourrez ainsi voir ce qui se passe étape par étape.

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