Importation de modules à partir du dossier parent

627

J'utilise Python 2.5.

Voici mon arborescence de dossiers:

ptdraft/
  nib.py
  simulations/
    life/
      life.py

(J'ai également __init__.pydans chaque dossier, omis ici pour plus de lisibilité)

Comment importer le nibmodule depuis l'intérieur du lifemodule? J'espère qu'il est possible de le faire sans bricoler avec sys.path.

Remarque: Le module principal en cours d'exécution se trouve dans le ptdraftdossier.

Ram Rachum
la source
1
Quel est votre réglage PYTHONPATH?
S.Lott
2
Ross: J'ai regardé là-bas. Que dois-je faire à ce sujet? J'en ai déjà un __init__.py. S.Lott: Je ne sais pas comment vérifier ...
Ram Rachum
4
echo $ PYTHONPATH du shell; import sys; imprimer sys.path depuis Python. docs.python.org/tutorial/…
S.Lott
@FlipMcF Google est un moteur de recherche bouillonnant, donc le fait que ce résultat soit assez élevé pour vous n'a pas d'importance. Beaucoup plus important est le fait que le moteur de recherche sans bulles, DuckDuckGo, classe également cela très haut.
ArtOfWarfare
3
Je recommande vivement sauter passé tout sys.pathou PYTHONPATHréponses et vérifier excellente réponse de NP8 . Oui, c'est une longue lecture. Oui, cela ressemble à beaucoup de travail. Mais c'est la seule réponse qui résout le problème correctement et proprement.
Aran-Fey

Réponses:

119

Il semble que le problème ne soit pas lié au fait que le module se trouve dans un répertoire parent ou quelque chose comme ça.

Vous devez ajouter le répertoire contenant ptdraftPYTHONPATH

Vous avez dit que cela import nibfonctionnait avec vous, cela signifie probablement que vous vous êtes ajouté ptdraft(pas son parent) à PYTHONPATH.

hasen
la source
15
Devoir tout ajouter à pythonpath n'est pas une bonne solution si vous travaillez avec beaucoup de paquets qui ne seront jamais réutilisés.
Jason Cheng
@ JasonCheng: Alors pourquoi sont-ils des packages ?
Davis Herring
7
Pour moi, cette réponse a fonctionné. Cependant, pour le rendre plus explicite, la procédure est à import syset ensuitesys.path.append("..\<parent_folder>")
BCJuan
604

Vous pouvez utiliser des importations relatives (python> = 2.5):

from ... import nib

(Quoi de neuf dans Python 2.5) PEP 328: Importations absolues et relatives

EDIT : ajouté un autre point '.' monter deux packages

f3lix
la source
178
ValueError: Attempted relative import in non-package
endolith
18
@endolith: Vous devez être dans un package, c'est-à-dire que vous devez avoir un fichier init .py.
kirbyfan64sos
31
Tentative d'importation relative au-delà du package de niveau supérieur
Utilisateur
291
Pour être encore plus précis, vous avez besoin d'un __init__.pyfichier.
kara deniz
17
Voir également la réponse suivante, car l'ajout __init__.pyn'est pas la seule chose que vous devez faire: stackoverflow.com/questions/11536764/…
Ben Farmer
286

Les importations relatives (comme dans from .. import mymodule) ne fonctionnent que dans un package. Pour importer 'mymodule' qui se trouve dans le répertoire parent de votre module actuel:

import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir) 

import mymodule

edit : l' __file__attribut n'est pas toujours donné. Au lieu d'utiliser, os.path.abspath(__file__)j'ai maintenant suggéré d'utiliser le module d'inspection pour récupérer le nom de fichier (et le chemin) du fichier actuel

