¿Cuál es la mejor estructura de proyecto para una aplicación Python? [cerrado]

730

Imagine que desea desarrollar una aplicación de escritorio de usuario final (no web) no trivial en Python. ¿Cuál es la mejor manera de estructurar la jerarquía de carpetas del proyecto?

Las características deseables son la facilidad de mantenimiento, la compatibilidad con IDE, la idoneidad para la ramificación / fusión del control de origen y la fácil generación de paquetes de instalación.

En particular:

  1. ¿Dónde pones la fuente?
  2. ¿Dónde pones los scripts de inicio de la aplicación?
  3. ¿Dónde pones el proyecto IDE cruft?
  4. ¿Dónde pones las pruebas de unidad / aceptación?
  5. ¿Dónde coloca los datos que no son de Python, como los archivos de configuración?
  6. ¿Dónde coloca las fuentes que no son de Python como C ++ para módulos de extensión binaria pyd / so?
kbluck
fuente

Respuestas:

378

No importa demasiado. Lo que sea que te haga feliz funcionará. No hay muchas reglas tontas porque los proyectos de Python pueden ser simples.

  • /scriptso /binpara ese tipo de cosas de interfaz de línea de comandos
  • /tests para tus pruebas
  • /lib para sus bibliotecas de lenguaje C
  • /doc para la mayoría de la documentación
  • /apidoc para los documentos API generados por Epydoc.

Y el directorio de nivel superior puede contener README's, Config's y otras cosas.

La elección difícil es si usar o no un /srcárbol. Python no tiene una distinción entre /src, /liby /bincomo Java o C tiene.

Dado que algunos consideran que un /srcdirectorio de nivel superior no tiene sentido, su directorio de nivel superior puede ser la arquitectura de nivel superior de su aplicación.

  • /foo
  • /bar
  • /baz

Recomiendo poner todo esto en el directorio "nombre-de-mi-producto". Entonces, si está escribiendo una aplicación llamada quux, se nombra el directorio que contiene todo esto /quux.

Otro proyecto PYTHONPATH , entonces, puede incluir la /path/to/quux/fooreutilización del QUUX.foomódulo.

En mi caso, dado que uso Komodo Edit, mi IDE cuft es un solo archivo .KPF. De hecho, lo puse en el /quuxdirectorio de nivel superior y omito agregarlo a SVN.

S.Lott
fuente
23
¿Algún proyecto de código abierto de Python que recomendaría emular su estructura de directorios?
Lance Rushing
44
Mira Django para un buen ejemplo.
S.Lott
33
No tiendo a considerar a Django un buen ejemplo: jugar trucos con sys.path es un DQ instantáneo en mi libro.
Charles Duffy
18
re "trucos": Django agrega el padre de la carpeta del proyecto raíz al sys.path, de modo que los módulos se puedan importar como "desde project.app.module import klass" o "desde app.module import klass".
Jonathan Hartley el
3
Oh, amo este truco y lo estoy usando ahora. Quiero colocar el módulo compartido en otro directorio, y no quiero instalar el módulo en todo el sistema, ni quiero pedirle a la gente que modifique PYTHONPATH manualmente. A menos que las personas propongan algo mejor, creo que esta es realmente la forma más limpia de hacerlo.
Yongwei Wu
242

De acuerdo con la estructura del sistema de archivos de Jean-Paul Calderone de un proyecto de Python :

Project/
|-- bin/
|   |-- project
|
|-- project/
|   |-- test/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |   
|   |-- __init__.py
|   |-- main.py
|
|-- setup.py
|-- README
cmcginty
fuente
23
Project/project/? Ah, el segundo es el nombre del paquete.
Cees Timmerman el
44
¿Cómo hace referencia el archivo ejecutable en la carpeta bin al módulo del proyecto? (No creo que la sintaxis de Python permita ../en una declaración de inclusión)
ThorSummoner
8
@ThorSummoner Simple. ¡Instalas el paquete! ( pip install -e /path/to/Project)
Kroltan
22
Sería increíble si alguien compilara una muestra de este diseño con hello.py y hello-test.py y lo pusiera a disposición de nosotros, los novatos.
jeremyjjbrown
8
@Bloke El núcleo es la -ebandera, que instala el paquete como un paquete editable, es decir, lo instala como enlaces a la carpeta del proyecto real. El ejecutable puede entonces simplemente import projecttener acceso al módulo.
Kroltan
232

Esta publicación de blog de Jean-Paul Calderone se da comúnmente como respuesta en #python en Freenode.

Estructura del sistema de archivos de un proyecto Python

