Intenté leer preguntas sobre importaciones de 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 se pueden importar los scripts en los directorios examples
y tests
desde el api
módulo y ejecutarlos 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?
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 mejoras.
setup.cfg
para almacenar los metadatos)-m
bandera y ejecutarlo 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 crear un paquete adecuado en algún momento, instalarlo pip -e
es probablemente su mejor opción, incluso si aún no es perfecto.
Como ya se dijo en otra parte, la terrible verdad es que tienes que hacer trucos horribles 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 ha rechazado en una cuenta de
El único caso de uso parece ser ejecutar scripts que viven dentro del directorio de un módulo, lo que siempre he visto como un antipatrón.
( aquí )
Sin embargo, utilizo este patrón de forma regular con
# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
from sys import path
from os.path import dirname as dir
path.append(dir(path[0]))
__package__ = "examples"
import api
Aquí path[0]
está la carpeta principal de su secuencia de comandos en ejecución y dir(path[0])
su carpeta de nivel superior.
Sin embargo, todavía no he podido usar importaciones relativas con esto, pero permite importaciones absolutas desde el nivel superior (en la api
carpeta principal de su ejemplo ).
Hay muchos sys.path.append
trucos disponibles, pero encontré una forma alternativa de resolver el problema en cuestión.
packaged_stuff
. Ej. )setup.py
script donde use setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
El punto de partida es la estructura de archivos que ha proporcionado, envuelta en una carpeta llamada myproject
.
.
└── myproject
├── api
│ ├── api_key.py
│ ├── api.py
│ └── __init__.py
├── examples
│ ├── example_one.py
│ ├── example_two.py
│ └── __init__.py
├── LICENCE.md
├── README.md
└── tests
├── __init__.py
└── test_one.py
Llamaré a la .
carpeta raíz y, en mi caso de ejemplo, se encuentra en C:\tmp\test_imports\
.
Como caso de prueba, usemos el siguiente ./api/api.py
def function_from_api():
return 'I am the return value from api.api!'
from api.api import function_from_api
def test_function():
print(function_from_api())
if __name__ == '__main__':
test_function()
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
File ".\myproject\tests\test_one.py", line 1, in <module>
from api.api import function_from_api
ModuleNotFoundError: No module named 'api'
El uso from ..api.api import function_from_api
resultaría en
PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
File ".\tests\test_one.py", line 1, in <module>
from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package
El contenido del setup.py
sería *
from setuptools import setup, find_packages
setup(name='myproject', version='1.0', packages=find_packages())
Si está familiarizado con los entornos virtuales, active uno y continúe con el siguiente paso. El uso de entornos virtuales no es absolutamente necesario, 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, simplemente busque en Google el "tutorial de entorno virtual de Python" 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 dar el nombre del entorno virtual entre paréntesis
PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
y su árbol de carpetas debería verse así **
.
├── myproject
│ ├── api
│ │ ├── api_key.py
│ │ ├── api.py
│ │ └── __init__.py
│ ├── examples
│ │ ├── example_one.py
│ │ ├── example_two.py
│ │ └── __init__.py
│ ├── LICENCE.md
│ ├── README.md
│ └── tests
│ ├── __init__.py
│ └── test_one.py
├── setup.py
└── venv
├── Include
├── Lib
├── pyvenv.cfg
└── Scripts [87 entries exceeds filelimit, not opening dir]
Instale su paquete de nivel superior myproject
usando pip
. El truco consiste en utilizar la -e
bandera al realizar 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 está instalado usando pip freeze
(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
myproject.
a sus importacionesTenga en cuenta que solo tendrá que agregar myproject.
a las importaciones que no funcionarían de otra manera. Las importaciones que funcionaron sin el setup.py
& pip install
seguirán funcionando bien. Vea un ejemplo a continuación.
Ahora, probemos la solución utilizando lo api.py
definido anteriormente y lo test_one.py
definido a continuación.
from myproject.api.api import function_from_api
def test_function():
print(function_from_api())
if __name__ == '__main__':
test_function()
(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!
* Consulte los documentos de setuptools para obtener ejemplos de setup.py más detallados.
** En realidad, podría colocar su entorno virtual en cualquier lugar de su disco duro.
Aquí hay otra alternativa que inserto en la parte superior de los archivos de Python en la tests
carpeta:
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
No necesitas ni debes hackear a sys.path
menos que sea necesario y en este caso no lo es. Usar:
import api.api_key # in tests, examples
Ejecutar en el directorio del proyecto: python -m tests.test_one
.
Probablemente debería mover tests
(si son pruebas unitarias de la API) adentro api
y ejecutar python -m api.test
para ejecutar todas las pruebas (suponiendo que las haya __main__.py
) o python -m api.test.test_one
ejecutarlas en su test_one
lugar.
También puede eliminar __init__.py
de examples
(no es un paquete de Python) y ejecutar los ejemplos en un virtualenv donde api
está instalado, por ejemplo, pip install -e .
en un virtualenv instalaría el api
paquete en el lugar si lo tiene setup.py
.
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 truco de importación de hermanos / parientes. Hasta ese día, esta es mi solución. Para examples
o tests
para importar cosas ..\api
, se vería así:
import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key
Para las importaciones de paquetes hermanos, puede usar el método insert o append del módulo [sys.path] [2] :
if __name__ == '__main__' and if __package__ is None:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
import api
Esto funcionará si está iniciando sus scripts de la siguiente manera:
python examples/example_one.py
python tests/test_one.py
Por otro lado, también puede utilizar la importación relativa:
if __name__ == '__main__' and if __package__ is not None:
import ..api.api
En este caso, tendrá que iniciar su script con el argumento '-m' (tenga en cuenta que, en este caso, no debe dar la extensión '.py' ):
python -m packageName.examples.example_one
python -m packageName.tests.test_one
Por supuesto, puede mezclar los dos enfoques, de modo que su script funcione sin importar cómo se llame:
if __name__ == '__main__':
if __package__ is None:
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
import api
else:
import ..api.api
TLDR
Este método no requiere herramientas de configuración, trucos de ruta, argumentos de línea de comando adicionales o especificar el nivel superior del paquete en cada archivo de su proyecto.
Simplemente cree un script en el directorio principal de lo que sea que esté llamando para que sea suyo __main__
y ejecute todo desde allí. Para mayor explicación, continúe leyendo.
Explicación
Esto se puede lograr sin piratear 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 que los programas que se llaman tienen su __name__
configuración como __main__
. Cuando esto ocurre, el script que se llama acepta estar en el nivel superior del paquete y se niega a reconocer los scripts en los directorios hermanos.
Sin embargo, todo lo que se encuentre 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 obtener archivos en directorios hermanos para que 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
|
|__Siblings
|
|___sib1
| |
| |__call.py
|
|___sib2
|
|__callsib.py
Main.py
contiene el siguiente código:
import sib1.call as call
def main():
call.Call()
if __name__ == '__main__':
main()
sib1 / call.py contiene:
import sib2.callsib as callsib
def Call():
callsib.CallSib()
if __name__ == '__main__':
Call()
y sib2 / callsib.py contiene:
def CallSib():
print("Got Called")
if __name__ == '__main__':
CallSib()
Si reproduce este ejemplo, notará que la llamada Main.py
dará como resultado que "Got Called" se imprima como se define en sib2/callsib.py
aunque sib2/callsib.py
se haya llamado sib1/call.py
. Sin embargo, si se llama directamente sib1/call.py
(después de realizar los cambios apropiados en las importaciones), se produce una excepción. Aunque funcionó cuando lo llamó el script en su directorio principal, no funcionará si cree que está en el nivel superior del paquete.
Hice un proyecto de muestra para demostrar cómo manejé esto, que de hecho es otro truco de sys.path como se indicó anteriormente. Ejemplo de importación de hermanos de Python , 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 genial saber si también funciona allí.
Debe mirar para ver cómo están escritas las declaraciones de importación en el código relacionado. Si examples/example_one.py
usa la siguiente declaración de importación:
import api.api
... entonces 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 truco (como lo dice) sería ejecutar los ejemplos desde el directorio de nivel superior, como este:
PYTHONPATH=$PYTHONPATH:. python examples/example_one.py
En caso de que alguien que use Pydev en Eclipse termine aquí: puede agregar la ruta principal del hermano (y, por lo tanto, la ruta principal del módulo de llamada) como una carpeta de biblioteca externa usando Proyecto-> Propiedades y configurando Bibliotecas externas en el menú de la izquierda Pydev-PYTHONPATH . Luego puede importar desde su hermano, por ejemplo from sibling import some_class
.
Quería comentar sobre la solución proporcionada por np8 pero no tengo suficiente reputación, así que solo mencionaré que puede crear un archivo setup.py exactamente como lo sugirieron, y luego hacerlo pipenv install --dev -e .
desde el directorio raíz del proyecto para convertirlo en una dependencia editable. Entonces sus importaciones absolutas funcionarán, por ejemplo, from api.api import foo
y no tendrá que perder el tiempo con instalaciones de todo el sistema.
Documentación
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.
Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.
Entiendo totalmente, completamente si tienes una relación difícil con los animales de peluche. Son lindos, tienen valor sentimental y es difícil separarse de ellos.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.