7
votes

Comment puis-je utiliser l'importation relative dans Python3 avec un bloc if __name __ = '__ main__'?

Je crée un paquet, et les modules de ce paquet ont du code à l'intérieur de if __name __ == '__ main__': blocs à des fins de test. Mais mes tentatives d'utiliser des importations relatives dans ces modules provoquent des erreurs.

J'ai lu ce fil et le milliard d'autres: Importations relatives pour la milliardième fois

Avant de marquer ceci comme un doublon, si ce que je veux faire n'est pas possible en Python3 alors ma question est pourquoi cela a-t-il fonctionné en Python2 et qu'est-ce qui a motivé la décision d'en faire un si compliqué en Python3?


Voici mon exemple de projet Python:

Traceback (most recent call last):
  File "C:/_MyFiles/Programming/Python Modules/mypackage/module1.py", line 1, in <module>
    from . import module2
ImportError: cannot import name 'module2' from '__main__' (C:/_MyFiles/Programming/Python Projects/pgui/mypackage/module1.py)

__init__.py et module2.py sont vides

module1.py

contient:

>>> from mypackage import module1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\_MyFiles\Programming\Python Modules\mypackage\module1.py", line 1, in <module>
    import module2
ModuleNotFoundError: No module named 'module2'

Cela fonctionne très bien dans Python2. Je suis capable d'importer module1 à partir d'autres projets n'importe où sur mon ordinateur, et je suis également capable d'exécuter module1 directement et de faire exécuter le code du bloc if .

Cependant, cette structure ne fonctionne pas en Python3. Si j'essaie d'importer le module ailleurs, cela échoue:

import module2

# module1 contents

if __name__=="__main__":
    # Some test cases for the contents of this module
    pass

J'ai donc essayé de changer la première ligne en de. import module2 , et cela l'a corrigé afin que je puisse importer le module de n'importe où avec succès. Mais ensuite, lorsque j'essaye d'exécuter module1 directement en tant que script, j'obtiens cette erreur:

mypackage
- module1.py
- module2.py
- __init__.py

Je ne veux pas avoir à ouvrir une console et à taper python -m myfile chaque fois que je travaille sur un module et que je veux l'exécuter directement en tant que script.

Je veux pouvoir travailler sur des modules sans ajouter leur dossier parent à PYTHONPATH en utilisant relative importations comme dans Python2

Y a-t-il une meilleure solution de contournement ou une meilleure solution à ces problèmes?


8 commentaires

