Python: importation d'un sous-package ou d'un sous-module

90

Ayant déjà utilisé des packages plats, je ne m'attendais pas au problème que j'ai rencontré avec les packages imbriqués. Voici…

Disposition du répertoire

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Contenu de init .py

Les deux package/__init__.pyet package/subpackage/__init__.pysont vides.

Contenu de module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Contenu de test.py(3 versions)

Version 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

C'est la manière mauvaise et dangereuse d'importer des choses (tout importer en vrac), mais cela fonctionne.

Version 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Un moyen plus sûr d'importer, élément par élément, mais ça échoue, Python ne veut pas ça: échoue avec le message: "Aucun module nommé module". Pourtant …

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… Dit <module 'package.subpackage.module' from '...'>. Donc c'est un module, mais ce n'est pas un module / -P 8-O ... euh

Version 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Celui-ci fonctionne. Donc, vous êtes soit obligé d'utiliser le préfixe overkill tout le temps, soit d'utiliser la méthode non sécurisée comme dans la version n ° 1 et interdite par Python d'utiliser la méthode sûre et pratique? La meilleure façon, qui est sûre et évite les longs préfixes inutiles, est la seule que Python rejette? Est-ce parce qu'il aime import *ou parce qu'il aime les préfixes trop longs (ce qui n'aide pas à appliquer cette pratique)?.

Désolé pour les mots difficiles, mais cela fait deux jours que j'essaye de contourner ce comportement stupide. À moins que je ne me sois totalement trompé quelque part, cela me laissera le sentiment que quelque chose est vraiment cassé dans le modèle de paquetage et de sous-paquet de Python.

Remarques

  • Je ne veux pas compter sur sys.path, pour éviter les effets secondaires globaux, ni sur les *.pthfichiers, qui ne sont qu'une autre façon de jouer sys.pathavec les mêmes effets globaux. Pour que la solution soit propre, elle doit être uniquement locale. Soit Python est capable de gérer les sous-packages, soit ce n'est pas le cas, mais il ne devrait pas nécessiter de jouer avec la configuration globale pour pouvoir gérer les choses locales.
  • J'ai aussi essayé d'utiliser les importations dans package/subpackage/__init__.py, mais ça n'a rien résolu, ça fait la même chose, et se plaint subpackagen'est pas un module connu, alors que print subpackagedit que c'est un module (comportement bizarre, encore une fois).

