Comment faire des importations relatives en Python?

528

Imaginez cette structure de répertoire:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Je suis en train de coder mod1et j'ai besoin d'importer quelque chose mod2. Comment dois-je procéder?

J'ai essayé from ..sub2 import mod2mais j'obtiens une «tentative d'importation relative dans un non-package».

Je suis allé sur Google, mais je n'ai trouvé que sys.pathdes hacks de " manipulation". N'y a-t-il pas une voie propre?


Edit: tous les mes __init__.pysont actuellement vides

Edit2: Je suis en train de le faire parce que SUB2 contient des classes qui sont partagées entre les sous répertoires ( sub1, subX, etc.).

Edit3: Le comportement que je recherche est le même que celui décrit dans PEP 366 (merci John B)

Joril
la source
8
Je recommande de mettre à jour votre question pour qu'il soit plus clair que vous décrivez le problème abordé dans PEP 366.
John B
2
C'est une longue explication mais vérifiez ici: stackoverflow.com/a/10713254/1267156 J'ai répondu à une question très similaire. J'ai eu ce même problème jusqu'à hier soir.
Sevvy325
3
Pour ceux qui souhaitent charger un module situé sur un chemin arbitraire, voir ceci: stackoverflow.com/questions/67631/…
Evgeni Sergeev
2
Sur une note connexe, Python 3 changera la gestion par défaut des importations pour qu'elle soit absolue par défaut; les importations relatives devront être explicitement spécifiées.
Ross

Réponses:

337

Tout le monde semble vouloir vous dire ce que vous devriez faire plutôt que de simplement répondre à la question.

Le problème est que vous exécutez le module en tant que '__main__' en passant le mod1.py comme argument à l'interpréteur.

Depuis PEP 328 :

Les importations relatives utilisent l'attribut __name__ d'un module pour déterminer la position de ce module dans la hiérarchie des packages. Si le nom du module ne contient aucune information sur le package (par exemple, il est défini sur '__main__'), les importations relatives sont résolues comme si le module était un module de niveau supérieur, quel que soit l'emplacement du module sur le système de fichiers.

Dans Python 2.6, ils ajoutent la possibilité de référencer des modules par rapport au module principal. Le PEP 366 décrit le changement.

Mise à jour : Selon Nick Coghlan, l'alternative recommandée consiste à exécuter le module à l'intérieur du package à l'aide du commutateur -m.

John B
la source
2
La réponse ici consiste à jouer avec sys.path à chaque point d'entrée de votre programme. Je suppose que c'est la seule façon de le faire.
Nick Retallack
76
L'alternative recommandée consiste à exécuter des modules à l'intérieur des packages à l'aide du -mcommutateur, plutôt qu'en spécifiant directement leur nom de fichier.
ncoghlan
127
Je ne comprends pas: où est la réponse ici? Comment importer des modules dans une telle structure de répertoires?
Tom
27
@Tom: Dans ce cas, mod1 le ferait from sub2 import mod2. Ensuite, pour exécuter mod1, à partir de l'application, faites python -m sub1.mod1.
Xiong Chiamiov
11
@XiongChiamiov: cela signifie-t-il que vous ne pouvez pas le faire si votre python est intégré dans une application, vous n'avez donc pas accès aux commutateurs de ligne de commande python?
LarsH
130

Voici la solution qui me convient:

Je fais les importations relatives en tant from ..sub2 import mod2 que puis, si je veux exécuter, mod1.pyje vais dans le répertoire parent de appet exécute le module en utilisant le commutateur python -m as python -m app.sub1.mod1.

La vraie raison pour laquelle ce problème se produit avec les importations relatives est que les importations relatives fonctionnent en prenant la __name__propriété du module. Si le module est exécuté directement, il __name__est défini sur __main__et ne contient aucune information sur la structure du package. Et, c'est pourquoi python se plaint de l' relative import in non-packageerreur.

Ainsi, en utilisant le commutateur -m, vous fournissez les informations de structure de package à python, grâce auxquelles il peut résoudre les importations relatives avec succès.

J'ai rencontré ce problème plusieurs fois lors d'importations relatives. Et, après avoir lu toutes les réponses précédentes, je n'arrivais toujours pas à comprendre comment le résoudre, d'une manière propre, sans avoir besoin de mettre du code passe-partout dans tous les fichiers. (Bien que certains des commentaires aient été très utiles, merci à @ncoghlan et @XiongChiamiov)

