python - librerias - pycharm pip install
¿Cómo se organiza un proyecto python que contiene múltiples paquetes para que cada archivo en un paquete se pueda ejecutar de forma individual? (3)
TL; DR
Aquí hay un repositorio de ejemplo que se configura como se describe en el primer diagrama (a continuación): https://github.com/Poddster/package_problems
Si pudiera hacer que se vea como el segundo diagrama en términos de organización del proyecto y todavía puede ejecutar los siguientes comandos, entonces ha respondido la pregunta:
$ git clone https://github.com/Poddster/package_problems.git
$ cd package_problems
<do your magic here>
$ nosetests
$ ./my_tool/my_tool.py
$ ./my_tool/t.py
$ ./my_tool/d.py
(or for the above commands, $ cd ./my_tool/ && ./my_tool.py is also acceptable)
Alternativamente: Dame una estructura de proyecto diferente que me permita agrupar archivos relacionados (''paquete''), ejecutar todos los archivos individualmente, importar los archivos a otros archivos en el mismo paquete e importar los paquetes / archivos a otros archivos del paquete .
Situación actual
Tengo un montón de archivos de Python. La mayoría de ellos son útiles cuando se pueden llamar desde la línea de comandos, es decir, todos usan argparse y if __name__ == "__main__"
para hacer cosas útiles.
Actualmente tengo esta estructura de directorios, y todo está funcionando bien:
.
├── config.txt
├── docs/
│ ├── ...
├── my_tool.py
├── a.py
├── b.py
├── c.py
├── d.py
├── e.py
├── README.md
├── tests
│ ├── __init__.py
│ ├── a.py
│ ├── b.py
│ ├── c.py
│ ├── d.py
│ └── e.py
└── resources
├── ...
Algunos de los scripts import
cosas de otros scripts para hacer su trabajo. Pero ningún script es meramente una biblioteca, todos son invokables. por ejemplo, podría invocar ./my_tool.py
, ./a.by
, ./b.py
, ./c.py
etc. y harían cosas útiles para el usuario.
"my_tool.py" es el script principal que aprovecha todos los otros scripts.
Lo que quiero que suceda
Sin embargo, quiero cambiar la forma en que se organiza el proyecto. El proyecto en sí representa un programa completo utilizable por el usuario, y se distribuirá como tal, pero sé que partes de él serán útiles en diferentes proyectos más adelante, así que quiero intentar y encapsular los archivos actuales en un paquete. En el futuro inmediato también agregaré otros paquetes a este mismo proyecto.
Para facilitar esto, he decidido reorganizar el proyecto a algo como lo siguiente:
.
├── config.txt
├── docs/
│ ├── ...
├── my_tool
│ ├── __init__.py
│ ├── my_tool.py
│ ├── a.py
│ ├── b.py
│ ├── c.py
│ ├── d.py
│ ├── e.py
│ └── tests
│ ├── __init__.py
│ ├── a.py
│ ├── b.py
│ ├── c.py
│ ├── d.py
│ └── e.py
├── package2
│ ├── __init__.py
│ ├── my_second_package.py
| ├── ...
├── README.md
└── resources
├── ...
Sin embargo, no puedo encontrar una organización de proyecto que satisfaga los siguientes criterios:
- Todos los scripts son invokables en la línea de comando (ya sea como
my_tool/a.py
ocd my_tool && a.py
) - Las pruebas realmente se ejecutan :)
- Los archivos del paquete2 pueden
import my_tool
El problema principal es con las declaraciones de importación utilizadas por los paquetes y las pruebas.
Actualmente, todos los paquetes, incluidas las pruebas, simplemente import <module>
y se resuelven correctamente. Pero cuando se enfrentan las cosas no funciona.
Tenga en cuenta que la compatibilidad con py2.7 es un requisito, por lo que todos los archivos tienen from __future__ import absolute_import, ...
en la parte superior.
Lo que he intentado y los resultados desastrosos
1
Si muevo los archivos como se muestra arriba, pero dejo todas las declaraciones de importación como están actualmente:
-
$ ./my_tool/*.py
funciona y todos funcionan correctamente -
$ nosetests
ejecutados desde el directorio superior no funciona. Las pruebas no pueden importar las secuencias de comandos de los paquetes. - pycharm resalta las declaraciones de importación en rojo al editar esos archivos :(
2
Si luego cambio los scripts de prueba para hacer:
from my_tool import x
-
$ ./my_tool/*.py
aún funciona y todos funcionan correctamente -
$ nosetests
ejecutados desde el directorio superior no funciona. Entonces, las pruebas pueden importar los scripts correctos, pero las importaciones en los scripts mismos fallan cuando los scripts de prueba los importan. - pycharm resalta las declaraciones de importación en rojo en los guiones principales aún :(
3
Si mantengo la misma estructura y cambio todo para que sea from my_tool import
entonces:
-
$ ./my_tool/*.py
resulta enImportError
s -
$ nosetests
ejecuta todo bien. - pycharm no se queja de nada
por ej. de 1 .:
Traceback (most recent call last):
File "./my_tool/a.py", line 34, in <module>
from my_tool import b
ImportError: cannot import name b
4
También lo intenté from . import x
from . import x
pero eso solo termina con ValueError: Attempted relative import in non-package
para la ejecución directa de scripts.
Mirando algunas otras respuestas SO:
No puedo usar python -m pkg.tests.core_test
como
a) No tengo el .py principal ¿Supongo que podría tener uno?
b) ¿Quiero poder ejecutar todos los scripts, no solo main?
He intentado:
if __name__ == ''__main__'' and __package__ is None:
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
pero no ayudó.
También probé:
__package__ = "my_tool"
from . import b
Pero recibido:
SystemError: Parent module ''loading_tool'' not loaded, cannot perform relative import
añadiendo import my_tool
before from . import b
from . import b
simplemente termina de nuevo con ImportError: cannot import name b
¿Fijar?
¿Cuál es el conjunto correcto de conjuros mágicos y el diseño del directorio para hacer que todo esto funcione?
Punto 1
Creo que está funcionando, entonces no lo comento.
Punto 2
Siempre utilicé pruebas al mismo nivel que my_tool, no debajo, pero deberían funcionar si haces esto en la parte superior de cada archivo de prueba (antes de importar my_tool o cualquier otro archivo py en el mismo directorio)
import os
import sys
sys.path.insert(0, os.path.abspath(__file__).rsplit(os.sep, 2)[0])
Punto 3
En my_second_package.py haga esto en la parte superior (antes de importar my_tool)
import os
import sys
sys.path.insert(0,
os.path.abspath(__file__).rsplit(os.sep, 2)[0] + os.sep
+ ''my_tool'')
Atentamente,
JM
Para ejecutarlo desde ambas líneas de comando y actuar como biblioteca, al tiempo que permite que nosetest opere de manera estándar, creo que tendrá que hacer un doble enfoque en Importaciones.
Por ejemplo, los archivos Python requerirán:
try:
import f
except ImportError:
import tools.f as f
Pasé e hice un PR del github que vinculó con todos los casos de prueba funcionando.
https://github.com/Poddster/package_problems/pull/1
Editar: Olvidé las importaciones en __init__.py
para __init__.py
correctamente en otros paquetes, agregó. Ahora debería poder hacer:
import tools
tools.c.do_something()
Una vez que se mueve a la configuración deseada, las importaciones absolutas que está utilizando para cargar los módulos que son específicos de my_tool
ya no funcionan.
Necesita tres modificaciones después de crear el subdirectorio my_tool
y mover los archivos en él:
Crea
my_tool/__init__.py
. (Parece que ya lo hace, pero quería mencionarlo para completarlo).En los archivos directamente debajo de
my_tool
: cambie las instrucciones deimport
para cargar los módulos del paquete actual. Entonces, enmy_tool.py
cambio:import c import d import k import s
a:
from . import c from . import d from . import k from . import s
Necesita hacer un cambio similar a todos sus otros archivos. (Menciona haber intentado establecer
__package__
y luego hacer una importación relativa, pero no es necesario configurar__package__
).En los archivos ubicados en
my_tool/tests
: cambie las instrucciones deimport
que importan el código que desea probar a las importaciones relativas que se cargan desde un paquete en la jerarquía. Entonces, entest_my_tool.py
cambiar:import my_tool
a:
from .. import my_tool
Del mismo modo para todos los otros archivos de prueba.
Con las modificaciones anteriores, puedo ejecutar módulos directamente:
$ python -m my_tool.my_tool
C!
D!
F!
V!
K!
T!
S!
my_tool!
my_tool main!
|main tool!||detected||tar edit!||installed||keys||LOL||ssl connect||parse ASN.1||config|
$ python -m my_tool.k
F!
V!
K!
K main!
|keys||LOL||ssl connect||parse ASN.1|
y puedo ejecutar pruebas:
$ nosetests
........
----------------------------------------------------------------------
Ran 8 tests in 0.006s
OK
Tenga en cuenta que puedo ejecutar lo anterior tanto con Python 2.7 como con Python 3.
En lugar de hacer que los diversos módulos de my_tool
sean ejecutables directamente, sugiero usar un archivo setup.py
adecuado para declarar los puntos de entrada y dejar que setup.py
cree estos puntos de entrada cuando se instale el paquete. Como tiene la intención de distribuir este código, debe usar setup.py
para empaquetarlo formalmente de todos modos.
Modifique los módulos que se pueden invocar desde la línea de comandos para que, tomando
my_tool/my_tool.py
como ejemplo, en lugar de esto:if __name__ == "__main__": print("my_tool main!") print(do_something())
Tienes:
def main(): print("my_tool main!") print(do_something()) if __name__ == "__main__": main()
Cree un archivo
setup.py
que contenga losentry_points
correctos. Por ejemplo:from setuptools import setup, find_packages setup( name="my_tool", version="0.1.0", packages=find_packages(), entry_points={ ''console_scripts'': [ ''my_tool = my_tool.my_tool:main'' ], }, author="", author_email="", description="Does stuff.", license="MIT", keywords=[], url="", classifiers=[ ], )
El archivo anterior indica a
setup.py
que cree un script llamadomy_tool
que invocará el métodomain
en el módulomy_tool.my_tool
. En mi sistema, una vez que el paquete está instalado, hay un script ubicado en/usr/local/bin/my_tool
que invoca el métodomain
enmy_tool.my_tool
. Produce el mismo resultado que ejecutarpython -m my_tool.my_tool
, que he mostrado anteriormente.