Comment importer une classe Python qui se trouve dans un répertoire ci-dessus?

213

Je veux hériter d'une classe dans un fichier qui se trouve dans un répertoire au-dessus du répertoire actuel.

Est-il possible d'importer relativement ce fichier?

user129975
la source

Réponses:

177

from ..subpkg2 import mod

Selon les documents Python: lorsque vous êtes dans une hiérarchie de packages, utilisez deux points, comme le dit la déclaration d'importation doc:

Lorsque vous spécifiez le module à importer, vous n'avez pas besoin de spécifier le nom absolu du module. Lorsqu'un module ou un package est contenu dans un autre package, il est possible d'effectuer une importation relative dans le même package supérieur sans avoir à mentionner le nom du package. En utilisant des points de début dans le module ou le package spécifié après, fromvous pouvez spécifier la hauteur à parcourir dans la hiérarchie de packages actuelle sans spécifier de noms exacts. Un point en tête signifie le package actuel où le module effectuant l'importation existe. Deux points signifient un niveau de package . Trois points représentent deux niveaux, etc. Donc, si vous exécutez à from . import modpartir d'un module du pkgpackage, vous finirez par importer pkg.mod. Si vous exécutez from ..subpkg2 import modde l'intérieur, pkg.subpkg1vous importerezpkg.subpkg2.mod. La spécification des importations relatives est contenue dans le PEP 328 .

Le PEP 328 traite des importations absolues / relatives.

gimel
la source
4
up1 = os.path.abspath ('..') sys.path.insert (0, up1)
rp.
Pep 328 affiche uniquement la version Python: 2.4, 2,5, 2.6. La version 3 est laissée à des âmes plus compétentes.
gimel
22
Cela déclenche l'erreur ValueError: tentative d'importation relative au-delà du package de niveau supérieur
Carlo
4
Résultats dans ImportError: tentative d'importation relative sans package parent connu
Rotkiv
116
import sys
sys.path.append("..") # Adds higher directory to python modules path.
Sepero
la source
1
cela fonctionne pour moi. Après avoir ajouté cela, je peux importer directement les modules parents, pas besoin d'utiliser "..".
Evan Hu
8
cela ne fonctionne, en gros, que si la valeur PWD de l'application - son directeroy actuel, est un enfant du parent. Ainsi, même si cela fonctionne, de nombreux types de changements systémiques peuvent le déloger.
Phlip
Cela a également fonctionné pour moi pour l'importation de modules de niveau supérieur. Je l'utilise avec os.chdir ("..") pour charger d'autres fichiers de niveau supérieur.
Surendra Shrestha
Cela semble déclencher la même erreur que la réponse ci-dessus de gimel.
Carlo
81

La réponse de @ gimel est correcte si vous pouvez garantir la hiérarchie des packages qu'il mentionne. Si vous ne pouvez pas - si votre besoin réel est tel que vous l'avez exprimé, exclusivement lié aux répertoires et sans aucune relation nécessaire avec l'empaquetage - alors vous devez travailler __file__pour trouver le répertoire parent (quelques os.path.dirnameappels suffiront; -), puis (si ce répertoire n'est pas déjà sys.pathactivé) ajoutez provisoirement ledit répertoire au tout début de sys.path, __import__supprimez à nouveau ledit répertoire - un travail compliqué en effet, mais, "quand vous le devez, vous devez" (et Pyhon s'efforce de n'arrêtez jamais le programmeur de faire ce qui doit être fait - comme le dit la norme ISO C dans la section "Spirit of C" dans sa préface! -).

Voici un exemple qui peut vous convenir:

import sys
import os.path
sys.path.append(
    os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))

import module_in_parent_dir
Alex Martelli
la source
2
cela pourrait ajouter un répertoire qui est à l' intérieur d' un paquet Python pour sys.pathrendre ainsi le même module disponible sous des noms différents et tous les bogues correspondants. autopath.py dans pypy ou _preamble.py dans twisted le résoudre en utilisant un critère de recherche qui identifie le package de niveau supérieur tout en parcourant les répertoires vers le haut.
jfs
4
Vous voudrez peut-être faire quelque chose comme sys.path.remove(pathYouJustAdded)après l'importation dont vous aviez besoin afin de ne pas conserver ce nouveau chemin.
Matthias
35

Importez le module à partir d'un répertoire qui se trouve exactement un niveau au-dessus du répertoire actuel:

from .. import module
Sepero
la source
38
J'ai obtenu: tentative d'importation relative au-delà du package de niveau supérieur :(
RicardoE
25
en utilisant from .. import module j'ai obtenu l'erreur ValueError: tentative d'importation relative dans un non-package en suivant la recommandation
user3428154
4

Comment charger un module qui est un répertoire

préface: J'ai fait une réécriture substantielle d'une réponse précédente dans l'espoir d'aider les gens à pénétrer l'écosystème de python et, espérons-le, de donner à chacun le meilleur changement de succès avec le système d'importation de python.

Cela couvrira les importations relatives au sein d'un package , ce qui, je pense, est le cas le plus probable de la question d'OP.

Python est un système modulaire

C'est pourquoi nous écrivons import foopour charger un module "foo" depuis l'espace de noms racine, au lieu d'écrire:

foo = dict();  # please avoid doing this
with open(os.path.join(os.path.dirname(__file__), '../foo.py') as foo_fh:  # please avoid doing this
    exec(compile(foo_fh.read(), 'foo.py', 'exec'), foo)  # please avoid doing this

Python n'est pas couplé à un système de fichiers

C'est pourquoi nous pouvons intégrer python dans un environnement où il n'y a pas de système de fichiers de facto sans en fournir un virtuel, tel que Jython.

Le découplage d'un système de fichiers permet aux importations d'être flexibles, cette conception permet des choses comme les importations à partir de fichiers archive / zip, l'importation de singletons, la mise en cache de bytecode, les extensions cffi, même le chargement de définition de code à distance.

Donc, si les importations ne sont pas couplées à un système de fichiers, que signifie "un répertoire vers le haut"? Nous devons sélectionner quelques heuristiques mais nous pouvons le faire, par exemple lorsque vous travaillez dans un package , certaines heuristiques ont déjà été définies qui rendent les importations relatives similaires .fooet..foo fonctionnent dans le même package. Cool!

Si vous souhaitez sincèrement coupler vos modèles de chargement de code source à un système de fichiers, vous pouvez le faire. Vous devrez choisir votre propre heuristique et utiliser une sorte de machinerie d'importation, je recommande importlib

L'exemple d'importlib de Python ressemble à ceci:

import importlib.util
import sys

# For illustrative purposes.
file_path = os.path.join(os.path.dirname(__file__), '../foo.py')
module_name = 'foo'

foo_spec = importlib.util.spec_from_file_location(module_name, file_path)
# foo_spec is a ModuleSpec specifying a SourceFileLoader
foo_module = importlib.util.module_from_spec(foo_spec)
sys.modules[module_name] = foo_module
foo_spec.loader.exec_module(foo_module)

foo = sys.modules[module_name]
# foo is the sys.modules['foo'] singleton

Emballage

Il y a un grand exemple de projet disponible officiellement ici: https://github.com/pypa/sampleproject

Un package python est une collection d'informations sur votre code source, qui peut indiquer à d'autres outils comment copier votre code source sur d'autres ordinateurs et comment intégrer votre code source dans le chemin d'accès de ce système afin qu'il import foofonctionne pour d'autres ordinateurs (quel que soit l'interpréteur, système d'exploitation hôte, etc.)

Structure du répertoire

Permet d'avoir un nom de package foo, dans un répertoire (de préférence un répertoire vide).

some_directory/
    foo.py  # `if __name__ == "__main__":`  lives here

Ma préférence est de créer en setup.pytant que frère foo.py, car cela rend l'écriture du fichier setup.py plus simple, mais vous pouvez écrire la configuration pour changer / rediriger tout ce que setuptools fait par défaut si vous le souhaitez; par exemple, placer foo.pysous un répertoire "src /" est quelque peu populaire, non couvert ici.

some_directory/
    foo.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    py_modules=['foo'],
)

.

python3 -m pip install --editable ./  # or path/to/some_directory/

"editable" aka -eredirigera à nouveau la machine d'importation pour charger les fichiers source dans ce répertoire, au lieu de copier les fichiers exacts actuels dans la bibliothèque de l'environnement d'installation. Cela peut également provoquer des différences de comportement sur la machine d'un développeur, assurez-vous de tester votre code! Il existe des outils autres que pip, mais je recommanderais pip comme introduction :)

J'aime aussi faire fooun "package" (un répertoire contenant __init__.py) au lieu d'un module (un seul fichier ".py"), les "packages" et les "modules" peuvent être chargés dans l'espace de noms racine, les modules permettent des espaces de noms imbriqués, ce qui est utile si nous voulons avoir une importation "relative d'un répertoire vers le haut".

some_directory/
    foo/
        __init__.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
)

J'aime aussi faire un foo/__main__.py, cela permet à python d'exécuter le package en tant que module, par exemple python3 -m foos'exécutera en foo/__main__.pytant que __main__.

some_directory/
    foo/
        __init__.py
        __main__.py  # `if __name__ == "__main__":`  lives here, `def main():` too!
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
    ...
    entry_points={
        'console_scripts': [
            # "foo" will be added to the installing-environment's text mode shell, eg `bash -c foo`
            'foo=foo.__main__:main',
        ]
    },
)

Permet d'étoffer cela avec quelques modules supplémentaires: Fondamentalement, vous pouvez avoir une structure de répertoire comme ceci:

some_directory/
    bar.py           # `import bar`
    foo/
        __init__.py  # `import foo`
        __main__.py
        baz.py       # `import foo.baz
        spam/           
            __init__.py  # `import foo.spam`
            eggs.py      # `import foo.spam.eggs`
    setup.py

