2012-01-20 16 views
18

Estoy desarrollando varios proyectos de Python para varios clientes al mismo tiempo. Una versión simplificada de la estructura de mi carpeta de proyecto se ve algo como esto:ruta de importación python: paquetes con el mismo nombre en diferentes carpetas

/path/ 
    to/ 
    projects/ 
     cust1/ 
     proj1/ 
      pack1/ 
      __init__.py 
      mod1.py 
     proj2/ 
      pack2/ 
      __init__.py 
      mod2.py 
     cust2/ 
     proj3/ 
      pack3/ 
      __init__.py 
      mod3.py 

Cuando, por ejemplo, desea utilizar la funcionalidad de proj1, que se extienden sys.path por /path/to/projects/cust1/proj1 (por ejemplo, fijando PYTHONPATH o añadir un archivo .pth a la carpeta site_packages o incluso modificar sys.path directamente) y luego importar el módulo de la siguiente manera:

>>> from pack1.mod1 import something 

como yo trabajo en más proyectos, ocurre que diferentes proyectos tienen nombres de paquetes idénticos:

/path/ 
    to/ 
    projects/ 
     cust3/ 
     proj4/ 
      pack1/ <-- same package name as in cust1/proj1 above 
      __init__.py 
      mod4.py 

Si ahora simplemente extiendo sys.path por /path/to/projects/cust3/proj4, todavía puedo importar de proj1, pero no de proj4:

>>> from pack1.mod1 import something 
>>> from pack1.mod4 import something_else 
ImportError: No module named mod4 

Creo que la razón por la cual la segunda importación falla es que Python sólo se busca en el primera carpeta en sys.path donde encuentra un paquete pack1 y se da por vencido si no encuentra allí el módulo mod4. He preguntado sobre esto en una pregunta anterior, vea import python modules with the same name, pero los detalles internos aún no están claros para mí.

De todos modos, la solución obvia es agregar otra capa de cualificación espacio de nombres girando directorios de proyectos en paquetes súper: Añadir __init__.py archivos en cada carpeta proj* y eliminar estas carpetas desde las líneas por el que se amplía, por ejemplo sys.path

$ export PYTHONPATH=/path/to/projects/cust1:/path/to/projects/cust3 
$ touch /path/to/projects/cust1/proj1/__init__.py 
$ touch /path/to/projects/cust3/proj4/__init__.py 
$ python 
>>> from proj1.pack1.mod1 import something 
>>> from proj4.pack1.mod4 import something_else 

Ahora estoy corriendo en una situación en diferentes proyectos para diferentes clientes tienen el mismo nombre, por ejemplo,

/path/ 
    to/ 
    projects/ 
     cust3/ 
     proj1/ <-- same project name as for cust1 above 
      __init__.py 
      pack4/ 
      __init__.py 
      mod4.py 

Tratando de importar de mod4 ya no funciona por la misma razón que antes:

>>> from proj1.pack4.mod4 import yet_something_else 
ImportError: No module named pack4.mod4 

Siguiendo el mismo enfoque que resuelve este problema antes, me gustaría añadir otra capa de paquete/espacio de nombres y convierta las carpetas de los clientes en súper súper paquetes.

Sin embargo, esto choca con otros requisitos que tengo con la estructura de mi carpeta de proyecto, p.

  • Desarrollo estructura/Liberación de mantener varias líneas de código
  • otros tipos de código fuente, como por ejemplo, JavaScript, SQL, etc.
  • otros archivos que no sean archivos de origen, como p. Ej. documentos o datos

A menos simplificada, más la representación del mundo real de algunas carpetas del proyecto es el siguiente:

/path/ 
    to/ 
    projects/ 
     cust1/ 
     proj1/ 
      Development/ 
      code/ 
       javascript/ 
       ... 
       python/ 
       pack1/ 
        __init__.py 
        mod1.py 
      doc/ 
       ... 
      Release/ 
      ... 
     proj2/ 
      Development/ 
      code/ 
       python/ 
       pack2/ 
        __init__.py 
        mod2.py 

no veo cómo puedo satisfacer las necesidades del intérprete Python tiene una estructura de carpetas y los que tengo al mismo tiempo. Tal vez podría crear una estructura de carpetas adicional con algunos enlaces simbólicos y usar eso en sys.path, pero mirando el esfuerzo que ya estoy haciendo, tengo la sensación de que hay algo fundamentalmente erróneo en todo mi enfoque. En una nota al margen, también me cuesta creer que Python realmente me restringe en mi elección de los nombres de las carpetas del código fuente, como parece hacer en el caso que se muestra.

¿Cómo puedo configurar mis carpetas de proyecto y sys.path para que pueda importar de todos los proyectos de manera consistente si hay proyectos y paquetes con nombres idénticos?

Respuesta

13

This es la solución a mi problema, aunque puede que no sea obvio al principio.

En mis proyectos, he presentado una convención de un espacio de nombres por cliente. En cada carpeta del cliente (cust1, cust2, etc.), hay un archivo __init__.py con este código:

import pkgutil 
__path__ = pkgutil.extend_path(__path__, __name__) 

