[Python] Nom de fichiers dans un module

a marqué ce sujet comme résolu.

Salut à tous,

je travaille sur un module Python qui contient deux sous-modules : A et B.

Ils ont une classe commune, X.

Chacun des sous modules A et B contiennent un fichier function.py et class1.py, ainsi que A.py (resp. B.py) qui contiennent les fonctions principales, "accessibles".

Du coup pour importer une fonction fun1 de A, je dois faire quelque chose comme :

from monModule.A.A import fun1 ou alors import monModule.A.A as A, ce qui est, je trouve, assez lourd.

Y a t’il :

  1. une meilleur idée de nommage pour les fichiers ? et/ou

  2. une manière de faire un import "par défaut", c’est à dire qu’en important monModule.A j’importerai directement le fichier A.py ?

Merci d’avance :)

Salut,

Personnellement, je fais le choix de ne pas utiliser le volet « import sur plusieurs niveaux d’arborescence » de la fonction d’import de Python, pour plusieurs raisons :

  • Il y a souvent des prises de tête pour savoir quelle arborescence ou quel fichier se trouve dans l’arborescence de quel fichier, et cela peut notamment dépendre du point d’entrée de ton programme.
  • Il peut y avoir une dépendance sur la présence ou non de fichiers __init__.py (surtout en Python 2 il me semble).
  • Le comportement change totalement entre Python 2 et 3.
  • Les cas d’imports récursifs ou mutuels sont très compliqués à gérer.

Du coup, à la place, je raisonne en trois étapes : je calcule les chemins absolus des dossiers qui m’intéressent dans des constantes (ces constantes me seront aussi utiles si je peux lire, écrire ou manipuler des fichiers), j’ajoute ces dossiers au chemin (sys.path), puis je fais des imports à un niveau avec la syntaxe classique.

Par exemple :

#!/usr/bin/python3
#-*- encoding: Utf-8 -*-

from os.path import dirname, realpath

SCRIPT_DIR = dirname(__file__)

MODULE_A_DIR = realpath(SCRIPT_DIR + '/module_a')
MODULE_B_DIR = realpath(SCRIPT_DIR + '/module_b') # Je peux mettre des /.. sans les cris de l'interpréteur

import sys
sys.path += [MODULE_A_DIR, MODULE_B_DIR]

from monsupermodule import masuperfonction

# Si je veux lire un fichier texte qui pour une raison X se trouve dans
# MODULE_B_DIR, je n'ai pas de chemin à réécrire ou de typo à faire, ou
# de remplacements de masse à faire sur mes fichiers en cas de changement
# d’arborescence, j'ai toujours ma constante

Ça charge un peu le haut de tes fichiers, mais ça simplifie beaucoup la vie à la longue (pour les raisons mentionnées ci-dessus), et pas seulement pour les imports.

Cela marche pour les projets avec une arborescence moyennement complexe, une petite application web par exemple. Si tu as une grosse arborescence bien complexe tu peux vouloir chercher à utiliser les imports multi-niveaux « natifs » comme tu l’as fait…

Bonne journée,

+0 -3

Du coup, à la place, je raisonne en trois étapes : je calcule les chemins absolus des dossiers qui m’intéressent dans des constantes (ces constantes me seront aussi utiles si je peux lire, écrire ou manipuler des fichiers), j’ajoute ces dossiers au chemin (sys.path), puis je fais des imports à un niveau avec la syntaxe classique.

r0anne

Juste pour indiquer que c’est une (très) mauvaise habitude à prendre.

Oui la hiérarchie des modules peut parfois être difficile à saisir, mais elle l’est d’autant plus si on multiplie les répertoires dans le sys.path.
Dans l’idéal cette liste devrait se limiter aux répertoires d’installation, et au répertoire courant du module principal.

Plus il y a de répertoires dans la liste, plus on risque de rencontrer des conflits entre les noms de module. Ce serait le cas avec l’exemple donné par l’OP de noms de modules communs entre les deux packages.