Hacer:

  • nombra el directorio algo relacionado con tu proyecto. Por ejemplo, si su proyecto se llama "Twisted", nombre el directorio de nivel superior para sus archivos de origen Twisted. Cuando realice lanzamientos, debe incluir un sufijo de número de versión:Twisted-2.5 .
  • crea un directorio Twisted/biny coloca tus ejecutables allí, si tienes alguno. No les des.py extensión, incluso si son archivos fuente de Python. No coloque ningún código en ellos, excepto la importación y la llamada a una función principal definida en otro lugar de sus proyectos. (Ligera arruga: dado que en Windows, el intérprete es seleccionado por la extensión del archivo, sus usuarios de Windows realmente quieren la extensión .py. Por lo tanto, cuando empaqueta para Windows, es posible que desee agregarla. Desafortunadamente no hay un truco fácil de distutils que Sé de automatizar este proceso. Teniendo en cuenta que en POSIX la extensión .py es solo una verruga, mientras que en Windows la falta es un error real, si su base de usuarios incluye usuarios de Windows, es posible que desee optar por tener solo el .py extensión a todas partes.)
  • Si su proyecto se puede expresar como un único archivo fuente de Python, póngalo en el directorio y asígnele un nombre relacionado con su proyecto. Por ejemplo, Twisted/twisted.py. Si necesita varios archivos fuente, cree un paquete en su lugar ( Twisted/twisted/con un vacío Twisted/twisted/__init__.py) y coloque sus archivos fuente en él. Por ejemplo,Twisted/twisted/internet.py .
  • coloque sus pruebas unitarias en un subpaquete de su paquete (nota: esto significa que la opción de archivo fuente Python anterior era un truco, siempre necesita al menos otro archivo para sus pruebas unitarias). Por ejemplo, Twisted/twisted/test/. Por supuesto, conviértalo en un paquete con Twisted/twisted/test/__init__.py. Coloque pruebas en archivos como Twisted/twisted/test/test_internet.py.
  • agregue Twisted/READMEy Twisted/setup.pypara explicar e instalar su software, respectivamente, si se siente bien.

No:

  • ponga su fuente en un directorio llamado srcolib . Esto hace que sea difícil de ejecutar sin instalar.
  • pon tus pruebas fuera de tu paquete de Python. Esto hace que sea difícil ejecutar las pruebas en una versión instalada.
  • cree un paquete que solo tenga un __init__.pyy luego coloque todo su código __init__.py. Simplemente haga un módulo en lugar de un paquete, es más simple.
  • intente crear hacks mágicos para que Python pueda importar su módulo o paquete sin que el usuario agregue el directorio que lo contiene a su ruta de importación (ya sea a través de PYTHONPATH o algún otro mecanismo). Usted no manejar correctamente todos los casos y los usuarios se va a enojar por lo que cuando el software no funciona en su entorno.
Adrian
fuente
25
Esto era exactamente lo que necesitaba. "NO intente crear trucos mágicos para que Python pueda importar su módulo o paquete sin que el usuario agregue el directorio que lo contiene a su ruta de importación". ¡Bueno saber!
Jack O'Connor
1
La cuestión es que esto no menciona la parte importante del documento de un proyecto donde ubicarlo.
lpapp
14
Confundido acerca de "poner su fuente en un directorio llamado src o lib. Esto hace que sea difícil de ejecutar sin instalar". ¿Qué se instalaría? ¿Es el nombre del directorio el que causa el problema o el hecho de que es un subdirectorio?
Peter Ehrlich
44
"Algunas personas afirman que debe distribuir sus pruebas dentro de su propio módulo. No estoy de acuerdo. A menudo aumenta la complejidad para sus usuarios; muchas suites de pruebas a menudo requieren dependencias adicionales y contextos de tiempo de ejecución". python-guide-pt-br.readthedocs.io/en/latest/writing/structure/…
endolith
2
"Esto dificulta la ejecución sin instalar". - ese es el punto
Nick T
123

Echa un vistazo a Open Sourcing a Python Project de la manera correcta .

Permítanme extraer la parte del diseño del proyecto de ese excelente artículo:

Al configurar un proyecto, el diseño (o la estructura del directorio) es importante para hacerlo bien. Una disposición sensata significa que los contribuyentes potenciales no tienen que pasar una eternidad buscando un código; Las ubicaciones de los archivos son intuitivas. Dado que estamos lidiando con un proyecto existente, significa que probablemente necesites mover algunas cosas.

