Você poderia me dizer como posso ler um arquivo que está dentro do meu pacote Python?
Minha situação
Um pacote que carrego contém vários modelos (arquivos de texto usados como strings) que desejo carregar de dentro do programa. Mas como faço para especificar o caminho para esse arquivo?
Imagine que eu queira ler um arquivo de:
package\templates\temp_file
Algum tipo de manipulação de caminho? Rastreamento do caminho básico do pacote?
Respostas:
[adicionado 2016-06-15: aparentemente, isso não funciona em todas as situações. consulte as outras respostas]
fonte
TLDR; Use o
importlib.resources
módulo da biblioteca padrão conforme explicado no método no 2, abaixo.O tradicional
pkg_resources
desetuptools
não é mais recomendado porque o novo método:setuptools
), mas confie apenas na biblioteca padrão do Python.Eu mantive o tradicional listado primeiro, para explicar as diferenças com o novo método ao portar o código existente (porting também explicado aqui ).
Vamos supor que seus modelos estejam localizados em uma pasta aninhada dentro do pacote do seu módulo:
1) Usando
pkg_resources
desetuptools
(lento)Você pode usar o
pkg_resources
pacote da distribuição de setuptools , mas isso vem com um custo e desempenho :... e observe que de acordo com Setuptools /
pkg_resources
docs, você não deve usaros.path.join
:2) Python> = 3.7 ou usando a
importlib_resources
biblioteca com backportUse o
importlib.resources
módulo da biblioteca padrão que é mais eficiente do que osetuptools
acima:Para o exemplo feito na pergunta, devemos agora:
<your_package>/templates/
em um pacote adequado, criando um__init__.py
arquivo vazio nele,import
instrução simples (possivelmente relativa) (sem mais análise de nomes de pacotes / módulos),resource_name = "temp_file"
(sem caminho).fonte
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
alguma ideia?importlib.resources
e nãopkg_resources
são necessariamente compatíveis .importlib.resources
funciona com arquivos zip adicionadossys.path
, ferramentas de instalação epkg_resources
funciona com arquivos egg, que são arquivos zip armazenados em um diretório ao qual ele próprio é adicionadosys.path
. Por exemplosys.path = [..., '.../foo', '.../bar.zip']
, com , os ovos entram.../foo
, mas os pacotesbar.zip
também podem ser importados. Você não pode usarpkg_resources
para extrair dados de pacotes embar.zip
. Não verifiquei se o setuptools registra o carregador necessário paraimportlib.resources
trabalhar com ovos.Package has no location
?templates
no exemplo), então você pode definir opackage
argumento como__package__
, por exemplopkg_resources.read_text(__package__, 'temp_file')
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:
Você deve passar
include_package_data=True
nasetup()
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 otemplates/temp_file
seja empacotado para esta estrutura de projeto de exemplo, adicione uma linha como esta no arquivo de manifesto: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 umsetup.py
arquivo, poderá ignorar todas as informações sobreMANIFEST.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: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:
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:
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:
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 vaziotemplates/__init__.py
para que os arquivos de dados residam em um subpacote em vez de em um subdiretório. Ele também irá expor opackage/templates
subdiretório como um subpacote importávelpackage.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 ummy_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:
Veja https://github.com/wimglenn/resources-example para mais informações.
fonte
importlib.resources
apesar de todas essas deficiências, uma API incompleta que já está com suspensão de uso ? Mais recente não é necessariamente melhor. Diga-me quais são as vantagens que ele realmente oferece sobre o stdlib pkgutil, sobre o qual sua resposta não faz nenhuma menção?pkgutil.get_data()
confirmou minha intuição - é uma API subdesenvolvida e a ser obsoleta. Dito isso, concordo com você,importlib.resources
não é uma alternativa muito melhor, mas até que PY3.10 resolva isso, mantenho essa escolha, mas aprendi que não é apenas mais um "padrão" recomendado pelos documentos.pkgutil
não é mencionado de forma alguma no cronograma de reprovação do PEP 594 - Remoção de baterias gastas da biblioteca padrão e é improvável que seja removido sem um bom motivo. Ele existe desde Python 2.3 e é especificado como parte do protocolo do carregador no PEP 302 . Usar uma "API subdefinida" não é uma resposta muito convincente, que poderia descrever a maior parte da biblioteca padrão do Python!pkgutil
em quase todos os sentidos. Seu "pressentimento" e apelo à autoridade não tem sentido para mim. Se houver problemas comget_data
carregadores, mostre evidências e exemplos práticos.Caso você tenha essa estrutura
você precisa deste código:
A parte estranha de "sempre usar barra" vem de
setuptools
APIsCaso você queira saber onde está a documentação:
fonte
O conteúdo em "10.8. Lendo Arquivos de Dados em um Pacote" do Python Cookbook, Terceira Edição de David Beazley e Brian K. Jones fornecendo as respostas.
Vou apenas chegar aqui:
Suponha que você tenha um pacote com arquivos organizados da seguinte maneira:
Agora, suponha que o arquivo spam.py deseja ler o conteúdo do arquivo somedata.dat. Para fazer isso, use o seguinte código:
Os dados variáveis resultantes serão uma string de bytes contendo o conteúdo bruto do arquivo.
O primeiro argumento para get_data () é uma string contendo o nome do pacote. Você pode fornecê-lo diretamente ou usar uma variável especial, como
__package__
. O segundo argumento é o nome relativo do arquivo dentro do pacote. Se necessário, você pode navegar em diferentes diretórios usando as convenções de nome de arquivo Unix padrão, desde que o diretório final ainda esteja localizado dentro do pacote.Desta forma, o pacote pode ser instalado como diretório, .zip ou .egg.
fonte
Cada módulo Python em seu pacote tem um
__file__
atributoVocê pode usá-lo como:
Para recursos de ovos, consulte: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources
fonte
assumindo que você está usando um arquivo de ovo; não extraído:
Eu "resolvi" isso em um projeto recente, usando um script de pós-instalação, que extrai meus modelos do ovo (arquivo zip) para o diretório apropriado no sistema de arquivos. Foi a solução mais rápida e confiável que encontrei, já que trabalhar com
__path__[0]
pode às vezes dar errado (não me lembro o nome, mas encontrei pelo menos uma biblioteca, que acrescentou algo na frente dessa lista!).Além disso, os arquivos de ovo são normalmente extraídos instantaneamente para um local temporário chamado "cache de ovo". Você pode alterar esse local usando uma variável de ambiente, antes de iniciar seu script ou até mais tarde, por exemplo.
No entanto, há pkg_resources que podem fazer o trabalho corretamente.
fonte