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 examples
y tests
importar desde el
api
módulo y ejecutarse desde la línea de comandos?
Además, me gustaría evitar el sys.path.insert
truco feo para cada archivo. Seguramente esto se puede hacer en Python, ¿verdad?
python
packages
python-import
siblings
zachwill
fuente
fuente
sys.path
hacks 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.path
sigue siendo un truco rápido y sucio que funciona bien para scripts privados, pero ha habido varias mejorassetup.cfg
para almacenar los metadatos)-m
indicador 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.path
hacks 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 -e
es 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
api
carpeta principal de su ejemplo ).fuente
-m
formulario 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.api
trabajar 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.append
trucos disponibles, pero encontré una forma alternativa de resolver el problema.Resumen
packaged_stuff
)setup.py
script donde use setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Preparar
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_api
resultaría enPasos
El contenido para el
setup.py
serí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 venv
source ./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
myproject
usandopip
. El truco es usar la-e
bandera 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 freeze
myproject.
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 install
funcionarán aún funcionan bien. Vea un ejemplo a continuación.Prueba la solución
Ahora, probemos la solución usando los
api.py
definidos anteriormente y lostest_one.py
definidos 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=myproject
Alguna 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 myproject
me saleModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
muestra que está allí, el directorio es correcta, y tanto el verison depip
ypython
se verifica que es correcta.pip list
muestra paquetes, mientras quepip freeze
muestra 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
tests
carpeta: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.path
menos 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) dentroapi
y correrpython -m api.test
para ejecutar todas las pruebas (suponiendo que las haya__main__.py
) opython -m api.test.test_one
correr en sutest_one
lugar.También puede eliminar
__init__.py
deexamples
(no es un paquete de Python) y ejecutar los ejemplos en un virtualenv dondeapi
está instalado, por ejemplo,pip install -e .
en un virtualenv instalaría elapi
paquete in situ si tiene el adecuadosetup.py
.fuente
python -m api.test.test_one
desde 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
examples
otests
para 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.py
contiene el siguiente código:sib1 / call.py contiene:
y sib2 / callsib.py contiene:
Si reproduce este ejemplo, notará que la llamada
Main.py
dará como resultado que se imprima "Got Called" tal como se define ensib2/callsib.py
aunquesib2/callsib.py
se 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.py
usa 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
examples
otests
puedes 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__.py
archivo 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.