Comencemos por la cima. La mayoría de los proyectos tienen una cantidad de archivos de nivel superior (como setup.py, README.md, required.txt, etc.). Hay tres directorios que cada proyecto debería tener:

  • Un directorio de documentos que contiene la documentación del proyecto.
  • Un directorio nombrado con el nombre del proyecto que almacena el paquete Python real
  • Un directorio de prueba en uno de dos lugares.
    • En el directorio del paquete que contiene código de prueba y recursos
    • Como un directorio de nivel superior independiente Para tener una mejor idea de cómo deben organizarse sus archivos, aquí hay una instantánea simplificada del diseño de uno de mis proyectos, sandman:
$ pwd
~/code/sandman
$ tree
.
|- LICENSE
|- README.md
|- TODO.md
|- docs
|   |-- conf.py
|   |-- generated
|   |-- index.rst
|   |-- installation.rst
|   |-- modules.rst
|   |-- quickstart.rst
|   |-- sandman.rst
|- requirements.txt
|- sandman
|   |-- __init__.py
|   |-- exception.py
|   |-- model.py
|   |-- sandman.py
|   |-- test
|       |-- models.py
|       |-- test_sandman.py
|- setup.py

Como puede ver, hay algunos archivos de nivel superior, un directorio de documentos (generado es un directorio vacío donde sphinx colocará la documentación generada), un directorio sandman y un directorio de prueba debajo de sandman.

David C. Bishop
fuente
44
Hago esto, pero más aún: tengo un Makefile de nivel superior con un objetivo 'env' que automatiza 'virtualenv env; ./env/bin/pip install -r require.txt; ./env/bin/python setup.py desarrollo ', y también generalmente un objetivo de' prueba 'que depende de env y también instala dependencias de prueba y luego ejecuta py.test.
pjz
@pjz ¿Podrías expandir tu idea? ¿Estás hablando de poner Makefileal mismo nivel que setup.py? Entonces, si entiendo que make envautomatiza correctamente la creación de un nuevo venve instala los paquetes en él ...
St.Antario
@ St.Antario exactamente. Como se mencionó, generalmente también tengo un objetivo de 'prueba' para ejecutar las pruebas, y a veces un objetivo de 'liberación' que mira la etiqueta actual y construye una rueda y la envía a pypi.
pjz
19

Intente iniciar el proyecto utilizando la plantilla python_boilerplate . Sigue en gran medida las mejores prácticas (por ejemplo, las de aquí ), pero es más adecuado en caso de que esté dispuesto a dividir su proyecto en más de un huevo en algún momento (y créame, con cualquier cosa menos los proyectos más simples, lo hará. Uno) Una situación común es cuando tiene que usar una versión modificada localmente de la biblioteca de otra persona).

  • ¿Dónde pones la fuente?

    • Para proyectos decentemente grandes, tiene sentido dividir la fuente en varios huevos. Cada huevo iría como una disposición de herramientas de configuración separada debajo PROJECT_ROOT/src/<egg_name>.
  • ¿Dónde pones los scripts de inicio de la aplicación?

    • La opción ideal es tener el script de inicio de la aplicación registrado como entry_pointuno de los huevos.
  • ¿Dónde pones el proyecto IDE cruft?

    • Depende del IDE. Muchos de ellos guardan sus cosas en PROJECT_ROOT/.<something>la raíz del proyecto, y esto está bien.
  • ¿Dónde pones las pruebas de unidad / aceptación?

    • Cada huevo tiene un conjunto separado de pruebas, guardado en su PROJECT_ROOT/src/<egg_name>/testsdirectorio. Personalmente prefiero usar py.testpara ejecutarlos.
  • ¿Dónde coloca los datos que no son de Python, como los archivos de configuración?

    • Depende. Puede haber diferentes tipos de datos que no son de Python.
      • "Recursos" , es decir, datos que deben empaquetarse dentro de un huevo. Estos datos van al directorio de huevo correspondiente, en algún lugar dentro del espacio de nombres del paquete. Se puede usar a través del pkg_resourcespaquete desde setuptools, o desde Python 3.7 a través del importlib.resourcesmódulo de la biblioteca estándar.
      • "Archivos de configuración" , es decir, archivos que no son de Python que deben considerarse externos a los archivos de origen del proyecto, pero deben inicializarse con algunos valores cuando la aplicación comienza a ejecutarse. Durante el desarrollo, prefiero guardar esos archivos PROJECT_ROOT/config. Para la implementación puede haber varias opciones. En Windows se puede usar %APP_DATA%/<app-name>/config, en Linux /etc/<app-name>o /opt/<app-name>/config.
      • Archivos generados , es decir, archivos que la aplicación puede crear o modificar durante la ejecución. Preferiría mantenerlos dentro PROJECT_ROOT/vardurante el desarrollo y debajo /vardurante la implementación de Linux.
  • ¿Dónde coloca las fuentes que no son de Python como C ++ para módulos de extensión binaria pyd / so?
    • Dentro PROJECT_ROOT/src/<egg_name>/native

