Intenté leer las preguntas sobre las importaciones entre hermanos e incluso la documentación del paquete , pero aún no he encontrado una respuesta.
Con la siguiente estructura:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
¿Cómo pueden los scripts en los directorios examplesy testsimportar desde el
apimódulo y ejecutarse desde la línea de comandos?
Además, me gustaría evitar el sys.path.inserttruco feo para cada archivo. Seguramente esto se puede hacer en Python, ¿verdad?
python
packages
python-import
siblings
zachwill
fuente
fuente

sys.pathhacks y leer la única solución real que se ha publicado hasta ahora (¡después de 7 años!).Respuestas:
Siete años después
Desde que escribí la respuesta a continuación, la modificación
sys.pathsigue siendo un truco rápido y sucio que funciona bien para scripts privados, pero ha habido varias mejorassetup.cfgpara almacenar los metadatos)-mindicador y ejecutarse como un paquete también funciona (pero resultará un poco incómodo si desea convertir su directorio de trabajo en un paquete instalable).sys.pathhacks por usted.Entonces, realmente depende de lo que quieras hacer. Sin embargo, en su caso, dado que parece que su objetivo es hacer un paquete adecuado en algún momento, la instalación
pip -ees probablemente su mejor opción, incluso si aún no es perfecta.Vieja respuesta
Como ya se dijo en otra parte, la terrible verdad es que tienes que hacer hacks feos para permitir importaciones de módulos hermanos o paquetes padres desde un
__main__módulo. El problema se detalla en PEP 366 . PEP 3122 intentó manejar las importaciones de una manera más racional, pero Guido lo rechazó por cuenta de( aquí )
Sin embargo, uso este patrón de forma regular con
Aquí
path[0]está la carpeta principal de su script en ejecución ydir(path[0])su carpeta de nivel superior.Sin embargo, todavía no he podido usar importaciones relativas con esto, pero sí permite importaciones absolutas desde el nivel superior (en la
apicarpeta principal de su ejemplo ).fuente
-mformulario o si instala el paquete (pip y virtualenv lo hacen fácil)__package__ = "examples"mí. por que lo usas? 2. ¿En qué situación está__name__ == "__main__"pero__package__no estáNone?__packages__ayuda si desea una ruta absoluta comoexamples.apitrabajar iirc (pero ha pasado mucho tiempo desde la última vez que lo hice) y verificar que el paquete no es Ninguno fue principalmente seguro para situaciones extrañas y futuras pruebas.¿Cansado de los sys.path hacks?
Hay muchos
sys.path.appendtrucos disponibles, pero encontré una forma alternativa de resolver el problema.Resumen
packaged_stuff)setup.pyscript donde use setuptools.setup () .pip install -e <myproject_folder>from packaged_stuff.modulename import function_namePreparar
El punto de partida es la estructura de archivos que ha proporcionado, envuelta en una carpeta llamada
myproject.Llamaré a la
.carpeta raíz y, en mi caso de ejemplo, se encuentra enC:\tmp\test_imports\.api.py
Como caso de prueba, usemos lo siguiente ./api/api.py
test_one.py
Intenta ejecutar test_one:
También intentar las importaciones relativas no funcionará:
Usar
from ..api.api import function_from_apiresultaría enPasos
El contenido para el
setup.pysería *Si está familiarizado con los entornos virtuales, active uno y salte al siguiente paso. El uso de entornos virtuales no es absolutamente obligatorio, pero realmente lo ayudarán a largo plazo (cuando tenga más de 1 proyecto en curso ...). Los pasos más básicos son (ejecutar en la carpeta raíz)
python -m venv venvsource ./venv/bin/activate(Linux, macOS) o./venv/Scripts/activate(Win)Para obtener más información sobre esto, solo busque en Google "tutorial de python virtual env" o similar. Probablemente nunca necesite más comandos que crear, activar y desactivar.
Una vez que haya creado y activado un entorno virtual, su consola debe indicar el nombre del entorno virtual entre paréntesis
y tu árbol de carpetas debería verse así **
Instale su paquete de nivel superior
myprojectusandopip. El truco es usar la-ebandera al hacer la instalación. De esta forma, se instala en un estado editable, y todas las ediciones realizadas en los archivos .py se incluirán automáticamente en el paquete instalado.En el directorio raíz, ejecute
pip install -e .(tenga en cuenta el punto, significa "directorio actual")También puede ver que se instala utilizando
pip freezemyproject.a sus importacionesTenga en cuenta que tendrá que agregar
myproject.solo a las importaciones que de otro modo no funcionarían. Las importaciones que funcionaron sin elsetup.py&pip installfuncionarán aún funcionan bien. Vea un ejemplo a continuación.Prueba la solución
Ahora, probemos la solución usando los
api.pydefinidos anteriormente y lostest_one.pydefinidos a continuación.test_one.py
corriendo la prueba
* Consulte los documentos de setuptools para obtener ejemplos más detallados de setup.py.
** En realidad, podría colocar su entorno virtual en cualquier parte de su disco duro.
fuente
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myprojectAlguna idea sobre cómo resolver?ModuleNotFoundError? ¿He instalado 'myproject' en un virtualenv siguiendo estos pasos, y cuando entro en una sesión interpretada y ejecutoimport myprojectme saleModuleNotFoundError: No module named 'myproject'?pip list installed | grep myprojectmuestra que está allí, el directorio es correcta, y tanto el verison depipypythonse verifica que es correcta.pip listmuestra paquetes, mientras quepip freezemuestra nombres extraños si está instalado con la bandera -eAquí hay otra alternativa que inserto en la parte superior de los archivos de Python en la
testscarpeta:fuente
..aquí está relacionado con el directorio desde el que está ejecutando, no el directorio que contiene ese archivo de prueba / ejemplo. Estoy ejecutando desde el directorio del proyecto, y necesitaba en su./lugar. Espero que esto ayude a alguien más.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwilerNo necesita y no debe piratear a
sys.pathmenos que sea necesario y en este caso no lo es. Utilizar:Ejecutar en el directorio del proyecto:
python -m tests.test_one.Probablemente deberías moverte
tests(si son las pruebas unitarias de la API) dentroapiy correrpython -m api.testpara ejecutar todas las pruebas (suponiendo que las haya__main__.py) opython -m api.test.test_onecorrer en sutest_onelugar.También puede eliminar
__init__.pydeexamples(no es un paquete de Python) y ejecutar los ejemplos en un virtualenv dondeapiestá instalado, por ejemplo,pip install -e .en un virtualenv instalaría elapipaquete in situ si tiene el adecuadosetup.py.fuente
python -m api.test.test_onedesde cualquier lugar cuando virtualenv esté activado. Si no puede configurar PyCharm para ejecutar sus pruebas, intente hacer una nueva pregunta de desbordamiento de pila (si no puede encontrar una pregunta existente sobre este tema).Todavía no tengo la comprensión de Pythonology necesaria para ver la forma prevista de compartir código entre proyectos no relacionados sin un hack de hermanos / importación relativa. Hasta ese día, esta es mi solución. Para
examplesotestspara importar cosas..\api, se vería así:fuente
Para las importaciones de paquetes de hermanos, puede usar el método insert o append del módulo [sys.path] [2] :
Esto funcionará si está iniciando sus scripts de la siguiente manera:
Por otro lado, también puede usar la importación relativa:
En este caso, deberá iniciar su script con el argumento '-m' (tenga en cuenta que, en este caso, no debe dar la extensión '.py' ):
Por supuesto, puede mezclar los dos enfoques, para que su script funcione sin importar cómo se llame:
fuente
__file__global, así que tuve que usar lo siguiente:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))Pero ahora funciona en cualquier directorioTLDR
Este método no requiere herramientas de configuración, ataques de ruta, argumentos adicionales de la línea de comandos o especificar el nivel superior del paquete en cada archivo de su proyecto.
Simplemente haga un script en el directorio principal de lo que sea que esté llamando a ser suyo
__main__y ejecute todo desde allí. Para más explicaciones continúe leyendo.Explicación
Esto se puede lograr sin hackear una nueva ruta juntos, argumentos de línea de comando adicionales o agregar código a cada uno de sus programas para reconocer a sus hermanos.
La razón por la que esto falla, como creo que se mencionó antes, es porque los programas que se llaman tienen su
__name__conjunto como__main__. Cuando esto ocurre, el script que se llama acepta que se encuentra en el nivel superior del paquete y se niega a reconocer los scripts en directorios hermanos.Sin embargo, todo lo que esté debajo del nivel superior del directorio seguirá reconociendo CUALQUIER OTRA COSA debajo del nivel superior. Esto significa que lo ÚNICO que tiene que hacer para que los archivos en directorios hermanos se reconozcan / utilicen entre sí es llamarlos desde un script en su directorio principal.
Prueba de concepto En un directorio con la siguiente estructura:
Main.pycontiene el siguiente código:sib1 / call.py contiene:
y sib2 / callsib.py contiene:
Si reproduce este ejemplo, notará que la llamada
Main.pydará como resultado que se imprima "Got Called" tal como se define ensib2/callsib.pyaunquesib2/callsib.pyse le haya llamadosib1/call.py. Sin embargo, si se llamara directamentesib1/call.py(después de hacer los cambios apropiados a las importaciones), arroja una excepción. Aunque funcionó cuando fue llamado por el script en su directorio padre, no funcionará si cree que se encuentra en el nivel superior del paquete.fuente
Hice un proyecto de muestra para demostrar cómo manejé esto, que de hecho es otro hack de sys.path como se indicó anteriormente. Ejemplo de importación de Python Sibling , que se basa en:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())Esto parece ser bastante efectivo siempre que su directorio de trabajo permanezca en la raíz del proyecto Python. Si alguien implementa esto en un entorno de producción real, sería bueno saber si funciona allí también.
fuente
Debe mirar para ver cómo se escriben las declaraciones de importación en el código relacionado. Si
examples/example_one.pyusa la siguiente declaración de importación:... luego espera que el directorio raíz del proyecto esté en la ruta del sistema.
La forma más fácil de soportar esto sin ningún tipo de pirateo (como lo dice) sería ejecutar los ejemplos desde el directorio de nivel superior, como este:
fuente
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api. También me pasa lo mismoimport api.api.En caso de que alguien que usa Pydev en Eclipse termine aquí: puede agregar la ruta principal del hermano (y, por lo tanto, el padre del módulo de llamada) como una carpeta de biblioteca externa usando Proyecto-> Propiedades y configurando Bibliotecas externas en el menú izquierdo Pydev-PYTHONPATH . Luego puede importar desde su hermano, por ejemplo
from sibling import some_class.fuente
Primero, debe evitar tener archivos con el mismo nombre que el módulo en sí. Puede romper otras importaciones.
Cuando importa un archivo, primero el intérprete verifica el directorio actual y luego busca directorios globales.
Dentro
examplesotestspuedes llamar:fuente
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package__init__.pyarchivo al directorio de nivel superior. De lo contrario, Python no puede tratarlo como un módulo__name__es en__main__lugar depackage.module, Python no puede ver su paquete principal, por lo.que no señala nada.