Python: module d'importation d'un autre répertoire au même niveau dans la hiérarchie du projet

87

J'ai vu toutes sortes d'exemples et d'autres questions similaires, mais je n'arrive pas à trouver un exemple qui correspond exactement à mon scénario. Je me sens comme un imbécile de poser cela parce qu'il y a tellement de questions similaires, mais je n'arrive tout simplement pas à faire fonctionner "correctement". Voici mon projet:

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |
        |------- Scripts/
        |           |
        |           |----- __init__.py
        |           |----- CreateUser.py
        |           |----- FindUser.py

Si je déplace "CreateUser.py" vers le répertoire principal user_management, je peux facilement utiliser: "import Modules.LDAPManager" pour importer LDAPManager.py --- cela fonctionne. Ce que je ne peux pas faire (ce que je veux faire), c'est de conserver CreateUser.py dans le sous-dossier Scripts et d'importer LDAPManager.py. J'espérais accomplir cela en utilisant "import user_management.Modules.LDAPManager.py". Cela ne marche pas. En bref, je peux faire en sorte que les fichiers Python examinent facilement plus profondément la hiérarchie, mais je ne peux pas faire en sorte qu'un script Python fasse référence à un répertoire et à un autre.

Notez que je suis capable de résoudre mon problème en utilisant:

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import Modules.LDAPManager as LDAPManager

J'ai entendu dire que c'était une mauvaise pratique et découragée.

Les fichiers dans les scripts sont destinés à être exécutés directement (le fichier init .py dans les scripts est-il même nécessaire?). J'ai lu que dans ce cas, je devrais exécuter CreateUser.py avec l'indicateur -m. J'ai essayé quelques variantes à ce sujet et je n'arrive tout simplement pas à faire en sorte que CreateUser.py reconnaisse LDAPManager.py.

CptSupermrkt
la source

Réponses:

66

Si je passe CreateUser.pyau répertoire principal user_management, je peux facilement utiliser: import Modules.LDAPManagerpour importer LDAPManager.py --- cela fonctionne.

S'il vous plaît, ne le faites pas . De cette façon, le LDAPManagermodule utilisé par neCreateUser sera pas le même que celui importé via d'autres importations. Cela peut créer des problèmes lorsque vous avez un état global dans le module ou pendant le décapage / décapage. Éviter importations qui fonctionnent uniquement parce que le module se trouve dans le même répertoire.

Lorsque vous avez une structure de package, vous devez soit:

  • Utilisez les importations relatives, c'est-à-dire si le CreateUser.pyest dans Scripts/:

     from ..Modules import LDAPManager
    

    Notez que c'était (notez le passé ) découragé par PEP 8 uniquement parce que les anciennes versions de python ne les supportaient pas très bien, mais ce problème a été résolu il y a des années. Le courant version du PEP 8 ne les suggère comme une alternative acceptable aux importations en termes absolus. Je les aime vraiment à l' intérieur des paquets.

  • Utilisez les importations absolues en utilisant le nom complet du package ( CreateUser.pyin Scripts/):

     from user_management.Modules import LDAPManager
    

Pour que le second fonctionne, le package user_managementdoit être installé dans le PYTHONPATH. Pendant le développement, vous pouvez configurer l'EDI pour que cela se produise, sans avoir à ajouter manuellement des appels àsys.path.append n'importe où.

Je trouve aussi étrange que ce Scripts/soit un sous-paquet. Parce que dans une installation réelle, le user_managementmodule serait installé sous le répertoire site-packagestrouvé dans le lib/répertoire (quel que soit le répertoire utilisé pour installer les bibliothèques dans votre système d'exploitation), tandis que les scripts devraient être installés sous unbin/ répertoire (celui qui contient les exécutables pour votre système d'exploitation).

En fait, je pense Script/qu'il ne devrait même pas être en dessous user_management. Il devrait être au même niveau de user_management. De cette façon, vous n'avez pas à utiliser -m, mais vous devez simplement vous assurer que le package peut être trouvé (il s'agit encore une fois de configurer l'EDI, d'installer correctement le package ou d'utiliser PYTHONPATH=. python Scripts/CreateUser.pypour lancer les scripts avec le chemin correct).


En résumé, la hiérarchie que j'utiliserais est:

user_management  (package)
        |
        |------- __init__.py
        |
        |------- Modules/
        |           |
        |           |----- __init__.py
        |           |----- LDAPManager.py
        |           |----- PasswordManager.py
        |

 Scripts/  (*not* a package)
        |  
        |----- CreateUser.py
        |----- FindUser.py

Puis le code de CreateUser.py et FindUser.pydoit utiliser des importations absolues pour importer les modules:

from user_management.Modules import LDAPManager

Pendant l'installation, assurez-vous que cela user_managementse termine quelque part dans le PYTHONPATH, et les scripts à l'intérieur du répertoire pour les exécutables afin qu'ils puissent trouver les modules. Pendant le développement, vous comptez soit sur la configuration IDE, soit vous lancez l' CreateUser.pyajout du Scripts/répertoire parent au PYTHONPATH(je veux dire le répertoire qui contient les deuxuser_management et Scripts):

PYTHONPATH=/the/parent/directory python Scripts/CreateUser.py

Ou vous pouvez modifier le PYTHONPATH globalement afin de ne pas avoir à le spécifier à chaque fois. Sur les OS Unix (Linux, Mac OS X etc.), vous pouvez modifier l'un des scripts shell pour définir la PYTHONPATHvariable externe, sous Windows, vous devez changer les paramètres des variables d'environnement.


Addenda Je pense que si vous utilisez python2, il vaut mieux éviter les importations relatives implicites en mettant:

from __future__ import absolute_import

en haut de vos modules. De cette manière signifie import X toujours importer le module de niveau supérieurX et n'essaiera jamais d'importer le X.pyfichier qui se trouve dans le même répertoire (si ce répertoire n'est pas dans le PYTHONPATH). De cette façon, la seule façon de faire une importation relative est d'utiliser la syntaxe explicite (la from . import X), qui est meilleure ( explicite vaut mieux qu'implicite ).

Cela garantira que vous n'utiliserez jamais les importations relatives implicites "fausses", car celles-ci soulèveraient un ImportErrorsignalement clair que quelque chose ne va pas. Sinon, vous pourriez utiliser un module qui n'est pas ce que vous pensez.

Bakuriu
la source
Si vous utilisez des importations relatives, vous devez exécuterpython -m user_management.Scripts.CreateUser
mononoke
14

À partir de Python 2.5, vous pouvez utiliser

from ..Modules import LDAPManager

La période principale vous fait «monter» d'un niveau dans votre héritage.

Consultez la documentation Python sur les références intra-package pour les importations.

Jonrsharpe
la source
3

Dans la "racine", __init__.pyvous pouvez également faire un

import sys
sys.path.insert(1, '.')

ce qui devrait rendre les deux modules importables.

rdodev
la source