Um prelúdio de embalagem:
Antes mesmo de se preocupar com a leitura de arquivos de recursos, a primeira etapa é certificar-se de que os arquivos de dados estão sendo empacotados em sua distribuição - é fácil lê-los diretamente da árvore de origem, mas a parte importante é fazer certifique-se de que esses arquivos de recursos sejam acessíveis a partir do código de um pacote instalado .
Estruture seu projeto assim, colocando os arquivos de dados em um subdiretório dentro do pacote:
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
Você deve passar include_package_data=True
na setup()
chamada. O arquivo de manifesto é necessário apenas se você quiser usar setuptools / distutils e distribuições de código-fonte de compilação. Para garantir que o templates/temp_file
seja empacotado para esta estrutura de projeto de exemplo, adicione uma linha como esta no arquivo de manifesto:
recursive-include package *
Nota crítica histórica: o uso de um arquivo de manifesto não é necessário para back-ends de compilação moderna , como flit, poesia, que incluirá os arquivos de dados do pacote por padrão. Portanto, se você estiver usando pyproject.toml
e não tiver um setup.py
arquivo, poderá ignorar todas as informações sobre MANIFEST.in
.
Agora, com a embalagem fora do caminho, para a parte de leitura ...
Recomendação:
Use pkgutil
APIs de biblioteca padrão . Vai ficar assim no código da 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 em zips. Funciona em Python 2 e Python 3. Não requer dependências de terceiros. Não estou realmente ciente de quaisquer desvantagens (se você estiver, por favor, comente sobre a resposta).
Maneiras ruins de evitar:
Maneira ruim nº 1: usando caminhos relativos de um arquivo de origem
Esta é atualmente a resposta aceita. Na melhor das hipóteses, é mais ou menos assim:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
O que há de errado nisso? A suposição de que você possui arquivos e subdiretórios disponíveis não é correta. Essa abordagem não funciona se estiver executando um código compactado em um zip ou roda, e pode estar totalmente fora do controle do usuário, independentemente de seu pacote ser extraído ou não para um sistema de arquivos.
Maneira ruim nº 2: usando APIs pkg_resources
Isso é descrito na resposta mais votada. É mais ou menos assim:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
O que há de errado nisso? Ele adiciona uma dependência de tempo de execução em ferramentas de instalação , que deve ser preferencialmente apenas uma dependência de tempo de instalação . A importação e o uso pkg_resources
podem se tornar muito lentos, pois o código constrói um conjunto de trabalho de todos os pacotes instalados, mesmo que você esteja interessado apenas em seus próprios recursos de pacote. Isso não é grande coisa no momento da instalação (já que a instalação é única), mas é feio no momento da execução.
Maneira ruim nº 3: usando APIs importlib.resources
Esta é atualmente a recomendação na resposta mais votada. É uma adição recente à biblioteca padrão ( nova no Python 3.7 ), mas também está disponível uma porta traseira. Se parece com isso:
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))
O que há de errado nisso? Bem, infelizmente, não funciona ... ainda. Esta ainda é uma API incompleta, o uso importlib.resources
exigirá que você adicione um arquivo vazio templates/__init__.py
para que os arquivos de dados residam em um subpacote em vez de em um subdiretório. Ele também irá expor o package/templates
subdiretório como um subpacote importável package.templates
por conta própria. Se isso não é um grande problema e não o incomoda, então você pode ir em frente e adicionar o __init__.py
arquivo lá e usar o sistema de importação para acessar os recursos. No entanto, enquanto você está nisso, você também pode transformá-lo em um my_resources.py
arquivo e apenas definir alguns bytes ou variáveis de string no módulo e importá-los no código Python. É o sistema de importação que está fazendo o trabalho pesado aqui de qualquer maneira.
Projeto de exemplo:
Criei um projeto de exemplo no github e carreguei no PyPI , que demonstra todas as quatro abordagens discutidas acima. Experimente com:
$ pip install resources-example
$ resources-example
Veja https://github.com/wimglenn/resources-example para mais informações.