¿Podría decirme cómo puedo leer un archivo que está dentro de mi paquete de Python?
Mi situación
Un paquete que cargo tiene varias plantillas (archivos de texto que se usan como cadenas) que quiero cargar desde el programa. Pero, ¿cómo especifico la ruta a dicho archivo?
Imagina que quiero leer un archivo de:
package\templates\temp_file
¿Algún tipo de manipulación del camino? ¿Seguimiento de la ruta base del paquete?
Respuestas:
[agregado 2016-06-15: aparentemente esto no funciona en todas las situaciones. consulte las otras respuestas]
fuente
TLDR; Utilice el
importlib.resources
módulo de biblioteca estándar como se explica en el método no 2, a continuación.El tradicional
pkg_resources
desetuptools
que no se recomienda más porque el nuevo método:setuptools
), sino que confía solo en la biblioteca estándar de Python.Mantuve la lista tradicional primero, para explicar las diferencias con el nuevo método al portar el código existente (el portar también se explica aquí ).
Supongamos que sus plantillas están ubicadas en una carpeta anidada dentro del paquete de su módulo:
1) Usando
pkg_resources
desdesetuptools
(lento)Puede usar el
pkg_resources
paquete de la distribución setuptools , pero eso tiene un costo, en términos de rendimiento :... y tenga en cuenta que de acuerdo con Setuptools /
pkg_resources
docs, no debe usaros.path.join
:2) Python> = 3.7, o usando la
importlib_resources
biblioteca backportadaUtilice el
importlib.resources
módulo de la biblioteca estándar que es más eficiente que elsetuptools
anterior:Para el ejemplo planteado en la pregunta, ahora debemos:
<your_package>/templates/
en un paquete adecuado, creando un__init__.py
archivo vacío en él,import
declaración simple (posiblemente relativa) (no más análisis de nombres de paquetes / módulos),resource_name = "temp_file"
(sin camino).fuente
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
alguna idea?importlib.resources
y nopkg_resources
son necesariamente compatibles .importlib.resources
trabaja con zipfiles agregadossys.path
, setuptools ypkg_resources
trabaja con archivos egg, que son zipfiles almacenados en un directorio al que se agregasys.path
. Por ejemplosys.path = [..., '.../foo', '.../bar.zip']
, con los huevos entran.../foo
, pero los paquetesbar.zip
también se pueden importar. No se puede utilizarpkg_resources
para extraer datos de paquetes en formatobar.zip
. No he comprobado si setuptools registra el cargador necesario paraimportlib.resources
trabajar con huevos.Package has no location
aparece un error ?templates
en el ejemplo), puede establecer elpackage
argumento en__package__
, por ejemplopkg_resources.read_text(__package__, 'temp_file')
Un preludio de empaque:
Antes de que pueda preocuparse por leer archivos de recursos, el primer paso es asegurarse de que los archivos de datos se empaqueten en su distribución en primer lugar; es fácil leerlos directamente desde el árbol de fuentes, pero la parte importante es hacer asegúrese de que estos archivos de recursos sean accesibles desde el código dentro de un paquete instalado .
Estructura tu proyecto de esta manera, poniendo los archivos de datos en un subdirectorio dentro del paquete:
Deberías pasar
include_package_data=True
lasetup()
llamada. El archivo de manifiesto solo es necesario si desea utilizar setuptools / distutils y compilar distribuciones de origen. Para asegurarse de quetemplates/temp_file
se empaquete para esta estructura de proyecto de ejemplo, agregue una línea como esta en el archivo de manifiesto:Nota cruft histórica: no es necesario usar un archivo de manifiesto para backends de compilación modernos como flit, poetry, que incluirán los archivos de datos del paquete de forma predeterminada. Entonces, si está usando
pyproject.toml
y no tiene unsetup.py
archivo, puede ignorar todo lo relacionado conMANIFEST.in
.Ahora, con el embalaje fuera del camino, en la parte de lectura ...
Recomendación:
Utilice
pkgutil
API de biblioteca estándar . Se verá así en el código de la biblioteca:Funciona en cremalleras. Funciona en Python 2 y Python 3. No requiere dependencias de terceros. Realmente no estoy al tanto de ninguna desventaja (si es así, por favor comente la respuesta).
Malas formas de evitar:
Manera mala # 1: usar rutas relativas de un archivo fuente
Esta es actualmente la respuesta aceptada. En el mejor de los casos, se parece a esto:
¿Qué está mal con eso? La suposición de que tiene archivos y subdirectorios disponibles no es correcta. Este enfoque no funciona si se ejecuta código que está empaquetado en un zip o una rueda, y puede estar completamente fuera del control del usuario si su paquete se extrae o no en un sistema de archivos.
Mal camino # 2: usar las API pkg_resources
Esto se describe en la respuesta más votada. Se parece a esto:
¿Qué está mal con eso? Agrega una dependencia del tiempo de ejecución en las herramientas de configuración , que preferiblemente debería ser solo una dependencia del tiempo de instalación . La importación y el uso
pkg_resources
pueden volverse muy lentos, ya que el código crea un conjunto de trabajo de todos los paquetes instalados, aunque solo le interesen los recursos de su propio paquete. Eso no es un gran problema en el momento de la instalación (ya que la instalación es única), pero es feo en el tiempo de ejecución.Mal camino # 3: usar las API de importlib.resources
Esta es actualmente la recomendación en la respuesta más votada. Es una adición de biblioteca estándar reciente ( nueva en Python 3.7 ), pero también hay un backport disponible. Se parece a esto:
¿Qué está mal con eso? Bueno, desafortunadamente, no funciona ... todavía. Esta es todavía una API incompleta, el uso
importlib.resources
requerirá que agregue un archivo vacíotemplates/__init__.py
para que los archivos de datos residan dentro de un subpaquete en lugar de en un subdirectorio. También expondrá elpackage/templates
subdirectorio como un subpaquete importablepackage.templates
por derecho propio. Si eso no es un gran problema y no le molesta, puede continuar y agregar el__init__.py
archivo allí y usar el sistema de importación para acceder a los recursos. Sin embargo, mientras lo hace, también puede convertirlo en unmy_resources.py
archivo, y simplemente definir algunos bytes o variables de cadena en el módulo, luego importarlos en código Python. Es el sistema de importación el que hace el trabajo pesado aquí de cualquier manera.Proyecto de ejemplo:
Creé un proyecto de ejemplo en github y lo cargué en PyPI , que demuestra los cuatro enfoques discutidos anteriormente. Pruébelo con:
Consulte https://github.com/wimglenn/resources-example para obtener más información.
fuente
importlib.resources
pesar de todas estas deficiencias con una API incompleta que ya está pendiente de desaprobación ? Lo nuevo no es necesariamente mejor. Dígame qué ventajas ofrece realmente sobre stdlib pkgutil, que su respuesta no menciona.pkgutil.get_data()
confirmó mi instinto: es una API subdesarrollada y que quedará obsoleta. Dicho esto, estoy de acuerdo con usted,importlib.resources
no es una alternativa mucho mejor, pero hasta que PY3.10 resuelva esto, mantengo esta elección, habiendo aprendido que no es simplemente otro "estándar" recomendado por los documentos.pkgutil
no se menciona en absoluto en el programa de obsolescencia de PEP 594 - Eliminación de baterías agotadas de la biblioteca estándar , y es poco probable que se eliminen sin una buena razón. Ha existido desde Python 2.3 y se especifica como parte del protocolo de carga en PEP 302 . Usar una "API subdefinida" no es una respuesta muy convincente, ¡que podría describir la mayoría de la biblioteca estándar de Python!pkgutil
en casi todos los sentidos. Su "instinto" y apelación a la autoridad no tiene sentido para mí, si hay problemas con losget_data
cargadores, muestre pruebas y ejemplos prácticos.En caso de que tengas esta estructura
necesitas este código:
La extraña parte de "usar siempre barra oblicua" proviene de las
setuptools
APIEn caso de que se pregunte dónde está la documentación:
fuente
El contenido de "10.8. Lectura de archivos de datos dentro de un paquete" de Python Cookbook, tercera edición por David Beazley y Brian K. Jones dando las respuestas.
Lo llevaré hasta aquí:
Suponga que tiene un paquete con archivos organizados de la siguiente manera:
Ahora suponga que el archivo spam.py quiere leer el contenido del archivo somedata.dat. Para hacerlo, use el siguiente código:
Los datos variables resultantes serán una cadena de bytes que contiene el contenido sin procesar del archivo.
El primer argumento de get_data () es una cadena que contiene el nombre del paquete. Puede suministrarlo directamente o utilizar una variable especial, como
__package__
. El segundo argumento es el nombre relativo del archivo dentro del paquete. Si es necesario, puede navegar en diferentes directorios utilizando las convenciones estándar de nombres de archivo de Unix siempre que el directorio final todavía se encuentre dentro del paquete.De esta forma, el paquete se puede instalar como directorio, .zip o .egg.
fuente
Cada módulo de Python en su paquete tiene un
__file__
atributoPuedes usarlo como:
Para obtener recursos sobre huevos, consulte: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources
fuente
asumiendo que está utilizando una lima de huevo; no extraído:
"Resolví" esto en un proyecto reciente, usando un script postinstall, que extrae mis plantillas del huevo (archivo zip) al directorio apropiado en el sistema de archivos. Fue la solución más rápida y confiable que encontré, ya que trabajar con
__path__[0]
puede salir mal a veces (no recuerdo el nombre, pero encontré al menos una biblioteca, ¡eso agregó algo al frente de esa lista!).Además, los archivos de huevos generalmente se extraen sobre la marcha a una ubicación temporal llamada "caché de huevos". Puede cambiar esa ubicación utilizando una variable de entorno, ya sea antes de iniciar su script o incluso más tarde, por ejemplo.
Sin embargo, hay pkg_resources que podrían hacer el trabajo correctamente.
fuente