setup.py contient classiquement des informations de métadonnées sur le code source, telles que:

  • quelles dépendances sont nécessaires pour installer nommé "install_requires"
  • quel nom doit être utilisé pour la gestion des packages (installer / désinstaller "nom"), je suggère que cela corresponde à votre nom de package python principal dans notre cas foo, bien que le remplacement des traits de soulignement par des tirets soit populaire
  • informations de licence
  • balises de maturité (alpha / beta / etc),
  • tags d'audience (pour les développeurs, pour l'apprentissage automatique, etc.),
  • contenu de documentation d'une seule page (comme un fichier README),
  • les noms de shell (noms que vous tapez dans le shell utilisateur comme bash, ou noms que vous trouvez dans un shell utilisateur graphique comme un menu de démarrage),
  • une liste des modules python que ce paquet va installer (et désinstaller)
  • un point d'entrée par défaut "exécuter les tests" python ./setup.py test

Son très vaste, il peut même compiler des extensions c à la volée si un module source est installé sur une machine de développement. Pour un exemple quotidien, je recommande setup.py du référentiel d'échantillons PYPA.

Si vous publiez un artefact de génération, par exemple une copie du code destiné à exécuter des ordinateurs presque identiques, un fichier requirements.txt est un moyen populaire de prendre des instantanés des informations de dépendance exactes, où "install_requires" est un bon moyen de capturer le minimum et versions compatibles maximum. Cependant, étant donné que les machines cibles sont de toute façon presque identiques, je recommande fortement de créer un tarball d'un préfixe python entier. Cela peut être délicat, trop détaillé pour entrer ici. Consultez pip installl' --targetoption de, ou virtualenv aka venv pour les pistes.

retour à l'exemple

comment importer un fichier d'un répertoire vers le haut:

De foo / spam / eggs.py, si nous voulions du code de foo / baz, nous pourrions le demander par son espace de noms absolu:

import foo.baz

Si nous voulions réserver la capacité de déplacer eggs.py dans un autre répertoire à l'avenir avec une autre bazimplémentation relative , nous pourrions utiliser une importation relative comme:

import ..baz
ThorSummoner
la source
-5

Python est un système modulaire

Python ne repose pas sur un système de fichiers

Pour charger le code python de manière fiable, ayez ce code dans un module et ce module installé dans la bibliothèque de python.

Les modules installés peuvent toujours être chargés à partir de l'espace de noms de niveau supérieur avec import <name>


Il y a un grand exemple de projet disponible officiellement ici: https://github.com/pypa/sampleproject

Fondamentalement, vous pouvez avoir une structure de répertoires comme celle-ci:

the_foo_project/
    setup.py  

    bar.py           # `import bar`
    foo/
      __init__.py    # `import foo`

      baz.py         # `import foo.baz`

      faz/           # `import foo.faz`
        __init__.py
        daz.py       # `import foo.faz.daz` ... etc.

.

Assurez - vous de déclarer votre setuptools.setup()dans setup.py,

exemple officiel: https://github.com/pypa/sampleproject/blob/master/setup.py

Dans notre cas, nous voulons probablement exporter bar.pyet foo/__init__.py, mon bref exemple:

setup.py

#!/usr/bin/env python3

import setuptools

setuptools.setup(
    ...
    py_modules=['bar'],
    packages=['foo'],
    ...
    entry_points={}, 
        # Note, any changes to your setup.py, like adding to `packages`, or
        # changing `entry_points` will require the module to be reinstalled;
        # `python3 -m pip install --upgrade --editable ./the_foo_project
)

.

Nous pouvons maintenant installer notre module dans la bibliothèque python; avec pip, vous pouvez installer the_foo_projectdans votre bibliothèque python en mode édition, afin que nous puissions y travailler en temps réel

python3 -m pip install --editable=./the_foo_project

# if you get a permission error, you can always use 
# `pip ... --user` to install in your user python library

.

Maintenant, à partir de n'importe quel contexte python, nous pouvons charger nos py_modules et packages partagés

foo_script.py

#!/usr/bin/env python3

import bar
import foo

print(dir(bar))
print(dir(foo))
ThorSummoner
la source
2
dois mentionner que j'installe toujours mon module pendant que je travaille dessus pip install --edit foo, presque toujours à l'intérieur d'un virtualenv. Je n'écris presque jamais un module qui n'est pas destiné à être installé. si je comprends mal quelque chose que j'aimerais savoir.
ThorSummoner
Je dois également mentionner que l'utilisation d'une suite de tests venv-auto-creation, comme tox, est très utile car editableles liens d'oeufs et les modules python installés ne sont pas exactement les mêmes à tous points de vue; par exemple, l'ajout d'un nouvel espace de noms à un module modifiable se trouvera dans votre recherche de chemin, mais s'il n'est pas exporté dans votre fichier setup.py, il ne sera pas empaqueté / installé! Testez votre cas d'utilisation :)
ThorSummoner