"Je ne veux pas avoir à ouvrir une console et à taper python -m myfile chaque fois que je travaille sur un module et que je veux l'exécuter directement en tant que script." - par opposition à quoi, ouvrir une console et taper python myfile.py ? Comment exécutez-vous vos fichiers actuellement? (Il s'agit également de python -m nom_package.nom de module pour les sous-modules de package, et non de python -m nom_module .)


Je suppose que d'autres personnes vous diront la bonne façon de python, cependant, vous pouvez simplement utiliser un `try ... sauf ImportError` pour l'importer de la manière qui fonctionne. Dive Into Python a un exemple .


@ user2357112 Actuellement, j'exécute mes fichiers via l'IDE PyCharm en cliquant sur le bouton Exécuter dans l'interface graphique. Je réalise que je pourrais ajouter -m à la configuration, mais cela semble être un problème et je préférerais que mon code fonctionne lorsqu'il est exécuté normalement, comme dans Python2. Et j'aimerais l'envoyer à quelqu'un pour qu'il l'utilise sans avoir à le prévenir et à lui expliquer comment l'exécuter afin d'éviter les pièges d'importations relatives échouant avec des blocs main avec une erreur cryptique.


@glumplum Merci, cela semble être une solution de contournement viable s'il n'y a pas de moyen «approprié».


Est-ce que cela répond à votre question? Python3 est-il un moyen correct d'importer de manière relative ou absolue?


@AniMenon Non, j'ai accepté la réponse d'Anoop puisque personne d'autre n'a donné plus, cela semble être la réponse à "Comment puis-je utiliser l'importation relative en Python3 avec un bloc if __name __ = '__ main__'? "est juste" tu ne peux pas "


@Esostack C'est vrai, lorsque vous exécutez un script, vous ne pouvez pas accéder à quelque chose de son répertoire parent relativement. Mais si cela fait partie d'un package et que vous l'exécutez en tant que module, vous pouvez importer relativement dans le package.


Si vous souhaitez ajouter un test de fichier avec une vérification pour main et que vous souhaitez également importer le fichier vers d'autres modules, utilisez la même vérification pour main au niveau de l'importation: si nom == ' main ': depuis mypackage import module1 else: depuis .mypackage import module1


3 Réponses :


4
votes

Selon la Documentation du module , pour __main__ modules, vous devez utiliser des importations absolues.

Notez que les importations relatives sont basées sur le nom du module actuel. Comme le nom du module principal est toujours " main ", les modules destinés à être utilisés comme module principal d'une application Python doivent toujours utiliser des importations absolues.

Modifiez simplement la ligne d'importation dans module1.py en:

du module d'importation mypackage2

Tout le reste reste le même.


6 commentaires

Y a-t-il une erreur avec votre code? module2.py comme première ligne de __init__.py donne une erreur de référence non résolue. Et en supprimant cela, j'obtiens toujours la même erreur qu'avant dans Python3.


Oh oui, je semble avoir le même problème maintenant. J'ai trouvé une solution; mettra à jour ma réponse. Désolé pour ça.


Merci, mais cela nécessite toujours d'ajouter le dossier parent du package à PYTHONPATH ou cela échoue. Cela signifie que lorsque j'écris le code pour la première fois, je dois utiliser des importations relatives, puis je dois refactoriser toutes mes instructions d'importation pour qu'elles soient absolues une fois que j'ai décidé d'en faire un module? Cela signifie également que mon code échoue pour quiconque télécharge mon paquet sous forme de zip à moins qu'il ne l'ajoute d'abord à son propre PYTHONPATH. Ainsi, même si le simple fait de ne pas utiliser d'importations relatives fait fonctionner le code, ce n'est pas une réponse à mon OP sur la façon de faire fonctionner les importations relatives comme elles le faisaient dans Python2.


L'ajout du contrôle __name__ == "__main__" est-il uniquement destiné aux tests? Idéalement, comme je le vois, vous n'auriez qu'un seul module principal , qui serait le point de départ de votre code. Pour les tests, vous pouvez utiliser une approche différente. De cette façon, vous pouvez utiliser des importations relatives partout sauf dans le module main . De plus, si vous prévoyez de le distribuer sous forme de zip, vous ne voulez pas de plusieurs points d'entrée pour le code (sauf s'il y a une raison spécifique de le faire).


Oui, c'est juste pour le test. C'est un projet PyQt5 avec un module pour l'interface graphique principale et d'autres modules pour divers autres widgets, dialogues et fonctions qui sont utilisés dans l'interface graphique principale. Donc, pour chaque module, j'ai un bloc __name__ == "__main__" qui crée des entrées de test et instancie le widget seul, donc je n'ai pas à ouvrir l'interface graphique principale et à l'ouvrir manuellement. Quelle est la bonne alternative? Dois-je simplement avoir un fichier de test séparé pour chacun, comme mydialog_test.py, qui utilise des importations relatives et dont le code de test n'est pas dans un bloc main ?


C'est intéressant. Je n'ai pas utilisé PyQt5 mais j'avais une exigence similaire pour l'un de mes projets utilisant Tkinter. Nous avons eu exactement le même scénario où nous avons utilisé la vérification principale pour des tests rapides. Je viens de creuser ce projet - on dirait que nous l'avons divisé en plusieurs packages et utilisé des importations absolues pour importer des classes spécifiques à partir de différents modules - quelque chose comme de la classe d'importation package.module . Maintenant, je ne sais pas s'il existe une meilleure façon.



2
votes

Un package Python n'est pas seulement un dossier dans lequel vous collez votre code, et le comportement d'importation dépend de plus que du dossier dans lequel vous avez bloqué votre code.

Lorsque vous exécutez votre fichier directement, vous n'êtes pas l'exécuter dans le cadre d'un package. L'initialisation au niveau du package ne s'exécute pas et Python ne reconnaît même pas l'existence du package. Sur Python 2, l'existence d'importations relatives implicites signifiait qu'un simple import module2 se résoudrait soit à une importation absolue, soit à une importation relative implicite, masquant le problème, mais la structure d'importation est toujours cassée. Sur Python 3, les importations relatives implicites ont disparu (pour une bonne raison), donc le problème est immédiatement visible.

L'exécution d'un sous-module d'un paquet directement par nom de fichier ne fonctionne tout simplement pas très bien. De nos jours, je pense que la norme est soit d'utiliser -m , soit d'utiliser un script de point d'entrée de premier niveau qui invoque la fonctionnalité du sous-module.

Il existe une sorte de moyen d'obtenir run-by-filename fonctionne de toute façon, mais c'est beaucoup de passe-partout. Les concepteurs de PEP 366 semblent avoir prévu un __package__ = 'appropriée.value' affectation pour que les importations relatives fonctionnent correctement, mais ce n'est pas suffisant, même si vous corrigez le chemin d'importation. Vous devez également initialiser manuellement le package parent, ou vous obtiendrez un "SystemError: module parent 'toto' non chargé, ne peut pas effectuer une importation relative" dès que vous essayez d'exécuter une importation relative. Le passe-partout ressemble plus à

import os.path
import sys
if __name__ == '__main__' and __package__ is None:
    __package__ = 'mypackage'
    right_import_root = os.path.abspath(__file__)
    for i in range(__package__.count('.') + 2):
        right_import_root = os.path.dirname(right_import_root)

    # sys.path[0] is usually the right sys.path entry to replace, but this
    # may need further refinement in the presence of anything else that messes
    # with sys.path
    sys.path[0] = right_import_root
    __import__(__package__)

Cela va après des choses comme les importations futures, mais avant toute importation qui dépend de votre package.

Je voudrais envelopper ceci passe-partout dans une fonction réutilisable (en utilisant la manipulation de la pile pour accéder aux globaux de l'appelant), sauf que si vous essayez de placer cette fonction quelque part dans votre projet, vous ne pourrez pas importer la fonction tant que vous n'aurez pas corrigé votre situation d'importation, ce qui vous avez besoin de la fonction à faire. Cela peut fonctionner comme une dépendance installable.


0 commentaires

1
votes

Je me suis retrouvé dans un scénario similaire et cela m'a beaucoup dérangé jusqu'à ce que je réalise comment l'importation de modules et de packages est censée fonctionner.

Considérez la structure suivante

python3 -m package.module2                                                                              2 ↵
Module 1
Module 2
Executed as script

Le contenu de module1 et module2 ressemble à ci-dessous

module1.py

Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from . import module2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'module2'
>>> import module2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/rbhanot/python-dotfiles/python3/modules-packages/mydir/package/module2.py", line 1, in <module>
    from . import module1
ImportError: attempted relative import with no known parent package
>>> import module1
Module 1
>>>


0 commentaires