web-dev-qa-db-fra.com

Importations relatives pour la milliardième fois

J'ai été ici:

et beaucoup d'URL que je n'ai pas copiées, certaines sur SO, d'autres sur d'autres sites, lorsque je pensais avoir rapidement la solution.

La question qui revient toujours est la suivante: avec Windows 7, Python 2.7.3 32 bits, comment puis-je résoudre ce message "Tentative d’importation relative dans un non-package"? J'ai construit une réplique exacte du paquet sur pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

J'ai créé des fonctions nommées spam et oeufs dans les modules appropriés. Naturellement, ça n'a pas marché. La réponse est apparemment dans la 4ème URL que j'ai énumérée, mais ce sont tous des anciens pour moi. Il y avait cette réponse sur l'une des URL que j'ai visitées:

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 "principal"), 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.

La réponse ci-dessus semble prometteuse, mais ce sont tous des hiéroglyphes pour moi. Alors ma question, comment faire en sorte que Python ne me renvoie pas "Tentative d’importation relative dans un non-package"? a une réponse qui implique -m, soi-disant.

Quelqu'un peut-il me dire, s'il vous plaît, pourquoi Python envoie ce message d'erreur, qu'est-ce que cela signifie-t-il par non-package!, Pourquoi et comment définissez-vous un 'package', et la réponse précise en termes assez simples un jardin d'enfants à comprendre

Edit: Les importations ont été effectuées à partir de la console.

367
user1881400

Script vs. Module

Voici une explication. La version courte indique qu’il existe une grande différence entre l’exécution directe d’un fichier Python et son importation ailleurs. Le simple fait de savoir dans quel répertoire se trouve un fichier ne détermine pas le paquet Python dans lequel il se trouve. Cela dépend également de la manière dont vous chargez le fichier dans Python. _ (en exécutant ou en important).

Il existe deux manières de charger un fichier Python: en tant que script de niveau supérieur ou en tant que module. Un fichier est chargé en tant que script de niveau supérieur si vous l'exécutez directement, par exemple en tapant python myfile.py sur la ligne de commande. Il est chargé en tant que module si vous faites python -m myfile ou s'il est chargé lorsqu'une instruction import est rencontrée dans un autre fichier. Il ne peut y avoir qu'un seul script de niveau supérieur à la fois; le script de niveau supérieur est le fichier Python que vous avez exécuté pour démarrer.

Nom

Lorsqu'un fichier est chargé, un nom lui est attribué (qui est stocké dans son attribut __name__.). S'il a été chargé en tant que script de niveau supérieur, son nom est __main__. S'il a été chargé en tant que module, son nom est le nom du fichier, précédé des noms des packages/sous-packages dont il fait partie, séparés par des points.

Ainsi par exemple dans votre exemple:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

si vous importiez moduleX (remarque: importé , non exécuté directement), son nom serait package.subpackage1.moduleX. Si vous importiez moduleA, son nom serait package.moduleA. Cependant, si vous exécutez directement moduleX à partir de la ligne de commande, son nom sera plutôt __main__, et si vous exécutez directement moduleA à partir de la ligne de commande, son nom sera __main__. Lorsqu'un module est exécuté en tant que script de niveau supérieur, il perd son nom normal et s'appelle plutôt __main__.

Accéder à un module PAS via son paquet contenant

Il existe un problème supplémentaire: le nom du module dépend du fait qu'il ait été importé "directement" du répertoire dans lequel il se trouve ou importé via un package. Cela ne fait une différence que si vous exécutez Python dans un répertoire et essayez d'importer un fichier dans ce même répertoire (ou dans un sous-répertoire de celui-ci). Par exemple, si vous démarrez l’interprète Python dans le répertoire package/subpackage1, puis faites import moduleX, le nom de moduleX sera simplement moduleX, et pas package.subpackage1.moduleX. En effet, Python ajoute le répertoire actuel à son chemin de recherche au démarrage. s'il trouve le module à importer dans le répertoire en cours, il ne saura pas que ce répertoire fait partie d'un paquet et les informations sur le paquet ne feront pas partie du nom du module.

