imports contradictoires en python

Le problème exposé dans ce sujet a été résolu.

Salut,

En simplifiant au maximum mon souci, voici l’organisation d’un micro projet :

main.py
operations /
    __init__.py
    addition.py
    soustraction.py

Ce que j’essaye de faire : premièrement, je veux pouvoir exécuter main.py (le projet global), puis deuxièmement aller dans soustraction.py et l’exécuter pour le tester séparément (sans changer une ligne de code entre ces deux manips).

Maintenant, voici donc le code de mes trois petits fichiers :

#main.py :

import operations.addition
import operations.soustraction

a = operations.addition.add(1, 2)
print(a)
#addition.py :

def add(a, b):
    return a + b
#soustraction.py :

from .addition import add

def sous(a, b):
    return add(a, -b)

#pour tester ce module en l'exécutant mais pas à l'importation :

if __name__ == '__main__':
    test = sous(2, 1)
    print(test)

Voilà le soucis : quand j’exécute main.py, ça marche parfaitement ! youpi ! :soleil:

Et quand je vais dans soustraction.py, pour l’exécuter, patatra :'(

File "C:\Users\rmarchand\Desktop\operations\soustraction.py", line 1, in <module>
    from .addition import add
ImportError: attempted relative import with no known parent package

Bon comme je suis attentif au cours, je m’y attendais un peu :-°

Seulement voilà, quand je change la ligne d’import de soustraction.py en from addition import add (sans le . !), je peux maintenant exécuter mon module soustraction sans problème, mais maintenant je ne peux plus exécuter le module main.py :

  File "C:\Users\rmarchand\Desktop\main.py", line 2, in <module>
    import operations.soustraction
  File "C:\Users\rmarchand\Desktop\operations\soustraction.py", line 1, in <module>
    from addition import add
ModuleNotFoundError: No module named 'addition'

Me voilà coincé. Y a-t-il un moyen 'simple' de pallier à ce problème ? Quelle ligne d’import 'simple' puis-je mettre en tête de soustraction.py pour pouvoir exécuter main.py et soustraction.py sans avoir à modifier de lignes entre les deux exécutions ?

En espérant avoir été super clair, merci d’avance…

+0 -0

Il n’y a pas de méthode simple à ma connaissance.

Le seul truc que je vois c’est de faire un import conditionnel en fonction de si __name__ est __main__ ou pas.

Peut-être que d’autres personnes auront des méthodes plus propres mais personnellement ce problème m’embête suffisamment pour que j’aie abandonné d’exécuter des sous-modules.

+0 -0

Salut,

Voir ce sujet qui traite d’une question similaire.

Par ailleurs, mettre des tests dans if __name__ == "__main__", c’est vraiment pas terrible. D’une part parce que ça ne marche pas avec le modèle d’import de Python, d’autre part parce que les outils qui existent pour automatiser l’exécution des tests ne s’attendent pas à ça. Tu as autant de mettre les tests dans des fichiers à part et utiliser quelque chose comme pytest (wrappé par tox est le standard de facto) pour les faire tourner.

+0 -0

Oui c’est précisément pour ça que je ne le fais plus. ^^"

On dit la même chose @adri1

+0 -0

As tu lu l’autre sujet que j’ai mis en lien ? Il discute exactement ce cas de figure. Si tu as vraiment besoin de deux points d’entrée dans ton application, l’approche la plus propre est de… définir deux points d’entrée au niveau du package. Je doute qu’avoir deux points d’entrée différents apporte vraiment quelque chose la plupart du temps, cela dit. Tu peux avoir plutôt intérêt à définir des sous-commandes dans un seul point d’entrée. Si une sous-commande grandit, tu peux avoir intérêt à isoler le service qu’elle rend dans son propre package.

+0 -0

Il est aussi envisageable que ton programme ne soit qu’une bibliothèque, sans point d’entrée exécutable particulier, et que tu aies autour des scripts qui utilisent cette lib pour des options particulières.

