393 votes

Importations de paquets frères

J'ai essayé de lire les questions sur les importations entre frères et soeurs et même la 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 dans les examples y tests importés des répertoires api et être exécuté à partir de la ligne de commande ?

Aussi, j'aimerais éviter l'horrible sys.path.insert hack pour chaque fichier. Sûrement cela peut être fait en Python, non ?

437voto

np8 Points 1538

Vous en avez assez des bidouillages de sys.path ?

Il y a beaucoup de sys.path.append -Mais j'ai trouvé un autre moyen de résoudre le problème en question.

Résumé

  • Enveloppez le code dans un seul dossier (par ex. packaged_stuff )
  • Créer setup.py script où vous utilisez setuptools.setup() . (voir minimal setup.py ci-dessous)
  • Pip installe le paquet dans un état modifiable avec pip install -e <myproject_folder>
  • Importer en utilisant from packaged_stuff.modulename import function_name

Configuration

Le point de départ est la structure de fichier que vous avez fournie, enveloppé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 suivante C:\tmp\test_imports\ .

api.py

Comme cas de test, utilisons le fichier ./api/api.py suivant

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 __name__ == '__main__':
    test_function()

Essayez d'exécuter 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'

Les importations relatives ne fonctionnent pas non plus :

Utilisation de from ..api.api import function_from_api se traduirait par

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

Étapes

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

Le contenu pour le setup.py serait*

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. Utiliser un environnement virtuel

Si vous êtes familier avec les environnements virtuels, activez-en un et passez à l'étape suivante. L'utilisation des environnements virtuels n'est pas absolument nécessaires, mais ils realmente vous aider à long terme (lorsque vous avez plus d'un projet en cours ). Les étapes les plus simples sont les suivantes (à exécuter dans le dossier racine)

  • Créer un environnement virtuel
    • python -m venv venv
  • Activer l'environnement virtuel
    • source ./venv/bin/activate (Linux, macOS) ou ./venv/Scripts/activate (Win)

Pour en savoir plus, il suffit de chercher sur Google "python virtual env tutorial" ou similaire. Vous n'aurez 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 devrait 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]
  1. pip installer votre projet dans un état modifiable

Installez votre paquet de premier niveau myproject en utilisant pip . L'astuce consiste à utiliser le -e 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 paquet 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
  1. Ajouter myproject. dans vos importations

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


Testez la solution

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

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

exécution du test

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

* Voir le Documentation sur les outils d'installation pour des exemples plus verbeux de setup.py.

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

120voto

Evpok Points 1214

Sept ans après

Depuis que j'ai écrit la réponse ci-dessous, modifier sys.path est toujours une astuce rapide qui fonctionne bien pour les scripts privés, mais il y a eu plusieurs améliorations

  • Installation de 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 les métadonnées)
  • Utilisation de la -m drapeau et l'exécution en tant que paquet fonctionne aussi (mais sera un peu maladroit si vous voulez convertir votre répertoire de travail en un paquet installable).
  • Pour les tests, en particulier, pytest est en mesure de trouver le paquet api dans cette situation et s'occupe du paquet sys.path des astuces pour vous

Donc ça dépend vraiment de ce que vous voulez faire. Dans votre cas, cependant, puisqu'il semble que votre objectif soit de créer un paquet correct à un moment donné, l'installation par le biais de pip -e est probablement votre meilleure option, même si elle n'est pas encore parfaite.

Ancienne réponse

Comme cela a déjà été dit ailleurs, la terrible vérité est que vous devez faire de vilains bidouillages pour permettre les importations de modules frères ou de paquets parents à partir d'un module __main__ module. Le problème est détaillé dans PEP 366 . PEP 3122 a tenté de traiter les importations de manière plus rationnelle mais Guido l'a rejeté sur le compte de

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

( aquí )

Cependant, j'utilise régulièrement ce modèle avec

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__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 premier niveau.

Je n'ai toujours pas pu utiliser les importations relatives avec cette méthode, mais elle permet les importations absolues à partir du niveau supérieur (dans votre exemple api ).

51voto

Cenk Alti Points 656

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

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

46voto

J.F. Sebastian Points 102961

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. Utilisez :

import api.api_key # in tests, examples

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

Vous devriez probablement déménager tests (si ce sont des unittests de l'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 à exécuter test_one à la place.

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

14voto

Not Exactly Points 316

Pour les lecteurs de 2021 : Si vous n'êtes pas sûr de vous avec pip install -e :

Considérez cette hiérarchie, telle que recommandée par une réponse de Importations relatives en Python 3 :

MyProject
 src
    bot
       __init__.py
       main.py
       sib1.py
    mod
        __init__.py
        module1.py
 main.py

Le contenu de main.py qui est le point de départ et nous utilisons Importation absolue (sans points de suspension) ici :

from src.bot import main

if __name__ == '__main__':
    main.magic_tricks()

Le contenu de bot/main.py qui tire parti de importations relatives explicites :

from .sib1 import my_drink                # Both are explicit-relative-imports.
from ..mod.module1 import relative_magic

def magic_tricks():
    # Using sub-magic
    relative_magic(in=["newbie", "pain"], advice="cheer_up")

    my_drink()
    # Do your work
    ...

Maintenant, voici le raisonnement :

  • Lors de l'exécution python MyProject/main.py le path/to/MyProject est ajouté dans le sys.path .
  • L'importation absolue import src.bot le lira.
  • El from ..mod signifie qu'il sera monter d'un niveau pour MyProject/src .
    • On peut le voir ? OUI puisque path/to/MyProject est ajouté dans le sys.path .

Donc le point est :

Nous devrions mettre le principal script à côté de MyProject/src puisque lorsqu'on fait de la référence relative, on ne le fera pas sortir de la src et l'importation absolue import src. fournit le just-fit pour nous : le src/ l'étendue.

Voir aussi : ModuleNotFoundError : Aucun module nommé 'sib1'.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X