2010-04-23 24 views
42

Nunca había notado el atributo __path__ que se define en algunos de mis paquetes antes de hoy. De acuerdo con la documentación:¿Para qué sirve __path__?

Los paquetes admiten un atributo más especial, __path__. Esto es inicializado para ser una lista que contiene el nombre del directorio que contiene el del paquete __init__.py antes de que se ejecute el código en ese archivo. Esta variable se puede modificar; al hacerlo, afecta las búsquedas futuras de los módulos y los subpaquetes contenidos en el paquete .

Aunque esta característica no suele ser necesaria, se puede utilizar para ampliar el conjunto de módulos que se encuentran en un paquete.

¿Alguien podría explicarme qué significa exactamente esto y por qué alguna vez querría usarlo?

Respuesta

22

Esto generalmente se usa con pkgutil para dejar que un paquete se distribuya en el disco. Por ejemplo, zope.interface y zope.schema son distribuciones separadas (zope es un "paquete de espacio de nombres"). Puede tener interfaz zope.instalada en /usr/lib/python2.6/site-packages/zope/interface/, mientras está usando zope.schema más localmente en /home/me/src/myproject/lib/python2.6/site-packages/zope/schema.

Si pones pkgutil.extend_path(__path__, __name__) en /usr/lib/python2.6/site-packages/zope/__init__.py entonces ambos zope.interface y zope.schema habrá importables porque pkgutil tendrá el cambio __path__ a ['/usr/lib/python2.6/site-packages/zope', '/home/me/src/myproject/lib/python2.6/site-packages/zope'].

pkg_resources.declare_namespace (parte de Setuptools) es como pkgutil.extend_path pero es más consciente de las cremalleras en la ruta.

El cambio manual __path__ es poco común y probablemente no sea necesario, aunque es útil observar la variable al depurar problemas de importación con paquetes de espacio de nombres.

También se puede utilizar para __path__ monkeypatching, por ejemplo, he monkeypatched distutils a veces mediante la creación de un archivo distutils/__init__.py que es desde el principio 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... 
+1

Tenía la sensación de que tenía algo que ver con los paquetes de espacio de nombres, pero tuve problemas para reconstruir cómo funcionaba. ¡Gracias! –

28

Si cambia __path__, puede forzar al intérprete a mirar en un directorio diferente para los módulos que pertenecen a ese paquete.

Esto le permitiría, por ejemplo, cargar diferentes versiones del mismo módulo en función de las condiciones de tiempo de ejecución. Puede hacer esto si quiere usar diferentes implementaciones de la misma funcionalidad en diferentes plataformas.

+0

Django usa esto para cargar dinámicamente los comandos django-admin.py en el arranque!. 'commands = {name: 'django.core' for name in find_commands (__path __ [0])}' –

7

Además de seleccionar diferentes versiones de un módulo en función de las condiciones de tiempo de ejecución como indica Syntactic, esta funcionalidad también le permitirá dividir su paquete en varias piezas/descargas/instalaciones mientras mantiene la apariencia de un único paquete lógico.

Considere lo siguiente.

  • Tengo dos paquetes, mypkg y _mypkg_foo.
  • _mypkg_foo contiene el módulo opcional en mypkg, foo.py.
  • como descargado e instalado, mypkg no contiene un foo.py.

mypkg 's __init__.py puede hacer algo así:

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

Si alguien ha instalado el paquete _mypkg_foo, entonces mypkg.foo está disponible para ellos. Si no lo han hecho, no existe.

0

Una situación particular que he encontrado es cuando un paquete se vuelve lo suficientemente grande como para dividir partes de él en subdirectorios sin tener que cambiar ningún código que lo haga referencia.

Por ejemplo, tengo un paquete llamado views que estaba recopilando una serie de funciones de utilidad de soporte que estaban siendo confusas con el objetivo principal de nivel superior del paquete. Yo era capaz de mover estas funciones de apoyo en un subdirectorio utils y añadir la siguiente línea a la __init__.py para la views paquete:

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

Con este cambio también views/__init_.py, podría correr el resto del software con el nuevo archivo estructura sin ningún otro cambio en los archivos.

(I trató de hacer algo similar con import declaraciones en el archivo views/__init__.py, pero los módulos de sub-paquete todavía no eran visibles a través de una importación del paquete view - No estoy del todo seguro de si me falta algo allí ; comentarios sobre esa bienvenida)

(esta respuesta basada en Python 2.7 instalación)