Rémi
la source
97
quelque chose d'un peu plus court:sys.path.insert(1, os.path.join(sys.path[0], '..'))
JHolta
8
Une raison pour éviter sys.path.append () au lieu de l'insertion?
Tyler
2
@Tyler - Cela peut être important si ailleurs parentdir, mais dans l'un des chemins déjà spécifiés dans sys.path, il y a un autre module avec le nom 'mymodule'. L'insertion de parentdiras comme premier élément de la sys.pathliste garantit que le module de parentdirsera importé à la place.
Remi
5
@JHolta Si vous ne traitez pas avec des packages, la vôtre est la meilleure solution.
Jonathon Reinhart
5
@JHolta excellente idée. sys.path.insert(1, os.path.realpath(os.path.pardir))fonctionne aussi.
SpeedCoder5
179

J'ai également publié une réponse similaire à la question concernant les importations à partir de paquets frères. Vous pouvez le voir ici .

Solution sans sys.pathhacks

Sommaire

  • Enveloppez le code dans un dossier (par exemple packaged_stuff)
  • Utilisez le setup.pyscript de création dans lequel vous utilisez setuptools.setup () .
  • Pip installe le package dans un état modifiable avec pip install -e <myproject_folder>
  • Importer à l'aide from packaged_stuff.modulename import function_name

Installer

Je suppose que la même structure de dossiers que dans la question

.
└── ptdraft
    ├── __init__.py
    ├── nib.py
    └── simulations
        ├── __init__.py
        └── life
            ├── __init__.py
            └── life.py

J'appelle le .dossier racine, et dans mon cas, il se trouve dans C:\tmp\test_imports.

Pas

1) Ajoutez un setup.pyau dossier racine

Le contenu de la setup.pypeut être simplement

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())

Fondamentalement, «tout» setup.pyfonctionnerait. Ceci est juste un exemple de travail minimal.

2) Utilisez un environnement virtuel

Si vous connaissez les environnements virtuels, activez-en un et passez à l'étape suivante. L'utilisation d'environnements virtuels n'est pas absolument requise, mais ils vous aideront vraiment à long terme (lorsque vous avez plus d'un projet en cours ..). Les étapes les plus élémentaires sont (exécutées dans le dossier racine)

  • Créer un env virtuel
    • python -m venv venv
  • Activer env virtuel
    • . /venv/bin/activate(Linux) ou ./venv/Scripts/activate(Win)

Pour en savoir plus à ce sujet, il suffit de Google sur "tutoriel python virtualenv" ou similaire. Vous n'avez probablement jamais besoin d'autres commandes que la création, l'activation et la désactivation.

Une fois que vous avez créé et activé un environnement virtuel, votre console doit donner le nom de l'environnement virtuel entre parenthèses

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>

3) Pip installez votre projet dans un état modifiable

Installez votre package de niveau supérieur à l' myprojectaide de pip. L'astuce consiste à utiliser l' -eindicateur lors de l'installation. De cette façon, il est installé dans un état modifiable et toutes les modifications apportées aux fichiers .py seront automatiquement incluses dans le package installé.

Dans le répertoire racine, exécutez

pip install -e . (notez le point, il signifie "répertoire courant")

Vous pouvez également voir qu'il est installé à l'aide de pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0

4) Importer en ajoutant mainfolderà chaque importation

Dans cet exemple, le mainfolderserait ptdraft. Cela présente l'avantage que vous ne rencontrerez pas de collisions de noms avec d'autres noms de module (à partir de la bibliothèque standard python ou de modules tiers).


Exemple d'utilisation

nib.py

def function_from_nib():
    print('I am the return value from function_from_nib!')

life.py

from ptdraft.nib import function_from_nib

if __name__ == '__main__':
    function_from_nib()

Running life.py

(venv) PS C:\tmp\test_imports> python .\ptdraft\simulations\life\life.py
I am the return value from function_from_nib!
np8
la source
1
Pourquoi ça marche? Est-ce que ça change le PYTHONPATH dans les coulisses d'une manière ou d'une autre?
Homero Esmeraldo du
2
Aussi, pourquoi est-ce mieux ou plus élégant que d'utiliser l'approche sys.path?
Homero Esmeraldo
5
@HomeroBarrocasSEsmeraldo Bonnes questions. PYTHONPATH env var est intact. Cela s'installe dans les packages de site , qui sont déjà activés sys.pathet où le code serait généralement installé par les utilisateurs finaux. C'est mieux que les sys.pathhacks (et vous pouvez inclure l'utilisation de PYTHONPATH dans cette catégorie de hacks de chemin) car cela signifie que le code en développement devrait se comporter de la même manière pour vous que pour les autres utilisateurs. En revanche, lors de l'utilisation de modifications de chemin, les instructions d'importation seraient résolues d'une manière différente.
wim
5
C'est de loin LA meilleure solution que j'ai lue. Cela crée un lien vers votre répertoire actuel afin que toutes les mises à jour du code soient directement reflétées dans votre répertoire de travail. Si vous devez supprimer cette entrée, supprimez manuellement le fichier egg et supprimez l'entrée de easy-install dans dist-packages (site-packages si vous avez utilisé pip).
Rakshit Kothari
2
pourquoi rendre le mal de tête d'importation python encore plus compliqué? Nous devons écrire un fichier d'installation, un env virtuel, une installation pip? OMG!
Emerson Xu
68

Vous pouvez utiliser le chemin d'accès dépendant du système d'exploitation dans le "chemin de recherche de module" qui est répertorié dans sys.path . Ainsi, vous pouvez facilement ajouter un répertoire parent comme suit

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

Si vous souhaitez ajouter un répertoire parent-parent,

sys.path.insert(0,'../..')

Cela fonctionne à la fois en python 2 et 3.

Shinbero
la source
6
Ceci est relatif au répertoire courant , pas même nécessairement celui contenant le script principal.
Davis Herring
57

Si l'ajout de votre dossier de modules au PYTHONPATH n'a pas fonctionné, vous pouvez modifier la liste sys.path dans votre programme où l'interpréteur Python recherche les modules à importer, la documentation de python indique:

Lorsqu'un module nommé spam est importé, l'interpréteur recherche d'abord un module intégré portant ce nom. S'il n'est pas trouvé, il recherche ensuite un fichier nommé spam.py dans une liste de répertoires donnée par la variable sys.path. sys.path est initialisé à partir de ces emplacements:

  • le répertoire contenant le script d'entrée (ou le répertoire courant).
  • PYTHONPATH (une liste de noms de répertoires, avec la même syntaxe que la variable shell PATH).
  • la valeur par défaut dépendante de l'installation.

Après l'initialisation, les programmes Python peuvent modifier sys.path . Le répertoire contenant le script en cours d'exécution est placé au début du chemin de recherche, avant le chemin de bibliothèque standard. Cela signifie que les scripts de ce répertoire seront chargés à la place des modules du même nom dans le répertoire de la bibliothèque. Il s'agit d'une erreur, sauf si le remplacement est prévu.

Sachant cela, vous pouvez effectuer les opérations suivantes dans votre programme:

import sys
# Add the ptdraft folder path to the sys.path list
sys.path.append('/path/to/ptdraft/')

# Now you can import your module
from ptdraft import nib
# Or just
import ptdraft
Ricardo Murillo
la source
6
Votre réponse est bonne, mais peut ne pas toujours fonctionner et n'est pas très portable. Si le programme était déplacé vers un nouvel emplacement, il /path/to/ptdraftfaudrait le modifier. Il existe des solutions qui élaborent le répertoire actuel du fichier et l'importent également à partir du dossier parent.
Edward
@Edward quelles sont ces techniques?
Shuklaswag
1
@Shuklaswag par exemple, la réponse stackoverflow.com/questions/714063/… .
Edward
50

Je ne sais pas grand-chose sur python 2.
En python 3, le dossier parent peut être ajouté comme suit:

import sys 
sys.path.append('..')

... et puis on peut en importer des modules

Rob Ellenbroek
la source
13
Cela ne fonctionne que si votre répertoire de travail actuel est tel qu'il '..'mène au répertoire avec le module en question.
user5359531
31

Voici une réponse simple pour que vous puissiez voir comment cela fonctionne, petit et multiplateforme.
Il n'utilise que des modules intégrés ( os, syset inspect) et devrait donc fonctionner
sur n'importe quel système d'exploitation (OS) car Python est conçu pour cela.

Code de réponse plus court - moins de lignes et de variables

from inspect import getsourcefile
import os.path as path, sys
current_dir = path.dirname(path.abspath(getsourcefile(lambda:0)))
sys.path.insert(0, current_dir[:current_dir.rfind(path.sep)])
import my_module  # Replace "my_module" here with the module name.
sys.path.pop(0)

Pour moins de lignes que cela, remplacez la deuxième ligne par import os.path as path, sys, inspect,
ajoutez inspect.au début de getsourcefile(ligne 3) et supprimez la première ligne.
- cependant, cela importe tout le module et pourrait donc nécessiter plus de temps, de mémoire et de ressources.

Le code de ma réponse ( version longue )

from inspect import getsourcefile
import os.path
import sys

current_path = os.path.abspath(getsourcefile(lambda:0))
current_dir = os.path.dirname(current_path)
parent_dir = current_dir[:current_dir.rfind(os.path.sep)]

sys.path.insert(0, parent_dir)

import my_module  # Replace "my_module" here with the module name.

Il utilise un exemple d'une réponse Stack Overflow Comment puis-je obtenir le chemin du
fichier en cours d' exécution en Python?
pour trouver la source (nom de fichier) du code en cours d'exécution avec un outil intégré.

from inspect import getsourcefile  
from os.path import abspath  

Ensuite, où que vous souhaitiez trouver le fichier source, utilisez simplement:

abspath(getsourcefile(lambda:0))

Mon code ajoute un chemin de fichier à sys.path, la liste des chemins de python
car cela permet à Python d'importer des modules de ce dossier.

Après avoir importé un module dans le code, c'est une bonne idée de s'exécuter sys.path.pop(0)sur une nouvelle ligne
lorsque ce dossier ajouté a un module portant le même nom qu'un autre module importé
plus tard dans le programme. Vous devez supprimer l'élément de liste ajouté avant l'importation, pas les autres chemins.
Si votre programme n'importe pas d'autres modules, il est prudent de ne pas supprimer le chemin du fichier car
après la fin d'un programme (ou le redémarrage du shell Python), toutes les modifications apportées sys.pathdisparaissent.

Remarques sur une variable de nom de fichier

Ma réponse n'utilise pas la __file__variable pour obtenir le chemin d'accès / nom de fichier du
code en cours d'exécution car les utilisateurs ici l'ont souvent décrit comme peu fiable . Vous ne devez pas l'utiliser
pour importer des modules du dossier parent dans des programmes utilisés par d'autres personnes.

Quelques exemples où cela ne fonctionne pas (citation de cette question Stack Overflow):

• il ne peut pas être trouvé sur certaines plates-formes • ce n'est parfois pas le chemin complet du fichier

  • py2exen'a pas d' __file__attribut, mais il existe une solution de contournement
  • Lorsque vous exécutez depuis IDLE avec execute()aucun __file__attribut
  • OS X 10.6 où je reçois NameError: global name '__file__' is not defined
Edward
la source
1
J'ai fait cela et j'ai supprimé la nouvelle entrée de chemin sys.path.pop(0)immédiatement après avoir importé le module souhaité. Cependant, les importations ultérieures sont toujours allées à cet endroit. Par exemple, j'ai app/config, app/toolset app/submodule/config. De submodule, j'insère app/pour importer tools, puis supprime app/et essaie d'importer config, mais j'obtiens à la app/configplace de app/submodule/config.
user5359531
J'ai compris que l 'une des toolsimportations importait également du répertoire configparent. Donc, quand j'ai essayé de faire plus tard à l' sys.path.pop(0); import configintérieur submodule, en espérant obtenir app/submodule/config, j'obtenais réellement app/config. De toute évidence, Python renvoie une version en cache d'un module portant le même nom au lieu de vérifier réellement la présence sys.pathd'un module correspondant à ce nom. sys.pathdans ce cas était modifié correctement, Python ne le vérifiait tout simplement pas car le module portant le même nom avait déjà été chargé.
user5359531
Je pense que c'est la référence exacte au problème que j'ai eu: docs.python.org/3/reference/import.html#the-module-cache
user5359531
Quelques informations utiles de 5.3.1. Le cache du module sur la page de documentation : Lors de l'importation, le nom du module est recherché dans sys.modules ... sys.modules est accessible en écriture. La suppression d'une clé invalidera l'entrée de cache pour le module nommé, obligeant Python à effectuer une nouvelle recherche lors de sa prochaine importation. ... Attention cependant, comme si vous gardiez une référence à l'objet module, invalidiez son cache puis réimportiez, les deux objets seront différents. En revanche, importlib.reload()va réutiliser et réinitialiser le contenu du module ...
Edward
Encore plus court: os.path.realpath(getsourcefile(lambda:0) + "/../.."). Obtiendra le fichier source actuel ajouté avec deux symboles de répertoire parent. Je n'ai pas utilisé os.path.sepcomme cela getsourcefilerenvoie une chaîne utilisant /même sous Windows. realpathprendra soin de faire sauter deux entrées de répertoire à partir du chemin complet (dans ce cas, le nom de fichier, puis le répertoire actuel) qui donne le répertoire parent.
Coburn
28

Voici une solution plus générique qui inclut le répertoire parent dans sys.path (fonctionne pour moi):

import os.path, sys
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
Mike
la source
import os, sys\n sys.path.insert(0,os.path.pardir) même chose, mais sorther :) (pas de ligne dans les commentaires)
Anti Veeranna
@antiveeranna, si vous utilisez, os.path.pardirvous n'obtiendrez pas le chemin réel du parent, juste le chemin relatif d'où vous appelez lesys.path.insert()
alvas
1
Cette réponse utilise la __file__variable qui peut être peu fiable (n'est pas toujours le chemin d'accès complet au fichier, ne fonctionne pas sur tous les systèmes d'exploitation, etc.) comme les utilisateurs de StackOverflow l'ont souvent mentionné. Changer la réponse pour ne pas l'inclure causera moins de problèmes et sera plus compatible avec les autres. Pour plus d'informations, voir stackoverflow.com/a/33532002/3787376 .
Edward
23

J'ai trouvé que la méthode suivante fonctionne pour importer un package à partir du répertoire parent du script. Dans l'exemple, je voudrais importer des fonctions env.pydepuis le app.dbpackage.

.
└── my_application
    └── alembic
        └── env.py
    └── app
        ├── __init__.py
        └── db
import os
import sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
Yu N.
la source
Donc, un joli one-liner:sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
juzzlin
La moindre modification sys.pathest généralement une mauvaise idée. Vous avez besoin d'un moyen ( par exemple , PYTHONPATH) pour que votre bibliothèque soit accessible à un autre code (ou bien ce n'est pas vraiment une bibliothèque); utilisez-le tout le temps!
Davis Herring
14