J'espère que cela aide quelqu'un qui se bat avec un problème d'importations relatives, car passer par le PEP n'est vraiment pas amusant.

Pankaj
la source
9
Meilleure réponse à mon humble avis: explique non seulement pourquoi OP a eu le problème, mais trouve également un moyen de le résoudre sans changer la façon dont ses modules importent . Après tout, les importations relatives d'OP se sont bien passées. Le coupable était le manque d'accès aux packages externes lors de l'exécution directe en tant que script, quelque chose a -mété conçu pour résoudre.
MestreLion
26
Prenez également note: cette réponse était de 5 ans après la question. Ces fonctionnalités n'étaient pas disponibles à l'époque.
JeremyKun
1
Si vous souhaitez importer un module du même répertoire, vous pouvez le faire from . import some_module.
Rotareti
124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Tu cours python main.py.
  2. main.py Est-ce que: import app.package_a.module_a
  3. module_a.py Est-ce que import app.package_b.module_b

Alternativement, 2 ou 3 pourraient utiliser: from app.package_a import module_a

Cela fonctionnera aussi longtemps que vous en aurez appdans votre PYTHONPATH. main.pypourrait être n'importe où alors.

Vous écrivez donc un setup.pypour copier (installer) l'ensemble du package et des sous-packages de l'application dans les dossiers python du système cible et main.pydans les dossiers de script du système cible.

nosklo
la source
3
Excellente réponse. Existe-t-il un moyen d'importer de cette manière sans installer le package dans PYTHONPATH?
auraham
4
Lectures
nosklo
6
puis, un jour, il faut changer le nom de l'application en test_app. ce qui se passerait? Vous devrez modifier tous les codes source, importer app.package_b.module_b -> test_app.package_b.module_b. c'est absolument une mauvaise pratique ... Et nous devrions essayer d'utiliser l'importation relative dans le package.
Spybdai
49

"Guido considère l'exécution de scripts dans un package comme un anti-modèle" (rejeté PEP-3122 )

J'ai passé tellement de temps à essayer de trouver une solution, à lire les articles liés ici sur Stack Overflow et à me dire "il doit y avoir une meilleure façon!". On dirait qu'il n'y en a pas.

lesnik
la source
10
Remarque: déjà mentionné pep-366 (créé à peu près au même moment que pep-3122 ) offre les mêmes capacités mais utilise une implémentation différente compatible en amont, c'est-à-dire si vous voulez exécuter un module à l'intérieur d'un package en tant que script et utiliser des importations relatives explicites vous pouvez alors l'exécuter en utilisant -mswitch: python -m app.sub1.mod1ou invoquer à app.sub1.mod1.main()partir d'un script de niveau supérieur (par exemple, généré à partir des points d'entrée de setuptools définis dans setup.py).
jfs
+1 pour l'utilisation de setuptools et de points d'entrée - c'est un bon moyen de configurer des scripts qui seront exécutés de l'extérieur, dans un endroit bien défini, par opposition au piratage sans fin PYTHONPATH
RecencyEffect
38

Ceci est résolu à 100%:

  • app /
    • main.py
  • réglages/
    • local_setings.py

Importez les paramètres / local_setting.py dans l'application / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')
Роман Арсеньев
la source
2
Merci! tous les ppl me forçaient à exécuter mon script différemment au lieu de me dire comment le résoudre dans le script. Mais j'ai dû changer le code à utiliser sys.path.insert(0, "../settings"), puisfrom local_settings import *
Vit Bernatik
25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

J'utilise cet extrait pour importer des modules à partir de chemins, j'espère que cela aide

iElectric
la source
2
J'utilise cet extrait de code, combiné avec le module imp (comme expliqué ici [1]) pour un grand effet. [1]: stackoverflow.com/questions/1096216/…
Xiong Chiamiov
7
Probablement, sys.path.append (chemin) doit être remplacé par sys.path.insert (0, chemin) et sys.path [-1] doit être remplacé par sys.path [0]. Sinon, la fonction importera le mauvais module, s'il existe déjà un module du même nom dans le chemin de recherche. Par exemple, s'il y a "some.py" dans le répertoire courant, import_path ("/ imports / some.py") importera le mauvais fichier.
Alex Che
Je suis d'accord! Parfois, d'autres importations relatives prévaudront. Utilisez sys.path.insert
iElectric
Comment répliqueriez-vous le comportement de x import y (ou *)?
levesque
Ce n'est pas clair, veuillez spécifier l'utilisation complète de ce script pour résoudre le problème OP.
mrgloom
21

