31 votes

cauchemar avec les importations relatives, comment fonctionne le pep 366 ?

J'ai une "structure de fichier canonique" comme ça (je donne des noms sensés pour faciliter la lecture) :

mainpack/

  __main__.py
  __init__.py 

  - helpers/
     __init__.py
     path.py

  - network/
     __init__.py
     clientlib.py
     server.py

  - gui/
     __init__.py
     mainwindow.py
     controllers.py

Dans cette structure, par exemple, les modules contenus dans chaque paquet peuvent vouloir accéder à l'élément helpers les services publics par le biais d'importations relatives dans quelque chose comme :

# network/clientlib.py
from ..helpers.path import create_dir

Le programme est exécuté "en tant que script" à l'aide de la commande __main__.py de cette manière :

python mainpack/

Essayer de suivre le PEP 366 J'ai mis en __main__.py ces lignes :

___package___ = "mainpack"
from .network.clientlib import helloclient 

Mais en courant :

$ python mainpack 
Traceback (most recent call last):
  File "/usr/lib/python2.6/runpy.py", line 122, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.6/runpy.py", line 34, in _run_code
    exec code in run_globals
  File "path/mainpack/__main__.py", line 2, in <module>
    from .network.clientlib import helloclient
SystemError: Parent module 'mainpack' not loaded, cannot perform relative import

Quel est le problème ? Quelle est la manière correcte de gérer et d'utiliser efficacement les importations relatives ?

J'ai également essayé d'ajouter le répertoire actuel au PYTHONPATH, mais rien ne change.

42voto

taherh Points 331

Le "boilerplate" donné dans PEP 366 semble incomplète. Bien qu'il fixe le __package__ il n'importe pas réellement le paquet, ce qui est également nécessaire pour permettre aux importations relatives de fonctionner. extraneon La solution de l'UE est sur la bonne voie.

Notez qu'il n'est pas suffisant d'avoir simplement le répertoire contenant le module dans sys.path le paquet correspondant doit être explicitement importé. Ce qui suit semble être un meilleur modèle que ce qui était donné dans le document PEP 366 pour s'assurer qu'un module python peut être exécuté quelle que soit la manière dont il est invoqué (par le biais d'une commande régulière import ou avec python -m ou avec python à partir de n'importe quel endroit) :

# boilerplate to allow running as script directly
if __name__ == "__main__" and __package__ is None:
    import sys, os
    # The following assumes the script is in the top level of the package
    # directory.  We use dirname() to help get the parent directory to add to
    # sys.path, so that we can import the current package.  This is necessary 
    # since when invoked directly, the 'current' package is not automatically
    # imported.
    parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(1, parent_dir)
    import mypackage
    __package__ = str("mypackage")
    del sys, os

# now you can use relative imports here that will work regardless of how this
# python file was accessed (either through 'import', through 'python -m', or 
# directly.

Si le script ne se trouve pas au niveau supérieur du répertoire du paquetage et que vous devez importer un module situé en dessous du niveau supérieur, alors la commande os.path.dirname doit être répété jusqu'à ce que le parent_dir est le répertoire contenant le niveau supérieur.

7voto

extraneon Points 13362

Le code de chargement semble être quelque chose comme ce :

    try:
        return sys.modules[pkgname]
    except KeyError:
        if level < 1:
            warn("Parent module '%s' not found while handling "
                 "absolute import" % pkgname, RuntimeWarning, 1)
            return None
        else:
            raise SystemError, ("Parent module '%s' not loaded, cannot "
                                "perform relative import" % pkgname)

ce qui me fait penser que votre module n'est peut-être pas dans sys.path. Si vous lancez Python (normalement) et tapez simplement "import mainpack" à l'invite, que fait-il ? Il debe être capable de le trouver.

Je l'ai essayé moi-même et j'ai obtenu la même erreur. Après avoir lu un peu, j'ai trouvé la solution suivante :

# foo/__main__.py
import sys
mod = __import__('foo')
sys.modules["foo"]=mod

__package__='foo'
from .bar import hello

hello()

Cela me semble un peu bricolé, mais cela fonctionne. L'astuce semble être de s'assurer que le paquet foo est chargé afin que l'importation puisse être relative.

6voto

Brad Points 460

Inspiré par les réponses d'Extraneon et de Taherh, voici un code qui parcourt l'arborescence des fichiers jusqu'à ce qu'il n'y ait plus d'espace libre. __init__.py pour construire le nom complet du paquet. C'est un peu compliqué, mais cela semble fonctionner quelle que soit la profondeur du fichier dans votre arborescence. Il semble que les importations absolues sont lourdement encouragé.

import os, sys
if __name__ == "__main__" and __package__ is None:
    d,f = os.path.split(os.path.abspath(__file__))
    f = os.path.splitext(f)[0]
    __package__ = [f] #__package__ will be a reversed list of package name parts
    while os.path.exists(os.path.join(d,'__init__.py')): #go up until we run out of __init__.py files
        d,name = os.path.split(d) #pull of a lowest level directory name 
        __package__.append(name)  #add it to the package parts list
    __package__ = ".".join(reversed(__package__)) #create the full package name
    mod = __import__(__package__) #this assumes the top level package is in your $PYTHONPATH
    sys.modules[__package__] = mod  #add to modules

0voto

FredL Points 881

Il s'agit d'une configuration minimale basée sur la plupart des autres réponses, testée sur python 2.7 avec une disposition des paquets comme celle-ci. Elle présente également l'avantage de pouvoir appeler la fonction runme.py script de n'importe où et il semble Je ne l'ai pas encore testé dans une configuration plus complexe, donc caveat emptor... etc.

Il s'agit essentiellement de la réponse de Brad ci-dessus avec l'insertion dans sys.path que d'autres ont décrite.

packagetest/
  __init__.py       # Empty
  mylib/
    __init__.py     # Empty
    utils.py        # def times2(x): return x*2
  scripts/
    __init__.py     # Empty
    runme.py        # See below (executable)

runme.py ressemble à ceci :

#!/usr/bin/env python
if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    d = path.dirname(path.abspath(__file__))
    __package__ = []
    while path.exists(path.join(d, '__init__.py')):
        d, name = path.split(d)
        __package__.append(name)
    __package__ = ".".join(reversed(__package__))
    sys.path.insert(1, d)
    mod = __import__(__package__)
    sys.modules[__package__] = mod

from ..mylib.utils import times2

print times2(4)

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