Peut-être que je me trompe complètement (l'option que je préférerais), mais cela me déçoit beaucoup de Python.

Un autre moyen connu à côté des trois que j'ai essayé? Quelque chose que je ne sais pas?

(soupir)

-----% <----- éditer ----->% -----

Conclusion jusqu'à présent (après les commentaires des gens)

Il n'y a rien de tel qu'un vrai sous-package en Python, car toutes les références de package vont uniquement vers un dictionnaire global, ce qui signifie qu'il n'y a pas de dictionnaire local, ce qui implique qu'il n'y a aucun moyen de gérer la référence de package locale.

Vous devez utiliser un préfixe complet ou un préfixe court ou un alias. Un péché:

Version du préfixe complet

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Version de préfixe court (mais préfixe répété)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Ou bien, une variante de ce qui précède.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Version factorisée

Si cela ne vous dérange pas d'importer plusieurs entités en même temps dans un lot, vous pouvez:

from package.subpackage.module import attribute1, attribute2
# and etc.

Pas dans mon premier goût préféré (je préfère avoir une déclaration d'importation par entité importée), mais c'est peut-être celle que je privilégierai personnellement.

Mise à jour (2012-09-14):

Enfin semble être OK dans la pratique, sauf avec un commentaire sur la mise en page. Au lieu de ce qui précède, j'ai utilisé:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Hibou57
la source
Comment ça se passe quand vous écrivez "from. Import module" dans "/package/subpackage/__init__.py"?
Markus Unterwaditzer
Votre «version factorisée» semble parfaitement adaptée à ce que vous voulez faire. Si vous faites une ligne d'importation distincte pour attribut1 et attribut2 (comme vous "préférez"), vous vous donnez simplement plus de travail. Il n'y a aucune raison de faire ça.
BrenBarn
Désolé mais je n'obtiens pas ce que vous voulez. Pourriez-vous reformuler votre question de manière plus claire? Que souhaitez-vous faire exactement? Je veux dire, qu'est-ce que vous aimeriez écrire qui ne fonctionne pas et comment vous attendez-vous à ce que cela fonctionne? D'après ce que j'ai lu, je pense que la sémantique de l'importation doit être comme celle de Java ou peut-être celle de C. Dernière chose: vous pouvez sécuriser un module "star-import" en ajoutant une __all__variable contenant une liste des noms à exporter lors de l'importation en étoile. edit: OK, en lisant la réponse de BrenBarn, j'ai compris ce que vous vouliez dire.
Bakuriu

Réponses:

68

Vous semblez mal comprendre comment importrecherche des modules. Lorsque vous utilisez une instruction d'importation, elle recherche toujours le chemin d'accès réel du module (et / ou sys.modules); il n'utilise pas les objets de module dans l'espace de noms local qui existent en raison d'importations précédentes. Quand vous faites:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

La deuxième ligne recherche un package appelé package.subpackageet importe moduledepuis ce package. Cette ligne n'a aucun effet sur la troisième ligne. La troisième ligne recherche simplement un module appelé moduleet n'en trouve pas. Il ne "réutilise" pas l'objet appelé moduleque vous avez obtenu à partir de la ligne ci-dessus.

En d'autres termes, from someModule import ...cela ne signifie pas "du module appelé someModule que j'ai importé plus tôt ..." cela signifie "du module nommé someModule que vous trouvez sur sys.path ...". Il n'y a aucun moyen de construire "incrémentalement" le chemin d'un module en important les packages qui y mènent. Vous devez toujours vous référer au nom complet du module lors de l'importation.

Ce que vous essayez d'accomplir n'est pas clair. Si vous voulez seulement importer l'attribut d'objet particulier1, faites-le from package.subpackage.module import attribute1et en avez fini avec lui. Vous n'avez jamais à vous soucier de la durée package.subpackage.moduleune fois que vous avez importé le nom souhaité.

Si vous ne voulez avoir accès au module d'accès d' autres noms plus tard, alors vous pouvez le faire from package.subpackage import moduleet, comme vous l' avez vu , vous pouvez le faire module.attribute1et ainsi de suite autant que vous le souhaitez.

Si vous voulez les deux --- c'est-à-dire si vous voulez attribute1directement accessible et que vous voulez moduleaccessible, faites simplement les deux ci-dessus:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Si vous n'aimez pas taper package.subpackagemême deux fois, vous pouvez simplement créer manuellement une référence locale à l'attribut1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
la source
Vos commentaires vont dans le même sens que ceux d'Ignacio Vazquez-Abrams (j'ai commenté son message). Vous ajoutez à la fin, à propos de l'utilisation module.attribute1est quelque chose que je pensais, mais je pensais qu'il y aurait un moyen d'éviter la nécessité d'un préfixe partout. Je dois donc soit utiliser un préfixe partout, soit créer un alias local en répétant le nom. Pas le style que j'attendais, mais s'il n'y a pas moyen (après tout, je suis habitué à Ada, qui nécessite quelque chose de similaire avec ses déclarations de renommage).
Hibou57
@ Hibou57: Je ne sais toujours pas ce que vous essayez d'accomplir dans votre "Version 2". Que voulez-vous faire qui n'est pas possible? Vous ne voulez jamais retaper aucune partie du nom du package / module / attribut, mais toujours importer le module et son attribut?
BrenBarn
Je voulais avoir une référence de package locale, tout comme la façon dont vous pouvez avoir une référence d'objet local. Il semble qu'il existe enfin une référence de module vraiment locale, mais vous ne pouvez pas importer à partir de ceux-ci. C'est un mélange de local et de global avec un goût drôle (certaines choses peuvent être locales, d'autres doivent être mondiales, je n'aime pas ça, mais ça va tant que je comprends mieux maintenant comment ça marche). Merci pour votre message en passant.
Hibou57
1
Je ne suis pas sûr que vous compreniez encore comment cela fonctionne. Ou ce que vous avez fait en 2012 en tout cas.
Hejazzman
1
Chaque fois que je reviens à Python après une mise à pied de 6 mois, je me retrouve ici. Si seulement je pouvais voter à chaque fois que je visite cette page! Je vais faire un poster gigantesque avec cette phrase: "Il n'y a aucun moyen de construire" incrémentalement "le chemin d'un module en important les paquets qui y mènent."
PatrickT
10

La raison n ° 2 échoue est qu'il sys.modules['module']n'existe pas (la routine d'importation a sa propre portée et ne peut pas voir le modulenom local) et qu'il n'y a pas de modulemodule ou de package sur le disque. Notez que vous pouvez séparer plusieurs noms importés par des virgules.

from package.subpackage.module import attribute1, attribute2, attribute3

Aussi:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams
la source
Votre référence sys.modules['name']que je ne savais pas jusqu'à présent, m'a fait penser que c'était ce que j'avais peur (et BrenBarn confirme): il n'y a rien de tel que de vrais sous-packages en Python. sys.modules, comme son nom l'indique, est globale, et si toutes les références aux modules reposent sur cela, alors il n'y a rien de tel qu'une référence locale à un module (peut-être venir avec Python 3.x?).
Hibou57
Votre utilisation de «référence» est ambiguë; le premier importdans # 2 génère une référence locale à laquelle package.subpackage.modulelier module.
Ignacio Vazquez-Abrams
Oui, mais c'est un «module» que je ne peux pas importer ;-)
Hibou57
0

Si tout ce que vous essayez de faire est d'obtenir l'attribut1 dans votre espace de noms global, la version 3 semble très bien. Pourquoi est-ce un préfixe excessif?

Dans la version 2, au lieu de

from module import attribute1

tu peux faire

attribute1 = module.attribute1
Thomas Vander Stichele
la source
attribute1 = module.attribute1répète simplement le nom sans valeur ajoutée. Je sais que cela fonctionne, mais je n'aime pas ce style (ce qui ne veut pas dire que je n'aime pas votre réponse).
Hibou57
2
Je suppose que, comme toutes les personnes qui commentent ici, je ne comprends pas ce que vous voulez faire. Dans tous les exemples que vous donnez, il semble que vous vouliez vous retrouver avec un symbole d'un sous-paquet dans votre espace de noms. Votre exemple non fonctionnel (exemple 2) veut le faire en important un sous-module à partir d'un package, puis en important un symbole à partir de ce sous-module. Je ne sais pas pourquoi vous voulez le faire en deux étapes au lieu d'une. Expliquez peut-être davantage quelle serait votre solution idéale et pourquoi.
Thomas Vander Stichele