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:
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
Deberías pasar include_package_data=True
la setup()
llamada. El archivo de manifiesto solo es necesario si desea utilizar setuptools / distutils y compilar distribuciones de origen. Para asegurarse de que templates/temp_file
se empaquete para esta estructura de proyecto de ejemplo, agregue una línea como esta en el archivo de manifiesto:
recursive-include package *
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 un setup.py
archivo, puede ignorar todo lo relacionado con MANIFEST.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:
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
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:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
¿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:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
¿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:
try:
from importlib.resources import read_binary
from importlib.resources import read_text
except ImportError:
# Python 2.x backport
from importlib_resources import read_binary
from importlib_resources import read_text
data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))
¿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ío templates/__init__.py
para que los archivos de datos residan dentro de un subpaquete en lugar de en un subdirectorio. También expondrá el package/templates
subdirectorio como un subpaquete importable package.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 un my_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:
$ pip install resources-example
$ resources-example
Consulte https://github.com/wimglenn/resources-example para obtener más información.