Il te faudrait alors modeler ton paquet correctement pour exposer ce que tu veux là où tu veux, mais je doute de la pertinence de ton module main.py ans l’histoire.

@Adri1 je viens de le lire mais je n’y trouve pas de réponse précise, et il renvoie en définitive vers encore un autre sujet qui étudie une situation encore plus compliquée surtout pour un débutant >_<

J’ai donc cuisiné chatGPT-4 (ce qui ne rend pas inutile l’aide des pros d’ici puisque GPT ne dit pas quand on structure notre projet n’importe comment, ni les bonnes pratiques, il vous aide simplement à bricoler votre truc bancal), et il en ressort que la solution la plus simple pour moi dans ce cas serait de laisser inchangée la ligne from .addition import add dans soustraction.py, mais d’exécuter ce module avec la commande python -m operation.soustraction depuis le répertoire racine. Ainsi, python ne considère pas soustraction.py comme le script principal et il a accès au contexte (les répertoires du projet). Il sait donc que .addition fait référence au répertoire operations, et le tour est joué

+1 -0

Il pourrait être utile d’expliciter ce que tu as du mal à comprendre. Dans l’autre sujet, tu n’as rien à lire à part la réponse qui est directement liée. Elle contient tout ce dont tu as besoin pour définir des points d’entrées, avec un exemple complet.

Utiliser python -m fonctionne, mais cette façon de procéder n’est pas la façon idiomatique d’exposer une CLI à l’utilisateur. C’est utilisé par la bibliothèque standard pour exposer des outils de développement (comme python -m ensurepip ou python -m timeit) et conseillé pour exécuter certains modules où les points d’entrées peuvent se retrouver en conflit si plusieurs versions de Python sont installées (e.g. python -m pip pour être sûr de quel pip on appelle).

Il serait utile de connaitre ton cas d’usage exact pour pouvoir te conseiller sur l’organisation générale de ton projet.

+0 -0

Je ne comprends pas cette histoire de points d’entrée, de fichier toml… Et donc je ne vois pas en quoi les autres conversations répondent à ma problématique… Même si je comprends bien que le problème est le même.

Cela mis à part, c’était surtout une question théorique, j’imagine que la solution avec la commande python -m operations.soustraction y répondait. Quand j’aurai passé les parties du cours traitant des notions que tu soulèves, je pourrais m’y attaquer, et je garde à l’esprit que l’architecture que j’utilise ici est torp naïve pour une projet réel et conséquent

Je ne comprends pas cette histoire de points d’entrée, de fichier toml…

Le pyproject.toml contient les métadonnées de ton paquet Python, dans lesquelles tu peux définir des points d’entrée. Puisque tu connais un peu C si je me fis à tes autres sujets, c’est un peu pareil que la fonction main qui est par définition le point d’entrée dans un programme en C. Chaque project.scripts pointe vers une fonction de ton package sous le format commande = paquet.module:fonction. Lors de l’installation du paquet (avec pip par exemple), un exécutable appelé commande est créé qui exécute la fonction pointée.

La question "théorique" que tu poses n’a pas de réponse unique, en fait. Ça dépend du cas d’utilisation. Dans ton premier message tu parlais de tests, auxquels cas la pratique standard est de définir les tests dans des fichiers dédiés et utiliser e.g. pytest pour les faire tourner. Si on parle d’exposer des commandes à l’utilisateur final, la pratique standard est d’utiliser un point d’entrée. En plus des cas spécifiques cités, j’ai pas d’exemple en tête qui rende python -m pertinent pour appeler des sous-modules par rapport à soit définir une CLI proprement, soit des scripts à part comme @entwanne suggère. Peut être pour développer e.g. une collection de petits outils au sein d’un projet plus large ? Mais même là, développer une CLI est plus facile à tester et maintenir à long terme…

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte