web-dev-qa-db-fra.com

Importations de colis frères et soeurs

J'ai essayé de lire des questions sur les importations par les frères et même documentation du paquet , mais je n'ai pas encore trouvé de réponse.

Avec la structure suivante:

├── LICENSE.md
├── README.md
├── api
│   ├── __init__.py
│   ├── api.py
│   └── api_key.py
├── examples
│   ├── __init__.py
│   ├── example_one.py
│   └── example_two.py
└── tests
│   ├── __init__.py
│   └── test_one.py

Comment les scripts des répertoires examples et tests peuvent-ils être importés à partir du module api et exécutés à partir de la ligne de commande?

Aussi, je voudrais éviter le laid sys.path.insert hack pour chaque fichier. Cela peut sûrement être fait en Python, non?

133
zachwill

Sept ans après

Depuis que j'ai écrit la réponse ci-dessous, modifier sys.path Reste un truc rapide qui fonctionne bien pour les scripts privés, mais plusieurs améliorations ont été apportées.

  • Installation le paquet (dans un virtualenv ou non) vous donnera ce que vous voulez, bien que je suggère d'utiliser pip pour le faire plutôt que d'utiliser setuptools directement (et d'utiliser setup.cfg Pour stocker le métadonnées)
  • tiliser l'indicateur -m et s'exécuter en tant que paquet fonctionne également (mais s'avérera un peu gênant si vous voulez convertir votre répertoire de travail en un paquet installable).
  • Pour les tests, spécifiquement, pytest est capable de trouver le paquet api dans cette situation et s’occupe des hacks sys.path Pour vous

Donc, cela dépend vraiment de ce que vous voulez faire. Dans votre cas, cependant, puisqu'il semble que votre objectif soit de créer un package correct à un moment donné, l'installation via pip -e Est probablement votre meilleur pari, même s'il n'est pas encore parfait.

Ancienne réponse

Comme déjà indiqué ailleurs, la terrible vérité est que vous devez faire de vilains stratagèmes pour autoriser les importations de modules de frères et sœurs ou de paquetages parents à partir d'un module __main__. La question est détaillée dans PEP 366 . PEP 3122 a tenté de traiter les importations de manière plus rationnelle, mais Guido l’a rejeté

Le seul cas d'utilisation semble être l'exécution de scripts vivant dans le répertoire d'un module, ce que j'ai toujours considéré comme un anti-modèle.

( ici )

Bien que j’utilise régulièrement ce motif avec

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __== "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

Ici path[0] Est le dossier parent de votre script en cours d'exécution et dir(path[0]) votre dossier de niveau supérieur.

Je n'ai toujours pas pu utiliser les importations relatives avec cela, cependant, mais cela autorise les importations absolues à partir du niveau supérieur (dans votre exemple, le dossier parent de api).

53
Evpok

Fatigué des hacks sys.path?

Il y a beaucoup de sys.path.append _ -hacks disponible, mais j’ai trouvé un autre moyen de résoudre le problème: The setuptools . Je ne suis pas sûr s'il existe des cas Edge qui ne fonctionnent pas bien avec cela. Ce qui suit est testé avec Python 3.6.5, (Anaconda, version 4.5.1), ordinateur Windows 10.


Installer

Le point de départ est la structure de fichier que vous avez fournie, encapsulée dans un dossier appelé myproject.

.
└── myproject
    ├── api
    │   ├── api_key.py
    │   ├── api.py
    │   └── __init__.py
    ├── examples
    │   ├── example_one.py
    │   ├── example_two.py
    │   └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

Je vais appeler le . le dossier racine, et dans mon exemple, il se trouve à l'adresse C:\tmp\test_imports\.

api.py

En guise de test, utilisons ce qui suit ./api/api.py

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __== '__main__':
    test_function()

Essayez de lancer test_one:

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\myproject\tests\test_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

Essayer également les importations relatives ne fonctionnera pas:

En utilisant from ..api.api import function_from_api aboutirait à

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\tests\test_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

Pas

1) Créez un fichier setup.py dans le répertoire racine

Le contenu de la setup.py serait*

from setuptools import setup, find_packages

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

2) Utiliser un environnement virtuel

Si vous connaissez bien les environnements virtuels, activez-en un et passez à l'étape suivante. L'utilisation des environnements virtuels n'est pas absolument nécessaire, mais ils vont vraiment vous aider à 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 env virtuel
    • python -m venv venv
  • Activer env virtuel
    • source ./venv/bin/activate (Linux, macOS) ou ./venv/Scripts/activate (Win)

Pour en savoir plus à ce sujet, il suffit de sortir Google du "didacticiel python virtual env" 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>

et votre arborescence de dossiers devrait ressembler à ceci **

.
├── myproject
│   ├── api
│   │   ├── api_key.py
│   │   ├── api.py
│   │   └── __init__.py
│   ├── examples
│   │   ├── example_one.py
│   │   ├── example_two.py
│   │   └── __init__.py
│   ├── LICENCE.md
│   ├── README.md
│   └── tests
│       ├── __init__.py
│       └── test_one.py
├── setup.py
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]

3) pip installer votre projet en état éditable

Installez votre paquet de niveau supérieur myproject en utilisant pip. L'astuce consiste à utiliser le -e drapeau lors de l’installation. De cette façon, il est installé dans un état éditable 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 actuel")

Vous pouvez également voir qu'il est installé en utilisant 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) Ajouter myproject. dans vos importations

Notez que vous devrez ajouter myproject. uniquement dans les importations qui ne fonctionneraient pas autrement. Les importations qui ont fonctionné sans le setup.py & pip install fonctionnera toujours bien. Voir un exemple ci-dessous.


Testez la solution

Maintenant, testons la solution en utilisant api.py défini ci-dessus, et test_one.py défini ci-dessous.

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __== '__main__':
    test_function()

faire le test

(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!

* Voir le setuptools docs pour plus d'exemples détaillés de setup.py.

** En réalité, vous pouvez placer votre environnement virtuel n'importe où sur votre disque dur.

82
np8

Voici une autre alternative que j'insère en haut du dossier Python du dossier tests:

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
39
Cenk Alti

Vous n'avez pas besoin et ne devriez pas pirater sys.path sauf si c'est nécessaire et dans ce cas, ce n'est pas le cas. Utilisation:

import api.api_key # in tests, examples

Exécuter à partir du répertoire du projet: python -m tests.test_one.

Vous devriez probablement déplacer tests (s’ils sont unittests d’api) à l’intérieur de api et exécuter python -m api.test pour exécuter tous les tests (en supposant qu'il y ait __main__.py) ou python -m api.test.test_one courir test_one au lieu.

Vous pouvez également supprimer __init__.py de examples (ce n'est pas un package Python)) et exécutez les exemples dans un virtualenv où api est installé, par exemple, pip install -e . dans un virtualenv installerait le paquet inplace api si vous avez le bon setup.py.

27
jfs

Je ne dispose pas encore de la compréhension de la pythonologie nécessaire pour comprendre le moyen envisagé de partager le code entre des projets indépendants, sans bidouille d'importation entre frères et soeurs. Jusqu'à ce jour, c'est ma solution. Pour examples ou tests importer des éléments de ..\api, cela ressemblerait à:

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key
9
user1330131

Pour les importations de paquets de frères et soeurs, vous pouvez utiliser soit le insert soit le append méthode du module [sys.path] [2] :

if __== '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

Cela fonctionnera si vous lancez vos scripts comme suit:

python examples/example_one.py
python tests/test_one.py

D'autre part, vous pouvez également utiliser l'importation relative:

if __== '__main__' and if __package__ is not None:
    import ..api.api

Dans ce cas, vous devrez lancer votre script avec le '- m' argument (notez que, dans ce cas, vous ne devez pas donner le '. Py' extension):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

Bien sûr, vous pouvez mélanger les deux approches pour que votre script fonctionne, peu importe comment il s'appelle:

if __== '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api
3
Paolo Rovelli

TLDR

Cette méthode ne nécessite ni setuptools, ni hacks de chemins, ni arguments de ligne de commande supplémentaires, ni spécification du niveau supérieur du package dans chaque fichier de votre projet.

Il suffit de faire un script dans le répertoire parent de ce que vous appelez pour être votre __main__ et tout courir à partir de là. Pour plus d'explications, continuez à lire.

Explication

Cela peut être accompli sans pirater un nouveau chemin ensemble, sans argument supplémentaire en ligne de commande ou sans ajouter de code à chacun de vos programmes pour reconnaître ses frères et sœurs.

La raison pour laquelle cela échoue, comme je crois mentionné précédemment, est que les programmes appelés ont leur __name__ défini comme __main__. Lorsque cela se produit, le script appelé accepte de se placer au niveau supérieur du package et refuse de reconnaître les scripts des répertoires frères.

Cependant, tout ce qui se trouve sous le niveau supérieur du répertoire reconnaîtra toujours TOUT AUTRE sous le niveau supérieur. Cela signifie que SEULEMENT == ===================================================== Pour obtenir des fichiers dans les répertoires frères pour se reconnaître/utiliser est de les appeler à partir d'un script dans leur répertoire parent.

Proof of Concept Dans un répertoire avec la structure suivante:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py contient le code suivant:

import sib1.call as call


def main():
    call.Call()


if __== '__main__':
    main()

sib1/call.py contient:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __== '__main__':
    Call()

et sib2/callsib.py contient:

def CallSib():
    print("Got Called")

if __== '__main__':
    CallSib()

Si vous reproduisez cet exemple, vous remarquerez que l'appel de Main.py entraînera l’impression "Got Called" comme défini dans sib2/callsib.py même si sib2/callsib.py a été appelé par sib1/call.py. Cependant, si l'on appelait directement sib1/call.py (après avoir apporté les modifications appropriées aux importations), une exception est levée. Même si cela fonctionnait lorsqu'il était appelé par le script dans son répertoire parent, cela ne fonctionnerait pas s'il se croyait au premier niveau du paquet.

2
Thunderwood

Juste au cas où quelqu'un utilisant Pydev sur Eclipse se retrouverait ici: vous pouvez ajouter le chemin parent du frère (et donc le parent du module appelant) en tant que dossier de bibliothèque externe en utilisant Projet-> Propriétés et en mettant Bibliothèques externes dans le menu de gauche Pydev-PYTHONPATH. Ensuite, vous pouvez importer de votre frère, e. g. from sibling import some_class.

1

Vous devez vérifier comment les instructions d'importation sont écrites dans le code associé. Si examples/example_one.py utilise la déclaration d'importation suivante:

import api.api

... alors il s'attend à ce que le répertoire racine du projet se trouve dans le chemin système.

Le moyen le plus simple de prendre en charge ceci sans aucun piratage (comme vous le dites) serait d’exécuter les exemples à partir du répertoire de niveau supérieur, comme ceci:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 
1
AJ.

J'ai créé un exemple de projet pour montrer comment j'ai géré cela, ce qui est en fait un autre hack de sys.path, comme indiqué ci-dessus. Exemple d'importation de frères et soeurs Python , qui repose sur:

if __== '__main__': import os import sys sys.path.append(os.getcwd())

Cela semble assez efficace tant que votre répertoire de travail reste à la racine du projet Python. Si quelqu'un le déploie dans un environnement de production réel, il serait bon de savoir s'il fonctionne là-bas. ainsi que.

0
ADataGMan