explication de la nosklo'sréponse avec des exemples

remarque: tous les __init__.pyfichiers sont vides.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

si vous l'exécutez, $ python main.pyil renvoie:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py: from app.package_b import fun_b
  • fun_b.py fait from app.package_a.fun_a import print_a

donc fichier dans le dossier package_butilisé fichier dans le dossier package_a, ce que vous voulez. Droite??

suhailvs
la source
12

Il s'agit malheureusement d'un hack sys.path, mais cela fonctionne assez bien.

J'ai rencontré ce problème avec une autre couche: j'avais déjà un module du nom spécifié, mais ce n'était pas le bon module.

ce que je voulais faire était le suivant (le module à partir duquel je travaillais était module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Notez que j'ai déjà installé mymodule, mais dans mon installation je n'ai pas "mymodule1"

et j'obtiendrais un ImportError car il essayait d'importer à partir de mes modules installés.

J'ai essayé de faire un sys.path.append, et cela n'a pas fonctionné. Ce qui a fonctionné était un sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Donc, une sorte de hack, mais tout a fonctionné! Alors gardez à l'esprit, si vous voulez que votre décision écrase d'autres chemins, vous devez utiliser sys.path.insert (0, chemin d'accès) pour le faire fonctionner! C'était un point de friction très frustrant pour moi, beaucoup de gens disent d'utiliser la fonction "append" dans sys.path, mais cela ne fonctionne pas si vous avez déjà un module défini (je trouve cela très étrange)

Garrett Berg
la source
sys.path.append('../')fonctionne bien pour moi (Python 3.5.2)
Nister
Je pense que c'est bien car il localise le hack à l'exécutable et n'affecte pas les autres modules qui peuvent dépendre de vos packages.
Tom Russell
10

Permettez-moi de mettre ceci ici pour ma propre référence. Je sais que ce n'est pas du bon code Python, mais j'avais besoin d'un script pour un projet sur lequel je travaillais et je voulais mettre le script dans un scriptsrépertoire.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
postier
la source
9

Comme @EvgeniSergeev le dit dans les commentaires de l'OP, vous pouvez importer du code à partir d'un .pyfichier à un emplacement arbitraire avec:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Ceci est tiré de cette réponse SO .

LondonRob
la source
2

De doc Python ,

Dans Python 2.5, vous pouvez changer le comportement de l'importation en importations absolues à l'aide d'une from __future__ import absolute_importdirective. Ce comportement d'importation absolue deviendra la valeur par défaut dans une future version (probablement Python 2.7). Une fois les importations absolues par défaut, import stringla version standard de la bibliothèque sera toujours trouvée. Il est suggéré que les utilisateurs devraient commencer à utiliser les importations absolues autant que possible, il est donc préférable de commencer à écrire from pkg import stringdans votre code

jung rhew
la source
1

J'ai trouvé qu'il était plus facile de définir la variable d'environnement "PYTHONPATH" dans le dossier supérieur:

bash$ export PYTHONPATH=/PATH/TO/APP

puis:

import sub1.func1
#...more import

bien sûr, PYTHONPATH est "global", mais cela ne m'a pas encore posé de problème.

Andrew_1510
la source
C'est essentiellement ainsi que virtualenvvous pouvez gérer vos instructions d'importation.
byxor
1

En plus de ce que John B a dit, il semble que la définition de la __package__variable devrait aider, au lieu de changer __main__ce qui pourrait gâcher d'autres choses. Mais pour autant que je puisse tester, cela ne fonctionne pas complètement comme il se doit.

J'ai le même problème et aucun PEP 328 ou 366 ne résout le problème complètement, car les deux, à la fin de la journée, ont besoin que la tête du paquet soit incluse sys.path, pour autant que je puisse comprendre.

Je dois également mentionner que je n'ai pas trouvé comment formater la chaîne qui devrait aller dans ces variables. C'est "package_head.subfolder.module_name"ou quoi?

Gabriel
la source
0

Vous devez ajouter le chemin du module à PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Giorgos Myrianthous
la source
1
C'est à peu près la même chose que la manipulation sys.path, car elle sys.pathest initialisée à partir dePYTHONPATH
Joril
@Joril C'est correct mais sys.pathdoit être codé en dur dans le code source contrairement à PYTHONPATHce qui est une variable d'environnement et peut être exporté.
Giorgos Myrianthous