Un cas particulier est si vous exécutez l'interpréteur de manière interactive (par exemple, tapez simplement python et commencez à entrer Python code à la volée). Dans ce cas, le nom de cette session interactive est __main__.

Maintenant, voici l’essentiel pour votre message d’erreur: si le nom d’un module n’a pas de points, il n’est pas considéré comme faisant partie d’un paquet. Peu importe où le fichier est réellement sur le disque. Tout ce qui compte est son nom, et son nom dépend de la façon dont vous l'avez chargé.

Maintenant, regardez la citation que vous avez incluse dans votre question:

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 "principal"), les importations relatives sont résolues comme si le module était un module de niveau supérieur, quel que soit l'emplacement du module dans le système de fichiers.

Importations relatives ...

Les importations relatives utilisent le nom du module pour déterminer où il se trouve dans un package. Lorsque vous utilisez une importation relative telle que from .. import foo, les points indiquent qu'il faut augmenter un certain nombre de niveaux dans la hiérarchie des packages. Par exemple, si le nom de votre module actuel est package.subpackage1.moduleX, alors ..moduleA signifierait package.moduleA. Pour qu'un from .. import fonctionne, le nom du module doit comporter au moins autant de points qu'il y en a dans l'instruction import.

... ne sont relatifs que dans un package

Toutefois, si le nom de votre module est __main__, il n'est pas considéré comme faisant partie d'un package. Son nom n'a pas de point et vous ne pouvez donc pas utiliser d'instructions from .. import à l'intérieur. Si vous essayez de le faire, vous obtiendrez l'erreur "relative-import dans non-package".

Les scripts ne peuvent pas importer les fichiers relatifs

Ce que vous avez probablement fait, c'est que vous avez essayé d'exécuter moduleX ou similaire à partir de la ligne de commande. Lorsque vous avez fait cela, son nom a été défini sur __main__, ce qui signifie que les importations relatives qu'il contient vont échouer, car son nom ne révèle pas qu'il se trouve dans un package. Notez que cela se produira également si vous exécutez Python à partir du même répertoire que celui où se trouve un module, puis essayez d'importer ce module car, comme décrit ci-dessus, Python trouvera le module. dans le répertoire en cours "trop ​​tôt" sans se rendre compte qu'il fait partie d'un paquet.

Rappelez-vous également que lorsque vous exécutez l'interpréteur interactif, le "nom" de cette session interactive est toujours __main__. Donc vous ne pouvez pas importer directement depuis une session interactive. Les importations relatives ne doivent être utilisées que dans les fichiers de module.

Deux solutions:

  1. Si vous voulez vraiment exécuter moduleX directement, mais que vous souhaitiez tout de même qu'il soit considéré comme faisant partie d'un paquet, vous pouvez utiliser python -m package.subpackage1.moduleX. Le -m indique à Python de le charger en tant que module et non en tant que script de niveau supérieur.

  2. Ou peut-être que vous ne voulez pas réellement exécuter moduleX, vous voulez simplement exécuter un autre script, par exemple myfile.py, that utilise des fonctions dans moduleX. Si tel est le cas, mettez myfile.py ailleurs - not dans le répertoire package - et exécutez-le. Si à l'intérieur de myfile.py vous faites des choses comme from package.moduleA import spam, tout se passera bien.

Notes

  • Pour l'une de ces solutions, le répertoire du package (package dans votre exemple) doit être accessible à partir du chemin de recherche du module Python (sys.path). Si ce n'est pas le cas, vous ne pourrez plus utiliser quoi que ce soit dans le paquet.

  • Depuis Python 2.6, le "nom" du module à des fins de résolution de paquet est déterminé non seulement par ses attributs __name__, mais également par l'attribut __package__. C'est pourquoi j'évite d'utiliser le symbole explicite __name__ pour faire référence au "nom" du module. Depuis Python 2.6, le "nom" d'un module est effectivement __package__ + '.' + __name__ ou simplement __name__ si __package__ est None.)

