xticks barplot python packages python-import siblings

python - barplot - pandas plot



Importaciones de paquetes hermanos (9)

Intenté leer las preguntas sobre las 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 pueden las secuencias de comandos de los directorios de examples y tests importar desde el módulo api y ejecutarse desde la línea de comandos?

Además, me gustaría evitar el feo sys.path.insert hack para cada archivo. Sin duda, esto se puede hacer en Python, ¿verdad?


¿Cansado de sys.path hacks?

Hay muchos sys.path.append -hacks disponibles, pero encontré una forma alternativa de resolver el problema: las setuptools . No estoy seguro de si hay bordes que no funcionan bien con esto. Lo siguiente se prueba con Python 3.6.5, (Anaconda, conda 4.5.1), máquina con Windows 10.

Preparar

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é al . la carpeta raíz, y en mi caso de ejemplo, está ubicada en C:/tmp/test_imports/ .

api.py

Como caso de prueba, usemos lo 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 intentar importaciones relativas no funcionará:

Usando 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

Pasos

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

El contenido de setup.py sería *

from setuptools import setup, find_packages setup(name=''myproject'', version=''1.0'', packages=find_packages())

2) Usa un entorno virtual

Si está familiarizado con entornos virtuales, active uno y salte al próximo paso. El uso de entornos virtuales no es absolutamente necesario, pero realmente te ayudarán en el largo plazo (cuando tienes más de un proyecto en curso ...). Los pasos más básicos son (ejecutar en la carpeta raíz)

  • Crear env virtual
    • python -m venv venv
  • Activa el env virtual
    • ./venv/bin/activate (Linux) o ./venv/Scripts/activate (Win)

Para obtener más información acerca de esto, simplemente busque en Google "tutorial de env virtual de python" o similar. Probablemente nunca necesite otros comandos que crear, activar y desactivar.

Una vez que haya creado y activado un entorno virtual, su consola debería 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]

3) pip instala tu proyecto en estado editable

Instale su paquete de nivel superior myproject usando pip . El truco es usar el indicador -e cuando se realiza 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, ejecuta

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

4) Agregue myproject. en sus importaciones

Tenga en cuenta que tendrá que agregar myproject. solo en importaciones que no funcionarían de otra manera. Las importaciones que funcionaron sin la instalación de setup.py & pip install funcionarán aún funcionan bien. Vea un ejemplo a continuación.

Pruebe la solución

Ahora, api.py la solución usando api.py definido anteriormente, y test_one.py definido 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()

corriendo la prueba

(venv) PS C:/tmp/test_imports> python ./myproject/tests/test_one.py I am the return value from api.api!

* Consulte los setuptools para ver ejemplos más detallados de setup.py.

** En realidad, podría poner 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 carpeta de tests :

# Path hack. import sys, os sys.path.insert(0, os.path.abspath(''..''))


Como ya se dijo en otra parte, la horrible verdad es que tienes que hacer feos hacks para permitir las importaciones desde los módulos de hermanos o los padres desde un módulo __main__ . El problema se detalla en PEP 366 . PEP 3122 intentó manejar las importaciones de una manera más racional, pero Guido lo ha rechazado una vez la cuenta de

El único caso de uso parece ser ejecutar secuencias de comandos que casualmente están viviendo dentro del directorio de un módulo, que siempre he visto como un antipatrón.

( here )

Sin embargo, uso 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í, la path[0] es la carpeta principal de su secuencia de comandos en ejecución y dir(path[0]) su carpeta de nivel superior.

Sin embargo, aún no he podido usar importaciones relativas con esto, pero sí permite importaciones absolutas desde el nivel superior (en la carpeta principal de la api ejemplo).


Debe 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:

import api.api

... 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 usted dice) sería ejecutar los ejemplos desde el directorio de nivel superior, como este:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py


En primer lugar, 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 de examples o tests puedes llamar:

from ..api import api


No necesita y no debe hackear sys.path menos que sea necesario y en este caso no lo es. Utilizar:

import api.api_key # in tests, examples

Ejecutar desde el directorio del proyecto: python -m tests.test_one .

Probablemente deberías mover las tests (si son tests api) dentro de api y ejecutar python -m api.test para ejecutar todas las pruebas (asumiendo que __main__.py ) o python -m api.test.test_one para ejecutar test_one en 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 se instala api , por ejemplo, pip install -e . en un virtualenv instalaría el paquete api in situ si tiene setup.py apropiado.


Para las importaciones de paquete de hermanos, puede usar el método de inserción o el método de adición 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 puedes usar la importación relativa:

if __name__ == ''__main__'' and if __package__ is not None: import ..api.api

En este caso, deberá iniciar su secuencia de comandos 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 para 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


Solo en caso de que alguien use Pydev en Eclipse termine aquí: puede agregar la ruta padre del hermano (y por lo tanto el padre del módulo llamante) como una carpeta de biblioteca externa usando Proyecto-> Propiedades y configurando Bibliotecas Externas en el menú izquierdo Pydev-PYTHONPATH . Luego puede importar de su hermano, por ejemplo, from sibling import some_class .


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 importación relativa de hermanos / hermanas. Hasta ese día, esta es mi solución. Para examples o tests para importar cosas de ../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