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