Importaciones de paquetes hermanos

230
zachwill 2011-06-13 08:42.

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 examplesy testsdesde el apimódulo y ejecutarlos 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?

11 answers

85
Evpok 2011-06-24 23:48.

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 mejoras.

  • Instalar el paquete (en un virtualenv o no) le dará lo que desea, aunque sugeriría usar pip para hacerlo en lugar de usar setuptools directamente (y usar setup.cfgpara almacenar los metadatos)
  • Usar la -mbandera 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).
  • Para las pruebas, específicamente, pytest puede encontrar el paquete api en esta situación y se encarga de los sys.pathhacks 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 -ees probablemente su mejor opción, incluso si aún no es perfecto.

Respuesta antigua

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 apicarpeta principal de su ejemplo ).

200
np8 2018-05-06 10:52.

¿Estás cansado de los hacks de sys.path?

Hay muchos sys.path.appendtrucos disponibles, pero encontré una forma alternativa de resolver el problema en cuestión.

Resumen

  • Envuelva el código en una carpeta (p packaged_stuff. Ej. )
  • Use crear setup.pyscript donde use setuptools.setup () .
  • Pip instala el paquete en estado editable con pip install -e <myproject_folder>
  • Importar usando from packaged_stuff.modulename import function_name

Configuración

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\.

api.py

Como caso de prueba, usemos el siguiente ./api/api.py

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

Intenta ejecutar test_one:

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'

También probar las importaciones relativas no funcionará:

El uso from ..api.api import function_from_apiresultarí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

Pasos

  1. Cree un archivo setup.py en el directorio de nivel raíz

El contenido del setup.pysería *

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. Utilice un entorno virtual

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)

  • Crear entorno virtual
    • python -m venv venv
  • Activar env virtual
    • 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]
  1. pip instala tu proyecto en estado editable

Instale su paquete de nivel superior myprojectusando pip. El truco consiste en utilizar la -ebandera 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
  1. Agregue myproject.a sus importaciones

Tenga 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 installseguirán funcionando bien. Vea un ejemplo a continuación.


Prueba la solución

Ahora, probemos la solución utilizando lo api.pydefinido anteriormente y lo test_one.pydefinido a continuación.

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

ejecutando la prueba

(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.

45
Cenk Alti 2012-02-26 07:02.

Aquí hay otra alternativa que inserto en la parte superior de los archivos de Python en la testscarpeta:

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
33
jfs 2014-05-09 03:12.

No necesitas ni debes hackear a sys.pathmenos 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 apiy ejecutar python -m api.testpara ejecutar todas las pruebas (suponiendo que las haya __main__.py) o python -m api.test.test_oneejecutarlas en su test_onelugar.

También puede eliminar __init__.pyde examples(no es un paquete de Python) y ejecutar los ejemplos en un virtualenv donde apiestá instalado, por ejemplo, pip install -e .en un virtualenv instalaría el apipaquete en el lugar si lo tiene setup.py.

9
user1330131 2012-04-13 10:32.

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 exampleso testspara 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
5
Paolo Rovelli 2015-01-11 07:09.

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
3
Thunderwood 2018-11-07 07:11.

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.pydará como resultado que "Got Called" se imprima como se define en sib2/callsib.pyaunque sib2/callsib.pyse 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.

2
ADataGMan 2019-08-05 12:39.

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í.

1
AJ. 2011-06-13 08:47.

Debe mirar para ver cómo están escritas las declaraciones de importación en el código relacionado. Si examples/example_one.pyusa 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 
1
Lord Henry Wotton 2014-05-08 09:05.

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.

0
jerry_ 2020-10-25 14:34.

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 fooy no tendrá que perder el tiempo con instalaciones de todo el sistema.

Documentación

Related questions

MORE COOL STUFF

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

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.

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

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.

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

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 láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

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?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

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!

¿Caduca el repelente de insectos?

¿Caduca el repelente de insectos?

¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

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.

Cuéntanos tus mejores trucos de Washington, DC

Cuéntanos tus mejores trucos de Washington, DC

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á eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

Un minorista está eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

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.

La mejor forma de guardar animales de peluche es dentro de un puf

La mejor forma de guardar animales de peluche es dentro de un puf

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.

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

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.

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

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

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

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".

Concept Drift: el mundo está cambiando demasiado rápido para la IA

Concept Drift: el mundo está cambiando demasiado rápido para la IA

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.

India me está pateando el culo

India me está pateando el culo

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.

¿Merrick Garland le ha fallado a Estados Unidos?

Es más de la mitad de la presidencia de Biden. ¿Qué está esperando Merrick Garland?

¿Merrick Garland le ha fallado a Estados Unidos?

Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.

Language