La documentación generalmente entraría PROJECT_ROOT/doco PROJECT_ROOT/src/<egg_name>/doc(esto depende de si considera que algunos de los huevos son proyectos grandes separados). Alguna configuración adicional estará en archivos como PROJECT_ROOT/buildout.cfgy PROJECT_ROOT/setup.cfg.

KT.
fuente
Gracias por una gran respuesta! ¡Me aclaraste muchas cosas! Solo tengo una pregunta: ¿Se pueden anidar los huevos?
Shookie
No, no puede "anidar" los huevos en el sentido de almacenar archivos .egg dentro de otros archivos .egg y esperar que esto sea de mucha utilidad [a menos que esté haciendo algo realmente extraño]. Sin embargo, lo que puede hacer es crear huevos "virtuales": paquetes vacíos que no proporcionan ningún código útil, pero enumeran otros paquetes en sus listas de dependencias. De esta manera, cuando un usuario intenta instalar dicho paquete, instalará recursivamente muchos huevos dependientes.
KT.
@KT, ¿puede explicar un poco cómo maneja los datos generados? En particular, ¿cómo distingue (en código) entre desarrollo e implementación? Me imagino que tienes alguna base_data_locationvariable, pero ¿cómo la configuras adecuadamente?
cmyr
1
Supongo que está hablando de "datos de tiempo de ejecución", algo que la gente suele poner bajo / var / packagename o ~ / .packagename / var, o cualquier otra cosa. La mayoría de las veces, esas opciones son suficientes por defecto para que a sus usuarios no les importe cambiar. Si desea permitir que se ajuste este comportamiento, las opciones son bastante abundantes y no creo que haya una única práctica adecuada para todos. Opciones típicas: a) ~ / .packagename / configfile, b) export MY_PACKAGE_CONFIG = / path / to / configfile c) opciones de línea de comandos o parámetros de función d) combinación de esos.
KT.
Tenga en cuenta que es bastante habitual tener una clase de configuración singleton en alguna parte, que maneja su lógica de carga de configuración favorita para usted y tal vez incluso le permite al usuario modificar la configuración en tiempo de ejecución. En general, sin embargo, creo que este es un tema que vale una pregunta por separado (que podría haberse preguntado antes en algún lugar aquí).
KT.
15

En mi experiencia, es solo una cuestión de iteración. Ponga sus datos y código donde quiera que vaya. Lo más probable es que te equivoques de todos modos. Pero una vez que tenga una mejor idea de cómo se van a arreglar las cosas, estará en una posición mucho mejor para hacer este tipo de conjeturas.

En cuanto a las fuentes de extensión, tenemos un directorio de Código debajo del tronco que contiene un directorio para python y un directorio para varios otros idiomas. Personalmente, estoy más inclinado a intentar poner cualquier código de extensión en su propio repositorio la próxima vez.

Dicho esto, vuelvo a mi punto inicial: no hagas un gran problema. Ponlo en un lugar que parezca funcionar para ti. Si encuentra algo que no funciona, puede (y debería) cambiarse.

Jason Baker
fuente
Sí. Trato de ser "Pythonic" al respecto: explícito es mejor que implícito. Las jerarquías de directorio se leen / inspeccionan más de lo que se escriben. Etc ..
eric
10

Los datos que no son de Python se agrupan mejor dentro de sus módulos de Python utilizando el package_datasoporte en setuptools . Una cosa que recomiendo encarecidamente es usar paquetes de espacio de nombres para crear espacios de nombres compartidos que pueden usar múltiples proyectos, al igual que la convención de Java de colocar paquetes com.yourcompany.yourproject(y poder tener un espacio compartidocom.yourcompany.utils espacio de nombres ).

Re ramificación y fusión, si usa un sistema de control de fuente lo suficientemente bueno, manejará las fusiones incluso a través de cambios de nombre; Bazar es particularmente bueno en esto.

Al contrario de algunas otras respuestas aquí, estoy +1 en tener un srcdirectorio de nivel superior (con docy testdirectorios al lado). Las convenciones específicas para los árboles de directorios de documentación variarán según lo que esté utilizando; Sphinx , por ejemplo, tiene sus propias convenciones que admite su herramienta de inicio rápido.

Por favor, aproveche setuptools y pkg_resources; esto hace que sea mucho más fácil para otros proyectos confiar en versiones específicas de su código (y para que varias versiones se instalen simultáneamente con diferentes archivos sin código, si está usando package_data).

Charles Duffy
fuente