Les solutions mentionnées ci-dessus conviennent également. Une autre solution à ce problème est

Si vous souhaitez importer quoi que ce soit à partir du répertoire de niveau supérieur. Alors,

from ...module_name import *

En outre, si vous souhaitez importer un module du répertoire parent. Alors,

from ..module_name import *

En outre, si vous souhaitez importer un module du répertoire parent. Alors,

from ...module_name.another_module import *

De cette façon, vous pouvez importer n'importe quelle méthode particulière si vous le souhaitez.

Ravin Gupta
la source
6
Cela semble être comme cela devrait être, mais pour une raison quelconque, cela ne fonctionne pas de la même manière que les sys.path.appendhacks ci-dessus et je ne peux pas importer ma bibliothèque comme ça. C'est ce que j'ai essayé de faire avant de commencer sur Google :) Ok, c'était çaValueError: attempted relative import beyond top-level package
juzzlin
10

Pour moi, le plus court et mon oneliner préféré pour accéder au répertoire parent est:

sys.path.append(os.path.dirname(os.getcwd()))

ou:

sys.path.insert(1, os.path.dirname(os.getcwd()))

os.getcwd () renvoie le nom du répertoire de travail en cours, os.path.dirname (nom_répertoire) renvoie le nom du répertoire de celui qui a été transmis.

