web-dev-qa-db-fra.com

À quoi sert __path__?

Je n'avais jamais remarqué l'attribut __path__ Qui est défini sur certains de mes packages avant aujourd'hui. Selon la documentation:

Les packages prennent en charge un autre attribut spécial, __path__. Ceci est initialisé pour être une liste contenant le nom du répertoire contenant le __init__.py Du package avant l'exécution du code dans ce fichier. Cette variable peut être modifiée; cela affecte les futures recherches de modules et de sous-packages contenus dans le package.

Bien que cette fonctionnalité ne soit pas souvent nécessaire, elle peut être utilisée pour étendre l'ensemble de modules présents dans un package.

Quelqu'un pourrait-il m'expliquer ce que cela signifie exactement et pourquoi je voudrais un jour l'utiliser?

56
Jason Baker

Ceci est généralement utilisé avec pkgutil pour laisser un paquet être disposé sur le disque. Par exemple, zope.interface et zope.schema sont des distributions distinctes (zope est un "package d'espace de noms"). Vous pouvez avoir zope.interface installé dans /usr/lib/python2.6/site-packages/zope/interface/, Tandis que vous utilisez zope.schema plus localement dans /home/me/src/myproject/lib/python2.6/site-packages/zope/schema.

Si vous mettez pkgutil.extend_path(__path__, __name__) dans /usr/lib/python2.6/site-packages/zope/__init__.py, Alors zope.interface et zope.schema seront importables car pkgutil aura changé __path__ En ['/usr/lib/python2.6/site-packages/zope', '/home/me/src/myproject/lib/python2.6/site-packages/zope'].

pkg_resources.declare_namespace (Partie de Setuptools) est comme pkgutil.extend_path Mais est plus conscient des zips sur le chemin.

Changer manuellement __path__ Est rare et probablement pas nécessaire, bien qu'il soit utile de regarder la variable lors du débogage des problèmes d'importation avec les packages d'espace de noms.

Vous pouvez également utiliser __path__ Pour le monkeypatching, par exemple, j'ai parfois des distutils monkeypatched en créant un fichier distutils/__init__.py Qui est au début sys.path:

import os
stdlib_dir = os.path.dirname(os.__file__)
real_distutils_path = os.path.join(stdlib_dir, 'distutils')
__path__.append(real_distutils_path)
execfile(os.path.join(real_distutils_path, '__init__.py'))
# and then apply some monkeypatching here...
30
Ian Bicking

Si vous changez __path__, vous pouvez forcer l'interpréteur à rechercher dans un répertoire différent les modules appartenant à ce package.

Cela vous permettrait, par exemple, de charger différentes versions du même module en fonction des conditions d'exécution. Vous pouvez le faire si vous souhaitez utiliser différentes implémentations de la même fonctionnalité sur différentes plates-formes.

31
Syntactic

En plus de sélectionner différentes versions d'un module en fonction des conditions d'exécution comme Syntactic dit, cette fonctionnalité vous permettra également de diviser votre package en plusieurs pièces/téléchargements/installations tout en conservant l'apparence d'une seule logique paquet.

Considérer ce qui suit.

  • J'ai deux packages, mypkg et _mypkg_foo.
  • _mypkg_foo contient un module optionnel pour mypkg, foo.py.
  • tel que téléchargé et installé, mypkg ne contient pas de foo.py.

mypkg's __init__.py peut faire quelque chose comme ça:

try:
    import _mypkg_foo
    __path__.append(os.path.abspath(os.path.dirname(_mypkg_foo.__file__)))
    import mypkg.foo
except ImportError:
    pass

Si quelqu'un a installé le package _mypkg_foo, puis mypkg.foo est à leur disposition. S'ils ne l'ont pas fait, cela n'existe pas.

7
Matt Anderson

Une situation particulière que j'ai rencontrée est lorsqu'un package devient suffisamment grand pour que je veuille en diviser des parties en sous-répertoires sans avoir à modifier le code qui le référence.

Par exemple, j'ai un package appelé views qui collectait un certain nombre de fonctions utilitaires de support qui se confondaient avec l'objectif principal de niveau supérieur du package. J'ai pu déplacer ces fonctions de support dans un sous-répertoire utils et ajouter la ligne suivante au __init__.py pour le package views:

__path__.append(os.path.join(os.path.dirname(__file__), "utils"))

Avec ce changement aussi views/__init_.py, J'ai pu exécuter le reste du logiciel avec la nouvelle structure de fichiers sans aucune autre modification des fichiers.

(J'ai essayé de faire quelque chose de similaire avec les instructions import dans les views/__init__.py fichier, mais les modules du sous-package n'étaient toujours pas visibles via une importation du package view - je ne suis pas tout à fait sûr de manquer quelque chose; commentaires sur cet accueil!)

(Cette réponse basée sur Python 2.7)

0
Graham Klyne