Todos los demás archivos __init__.py en mis paquetes están vacíos (sobre todo porque no he tenido tiempo todavía para saber qué más hacer con ellos).

Como se explica here, extend_path se asegura de Python es consciente de que hay más de un sub-paquete dentro de un paquete, ubicada físicamente en otro lugar y - por lo que entiendo - el intérprete entonces no se detiene la búsqueda después de que no encuentra un módulo debajo de la ruta del primer paquete que encuentra en sys.path, pero busca todas las rutas en __path__.

Ahora puedo acceder a todos los códigos de forma coherente entre todos los proyectos, p.

from cust1.proj1.pack1.mod1 import something 
from cust3.proj4.pack1.mod4 import something_else 
from cust3.proj1.pack4.mod4 import yet_something_else 

Por un lado negativo, tuve que crear una estructura aún más profundo carpeta del proyecto:

/path/ 
    to/ 
    projects/ 
     cust1/ 
     proj1/ 
      Development/ 
      code/ 
       python/ 
       cust1/ 
        __init__.py <--- contains code as described above 
        proj1/ 
        __init__.py <--- empty 
        pack1/ 
        __init__.py <--- empty 
        mod1.py 

pero que parece muy aceptable para mí, sobre todo teniendo en cuenta el poco esfuerzo que tengo que hacer para mantener esta convención . sys.path se extiende por /path/to/projects/cust1/proj1/Development/code/python para este proyecto.

En una nota al margen, me di cuenta de que de todos los archivos __init__.py para el mismo cliente, se ejecuta uno en la ruta que aparece primero en sys.path, sin importar desde qué proyecto importo algo.

0

Debe utilizar las excelentes herramientas virtualenv y virtualenvwrapper.

+1

Los estoy usando, pero solo proporcionan una solución siempre que importe entre proyectos del mismo cliente. Si necesito importar desde un ámbito más amplio, surge el mismo conflicto en un entorno virtual en el sistema python env. Además, tanto como me gusta virtualenv, desde mi punto de vista, revisa una deficiencia importante del intérprete de Python. ¿Soy realmente el único que se encuentra con este problema? – ssc

+1

En mi humilde opinión, no debería compartir la funcionalidad entre los proyectos de esa manera. Si necesitan compartir alguna implementación, considere extraerla como biblioteca e incluirla en ambos proyectos – zsquare

+0

hmmm, entonces pregunto cómo hacer algo y la respuesta es no hacerlo?!? eso no es lo que esperaba. casualmente, en realidad estoy haciendo lo que sugiere: extraigo la funcionalidad en una biblioteca y la uso en otros proyectos, que es exactamente donde surge este problema de ruta de importación. Me da la impresión de que este es un problema que todos los demás simplemente trabajan de alguna manera y tendré que aceptar esta restricción de importación y, p. introduzca una convención para importar solo entre paquetes del mismo cliente (y también mantener un entorno virtual por cliente). – ssc

0

¿Qué sucede si accidentalmente importa código de un cliente/proyecto en otro y no lo nota? Cuando entregas, seguramente fallará. Adoptaría una convención de tener PYTHONPATH configurado para un proyecto a la vez, y no intentar que todo lo que hayas escrito sea importable a la vez.

Puede usar un script de envoltura por proyecto para establecer PYTHONPATH e iniciar python, o usar scripts para cambiar de entorno cuando cambie de proyecto.

Por supuesto, algunos proyectos tienen dependencias en otros proyectos (las bibliotecas que mencionas), pero si pretendes que el cliente pueda importar varios proyectos a la vez, debes organizar los nombres para que no entren en conflicto. Solo puede tener este problema cuando tiene varios proyectos en PYTHONPATH que no son supuestos para ser utilizados juntos.

+0

¿Cómo se 'importa código accidentalmente'? Las declaraciones de importación son explícitas en Python, por lo que no hay importación implícita y con la convención de instrucciones de importación totalmente calificadas como cust1.proj1.pack1.mod1, no existe realmente el peligro de importar accidentalmente un paquete incorrecto, no calificado, posiblemente sobrescribir uno previamente existente. En una nota, si algo falla _after_ he entregado, mis procedimientos de prueba y despliegue realmente necesita una revisión ... guiones RE/medio ambiente: Eso es exactamente lo virtualenvwrapper es así, consulte otra respuesta. – ssc

+0

Para un solo cliente, los nombres de los proyectos son únicos. Sin embargo, se está produciendo una gran cantidad de reutilización de conceptos y códigos entre proyectos para diferentes clientes; esto incluso ahora ha llevado a nombres de proyectos idénticos (biblioteca) para diferentes clientes, que es donde se originó el problema. La funcionalidad se migra a lo largo de líneas de integración de código horizontal y los proyectos de la biblioteca son similares, pero no idénticos. – ssc

+0

Admito que no siempre hay una necesidad de importar entre proyectos arbitrarios, pero no estoy dispuesto a aceptar una limitación técnica por parte del intérprete de Python que me impide hacerlo, así que estoy contento de haber encontrado esta solución. En la misma línea, no estoy dispuesto a aceptar "no debes hacer eso de todos modos" como una respuesta ya que eso simplemente me parece demasiado dogmático. – ssc

Cuestiones relacionadas