766
BrenBarn

C'est vraiment un problème au sein de python. L'origine de la confusion est que les gens prennent à tort l'importation relative comme chemin relatif qui ne l'est pas.  

Par exemple, lorsque vous écrivez dans faa.py:

from .. import foo

Cela n'a de sens que si faa.py était identifié et chargé par python, pendant l'exécution, dans le cadre d'un paquet. Dans ce cas, le nom du module pour faa.py, par exemple, nom_du_package.faa. Si le fichier a été chargé simplement parce qu'il se trouve dans le répertoire en cours, lors de l'exécution de python, son nom ne fera référence à aucun package et, par la suite, l'importation relative échouera. 

Une solution simple pour référencer les modules dans le répertoire courant, est d'utiliser ceci:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo
17
Rami Ka.

Voici une recette générale, modifiée pour correspondre à un exemple, que j'utilise actuellement pour traiter les bibliothèques Python écrites sous la forme de packages, contenant des fichiers interdépendants, dans lesquels je veux pouvoir tester certaines parties de celles-ci. Appelons cette lib.foo et disons qu'elle a besoin d'accéder à lib.fileA pour les fonctions f1 et f2 et à lib.fileB pour la classe Class3.

J'ai inclus quelques appels print pour illustrer son fonctionnement. En pratique, vous voudriez les supprimer (et peut-être aussi la ligne from __future__ import print_function).