En fait, à mon avis, l'architecture de projet Python devrait être effectuée de la manière où aucun module du répertoire enfant n'utilisera de module du répertoire parent. Si quelque chose comme cela se produit, il vaut la peine de repenser l'arborescence du projet.

Une autre façon consiste à ajouter le répertoire parent à la variable d'environnement système PYTHONPATH.

zviad
la source
2
+1 pour avoir mentionné la possibilité de devoir restructurer le projet (au lieu de lancer un hack sur le problème)
semore_1267
cela n'obtient pas le parent, il obtient le courant
Ricky Levi
9

Dans un cahier Jupyter

Tant que vous travaillez dans un bloc-notes Jupyter, cette courte solution peut être utile:

%cd ..
import nib

Cela fonctionne même sans __init__.py fichier.

Je l'ai testé avec Anaconda3 sur Linux et Windows 7.

Thomas R
la source
2
Ne serait-il pas judicieux d'ajouter %cd -après l'importation, de sorte que le répertoire actuel du bloc-notes reste inchangé.
Dror
8

import sys sys.path.append('../')

Andong Zhan
la source
8
cela ne fonctionnera pas lorsqu'il est exécuté à partir d'un répertoire différent.
Shatlyk Ashyralyyev
5

Lorsqu'elle n'est pas dans un environnement de package avec des __init__.pyfichiers, la bibliothèque pathlib (incluse avec> = Python 3.4) rend très concis et intuitif l'ajout du chemin du répertoire parent au PYTHONPATH:

import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))
Cela compte
la source
est-il possible d'utiliser init .py pour contourner ce problème?
ArtificiallyIntelligence
4

même style que la réponse précédente - mais en moins de lignes: P

import os,sys
parentdir = os.path.dirname(__file__)
sys.path.insert(0,parentdir)

le fichier renvoie l'emplacement dans lequel vous travaillez

YFP
la source
Pour moi, le fichier est le nom du fichier sans le chemin d'accès inclus. Je l'exécute en utilisant "ipy filename.py".
Curtis Yallop
Salut @CurtisYallop dans l'exemple ci-dessus, nous ajoutons le répertoire contenant le fichier [dans lequel nous sommes actuellement] dans lequel se trouve le fichier python. L'appel os.path.dirname avec le nom de fichier doit renvoyer le chemin du fichier et nous ajoutons cela au chemin et non au fichier explicitement - HTH :-)
YFP
Vous supposez que __file__ contient toujours le chemin plus le fichier. Parfois, il ne contient que le nom de fichier sans le chemin d'accès.
Curtis Yallop
@CurtisYallop - pas du tout monsieur, je suppose que fichier est le nom du fichier et j'utilise os.path.dirname () pour obtenir le chemin d'accès au fichier. Avez-vous un exemple de cela qui ne fonctionne pas? J'adorerais les étapes pour reproduire
YFP
1
@CurtisYallop - Non, je ne le suis pas! Je dis que: print / _ / file / _ / vous donnera le nom de fichier dans votre exemple ci-dessus "file.py" - Je dis ensuite que vous pouvez utiliser os.path.dirname () pour tirer le chemin complet de cette . Si vous appelez depuis un endroit étrange et que vous souhaitez que ce soit relationnel, vous pouvez facilement trouver votre répertoire de travail via le module os - Jeez!
YFP
1

Travaillez avec les bibliothèques. Créez une bibliothèque appelée nib, installez-la à l'aide de setup.py, laissez-la résider dans des packages de site et vos problèmes sont résolus. Vous n'avez pas à fourrer tout ce que vous faites dans un seul paquet. Brisez-le en morceaux.

user3181121
la source
1
Votre idée est bonne, mais certaines personnes voudront peut-être regrouper leur module avec leur code - peut-être pour le rendre portable et ne pas exiger de mettre leurs modules à site-packageschaque fois qu'ils l'exécutent sur un ordinateur différent.
Edward
0

Dans un système Linux, vous pouvez créer un lien logiciel du dossier "life" vers le fichier nib.py. Ensuite, vous pouvez simplement l'importer comme:

import nib
Erel Segal-Halevi
la source