La bonne solution se trouve en effet au niveau des fichiers __init__.py. Un tel fichier dans un répertoire foo est chargé lorsque tu fais import foo. Tu peux donc placer dans ce fichier tout ce que tu souhaites avoir à la racine du module directement au moment de l’import, y compris d’autres imports.

Dans ton cas, les fichiers monModule/A/__init__.py et monModule/__init__.py pourraient contenir :

from .A import fun1 # Import relatif
# Ou en absolue: from monModule.A.A import fun1
monModule/A/__init__.py
from .A import fun1 # Import relatif
# Ou en absolue: from monModule.A import fun1
monModule/__init__.py

Et tu pourrais ensuite simplement écrire from monModule import fun1 là où tu veux l’utiliser.

Autrement, ton arborescence semble tout de même compliquée, si je comprends bien tu as quelque chose comme :

.
└── monModule
    ├── A
    │   ├── A.py
    │   ├── class1.py
    │   └── function.py
    └── B
        ├── B.py
        ├── class1.py
        └── function.py

A/A.py semble superflu et devrait je pense plutôt directement être A/__init__.py, qui lui-même importerait les objets de class1.py et function.py s’ils sont nécessaires.

Je peux enfin t’inviter à consulter ce billet pour différentes informations relatives aux packages en Python : https://zestedesavoir.com/billets/1842/notes-sur-les-modules-et-packages-en-python/.

Juste pour indiquer que c’est une (très) mauvaise habitude à prendre.

entwanne

C’est non-standard, mais c’est une question de « complexité apparente contre complexité brute » : en choisissant de ne pas utiliser une fonctionnalité de Python qui est notoirement complexe et imprévisible (les frameworks comme Django sont conçus pour gérer la question, mais les applications Python GUI largement distribuées qui crashent en plein vol pour des raisons de localisation des imports internes au projet ne sont pas rares), je simplifie le fonctionnement de mon projet.

Certes, à un moment donné peut se poser la question des conflits de noms, d’où ma remarque du fait que ce n’est pas indiqué pour les larges projets.

C’est un peu comme le fait de mettre du virtualenv et du Docker à toutes les sauces, ou de multiplier les dépendances externes pour des tâches simples : ton code ou ta commande d’installation aura l’air « un peu » plus propre, mais à quel prix en termes de complexité extrinsèque : cas particuliers, lourdeur de mise en place, gestion et maintenance sur la durée, etc. D’où la justification de mon choix.

En fait la complexité réside dans le répertoire initial du projet et uniquement là.

Et les solutions à ce problème sont de lancer les projets avec python -m ou de les installer avec pip (qui les installera directement dans un des répertoires présent dans sys.path).

Sachant que cette installation est aussi possible sur un projet en développement, à l’aide d’un pip install -e . par exemple qui a pour effet de placer un lien symbolique vers le projet courant dans le répertoire d’installation (un virtualenv est bien sûr préférable dans ce cas).

À la limite tu pourrais avoir un script qui ajoute un répertoire au sys.path au lancement avant de charger à proprement parler ton projet, mais ça se limiterait à un répertoire et ce serait extérieur au projet. Ça n’empêcherait d’utiliser le projet correctement dans des conditions normales.
Le problème de la méthode que tu proposes est que tous les imports sont faux et casseront dès que tu retireras les sys.path.append.

Au passage je n’ai jamais rencontré d’application Python « largement distribuée » plantant pour un problème d’import. Tu es sûr qu’il ne s’agissait pas d’un soucis de version de Python ou de dépendance système manquante ?

Tu es sûr qu’il ne s’agissait pas d’un soucis de version de Python ou de dépendance système manquante ?

entwanne

Ça c’est beaucoup plus courant. Les deux derniers projets sur lesquels j’ai vu des problèmes d’imports internes devaient être Bokken et googleplaydownloader, si ma mémoire est bonne.

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