Cet exemple particulier est trop simple à montrer lorsque nous devons vraiment insérer une entrée dans sys.path. (Voir La réponse de Lars pour un cas où nous avons besoin de cela, lorsque nous avons deux ou plusieurs niveaux de répertoires de paquets, et que nous utilisons ensuite os.path.dirname(os.path.dirname(__file__))—, mais cela n'a pas vraiment blessé ici non plus.) C’est également assez sûr de le faire sans le test if _i in sys.path. Cependant, si chaque fichier importé insère le même chemin (par exemple, si fileA et fileB veulent importer des utilitaires depuis le package), cela encombrera sys.path avec le même chemin plusieurs fois. Il est donc agréable d'avoir le if _i not in sys.path dans le passe-partout.

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __== '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

L'idée ici est la suivante (et notez que tous fonctionnent de la même manière sur python2.7 et python 3.x):

  1. S'il est exécuté en tant que import lib ou from lib import foo comme une importation de package standard à partir de code ordinaire, __package est lib et __name__ est lib.foo. Nous prenons le premier chemin de code, important de .fileA, etc.

  2. Si exécuté en tant que python lib/foo.py, __package__ sera nul et __name__ sera __main__.

    Nous prenons le deuxième chemin de code. Le répertoire lib sera déjà dans sys.path, il n'est donc pas nécessaire de l'ajouter. Nous importons de fileA, etc.

  3. S'il est exécuté dans le répertoire lib en tant que python foo.py, le comportement est identique à celui du cas 2.

  4. S'il est exécuté dans le répertoire lib en tant que python -m foo, le comportement est similaire aux cas 2 et 3. Toutefois, le chemin d'accès au répertoire lib n'est pas dans sys.path. Nous l'ajoutons donc avant l'importation. Il en va de même si nous exécutons Python puis import foo.

    (Puisque .est dans sys.path, nous n'avons pas vraiment besoin d'ajouter la version absolue du chemin ici. C'est là qu'une structure d'imbrication de paquet plus profonde, où nous voulons faire from ..otherlib.fileC import ..., fait la différence. Si vous ' Si vous ne le faites pas, vous pouvez omettre entièrement la manipulation de sys.path.)

Remarques

Il y a encore une bizarrerie. Si vous courez tout ça de l'extérieur:

$ python2 lib.foo

ou:

$ python3 lib.foo

le comportement dépend du contenu de lib/__init__.py. Si cela existe et que est vide, tout va bien:

Package named 'lib'; __is '__main__'

Mais si lib/__init__.pylui-même importe routine pour pouvoir exporter routine.name directement sous la forme lib.name, vous obtenez:

$ python2 lib.foo
Package named 'lib'; __is 'lib.foo'
Package named 'lib'; __is '__main__'

En d'autres termes, le module est importé deux fois, une fois via le package, puis à nouveau sous la forme __main__ pour qu'il exécute votre code main. Python 3.6 et versions ultérieures avertissent à ce sujet:

$ python3 lib.routine
Package named 'lib'; __is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __is '__main__'

Warning est nouveau, mais le comportement averti ne l’est pas. Cela fait partie de ce que certains appellent le double piège d'importation . (Pour plus de détails, voir numéro 27487 .) Nick Coghlan dit:

Cette prochaine interruption existe dans toutes les versions actuelles de Python, y compris la version 3.3, et peut être résumée dans les instructions générales suivantes: "N'ajoutez jamais un répertoire de package, ni aucun répertoire à l'intérieur d'un package, directement dans le chemin Python".

Notez que bien que nous enfreignions cette règle ici, nous le faisons uniquement lorsque le fichier en cours de chargement est chargé non dans le cadre d'un package, et notre modification est spécifiquement conçue pour nous permettre d'accéder à d'autres fichiers dans ce paquet. (Et, comme je l’ai noté, nous ne devrions probablement pas faire cela du tout pour les paquets à niveau unique.) Si nous voulions être très propres, nous pourrions réécrire ceci comme, par exemple:

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

C’est-à-dire que nous modifions sys.path suffisamment longtemps pour que nous puissions importer nos données, puis le remettons tel quel (en supprimant un exemplaire de _i si et seulement si nous ajoutions un exemplaire de _i).

3
torek

Donc, après en avoir parlé avec beaucoup d'autres, je suis tombé sur une note postée par Dorian B dans cet article / qui résolvait le problème spécifique que je rencontrais: je mettrais au point des modules et des classes pouvant être utilisés avec un site Web. service, mais je veux aussi pouvoir les tester pendant le codage, en utilisant les fonctions de débogage de PyCharm. Pour exécuter des tests dans une classe autonome, j'ajouterais les éléments suivants à la fin de mon fichier de classe:

if __== '__main__':
   # run test code here...

mais si je voulais importer d'autres classes ou modules dans le même dossier, je devrais alors changer toutes mes instructions d'importation de la notation relative aux références locales (c'est-à-dire supprimer le point (.)). Mais après avoir lu la suggestion de Dorian, j'ai essayé sa ' one-liner 'et ça a fonctionné! Je peux maintenant tester dans PyCharm et laisser mon code de test en place lorsque j'utilise la classe dans une autre classe en cours de test ou lorsque je l'utilise dans mon service Web!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __== '__main__' or parent_module.__== '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

L'instruction if vérifie si nous utilisons ce module en tant que main ou s'il est utilisé dans un autre module testé en tant que main. C’est peut-être une évidence, mais j’offre cette note au cas où toute autre personne frustrée par les problèmes d’importation relative ci-dessus pourrait s’en servir.

3
Steve L

__name__ change selon que le code en question est exécuté dans l'espace de noms global ou dans le cadre d'un module importé.

Si le code ne fonctionne pas dans l'espace global, __name__ sera le nom du module. S'il s'exécute dans un espace de noms global, par exemple si vous le tapez dans une console ou exécutez le module en tant que script à l'aide de python.exe yourscriptnamehere.py, __name__ devient "__main__"

Vous verrez que beaucoup de code python avec if __== '__main__' est utilisé pour tester si le code est exécuté à partir de l'espace de noms global, ce qui vous permet d'avoir un module qui fait également office de script. 

Avez-vous essayé de faire ces importations à partir de la console? 

2
theodox

Voici une solution que je ne recommanderais pas, mais qui pourrait être utile dans certaines situations où les modules n'étaient simplement pas générés:

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
2
Federico

J'ai eu un problème similaire où je ne voulais pas changer le chemin de recherche du module Python Et je devais charger un module relativement à partir d'un script (malgré "les scripts ne peuvent pas importer relatifs "comme BrenBarn a expliqué joliment ci-dessus).

J'ai donc utilisé le hack suivant. Malheureusement, le module imp est devenu obsolète depuis la version 3.4 et remplacé par importlib. (Est-ce possible avec importlib? Je ne sais pas.) à présent.

Exemple d'accès aux membres de moduleX dans subpackage1 à partir d'un script résidant dans le dossier subpackage2:

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

Une approche plus propre semble consister à modifier le chemin sys.path utilisé pour le chargement des modules, comme indiqué par Federico.

#!/usr/bin/env python3

if __== '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
1
Lars

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 "principal"), les importations relatives sont résolues comme si le module était un module de niveau supérieur, quel que soit l'emplacement du module dans le système de fichiers.

A écrit un petit paquet Python dans PyPi qui pourrait aider les téléspectateurs de cette question. Le package agit comme une solution de contournement si vous souhaitez pouvoir exécuter des fichiers python contenant des importations contenant des packages de niveau supérieur à partir d'un package/projet sans être directement dans le répertoire du fichier d'importation. https://pypi.org/project/import-anywhere/

1
ec2604

Pour que Python ne me renvoie pas "Tentative d'importation relative dans un non-package" . Package /

init . py sous-paquetage1 / init . py moduleX.py moduleY.py sous-paquet2 / init . py moduleZ.py moduleA.py

Cette erreur se produit uniquement si vous appliquez une importation relative au fichier parent. Par exemple, le fichier parent retourne déjà main après le code "print ( nom )" dans moduleA.py .so CE fichier est déjà main il ne peut pas renvoyer de paquet parent plus avant ..__ Les importations .relatives sont requises dans les fichiers des packages subpackage1 et subpackage2vous pouvez utiliser ".." pour faire référence au répertoire ou au module parent. Mais si parent est déjà le package de niveau supérieur, il ne peut pas aller au-delà de ce répertoire parent (package) . Les fichiers dans lesquels vous appliquez une importation relative aux parents ne peuvent fonctionner qu'avec une application d'importation absolue . Si vous comptez utiliser ABSOLUTE IMPORT DANS UN PAQUET PARENT, AUCUNE ERREUR n'arrivera car python sait qui est au niveau supérieur du paquet, même si votre le fichier est dans des sous-paquets en raison du concept de PYTHON PATH qui définit le niveau supérieur du projet 

0
Sakshi Jain

La réponse de @BrenBarn dit tout, mais si vous êtes comme moi, il faudra peut-être un peu de temps pour comprendre. Voici mon cas et comment la réponse de @ BrenBarn s’applique, cela vous aidera peut-être.

Le cas

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

En utilisant notre exemple familier, et ajoutez à cela que moduleX.py a une importation relative vers ..moduleA. Étant donné que j’ai essayé d’écrire un script de test dans le répertoire subpackage1 qui importait moduleX, mais que j’ai eu l’erreur redoutée décrite par l’opérateur.

Solution

Déplacez le script de test au même niveau que package et importez package.subpackage1.moduleX.

Explication

Comme expliqué, les importations relatives sont effectuées par rapport au nom actuel. Lorsque mon script de test importe moduleX à partir du même répertoire, le nom du module à l'intérieur de moduleX est moduleX. Lorsqu'il rencontre une importation relative, l'interpréteur ne peut pas sauvegarder la hiérarchie des paquets car il est déjà au sommet.

Lorsque j'importe moduleX d'en haut, le nom à l'intérieur de moduleX est package.subpackage1.moduleX et l'importation relative peut être trouvée.

0
Brad Dre