Accedi ai dati nella sottodirectory del pacchetto


130

Sto scrivendo un pacchetto Python con moduli che devono aprire file di dati in una ./data/sottodirectory. In questo momento ho i percorsi per i file hardcoded nelle mie classi e funzioni. Vorrei scrivere un codice più robusto che possa accedere alla sottodirectory indipendentemente da dove sia installato sul sistema dell'utente.

Ho provato una varietà di metodi, ma finora non ho avuto fortuna. Sembra che la maggior parte dei comandi della "directory corrente" restituisca la directory dell'interprete python del sistema e non la directory del modulo.

Sembra che dovrebbe essere un problema banale e comune. Eppure non riesco a capirlo. Parte del problema è che i miei file di dati non sono .pyfile, quindi non posso usare funzioni di importazione e simili.

Eventuali suggerimenti?

In questo momento la mia directory del pacchetto appare come:

/
__init__.py
module1.py
module2.py
data/   
   data.txt

Sto provando ad accedere data.txtda module*.py!


Risposte:


24

È possibile utilizzare __file__per ottenere il percorso del pacchetto, in questo modo:

import os
this_dir, this_filename = os.path.split(__file__)
DATA_PATH = os.path.join(this_dir, "data", "data.txt")
print open(DATA_PATH).read()

44
Questo non funzionerà se i file sono in una distribuzione (IE. Egg). Utilizzare pkg_resources per accedere al file di dati.
Chris,

2
Anzi, questo è rotto.
Federico,

1
Inoltre, __file__non funziona con py2exe, poiché il valore sarà il percorso del file zip.
Pod

1
Questo ha funzionato davvero per me. Non ho avuto problemi. Sto usando Python 3.6
Jorge

1
Questo non funzionerà in caso di distribuzione (uovo ecc.).
Adarsh ​​Trivedi,

166

Il modo standard per farlo è con i pacchetti setuptools e pkg_resources.

È possibile disporre il pacchetto in base alla seguente gerarchia e configurare il file di installazione del pacchetto in modo che punti le risorse di dati, come da questo collegamento:

http://docs.python.org/distutils/setupscript.html#installing-package-data

Puoi quindi ritrovare e utilizzare quei file usando pkg_resources, come da questo link:

http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access

import pkg_resources

DATA_PATH = pkg_resources.resource_filename('<package name>', 'data/')
DB_FILE = pkg_resources.resource_filename('<package name>', 'data/sqlite.db')

7
Pkg_resources non creerà una dipendenza runtime da setuptools ? Ad esempio, ridistribuisco un pacchetto Debian, quindi perché dovrei dipendere python-setuptoolssolo da quello? Finora __file__funziona bene per me.
mlt

4
Perché è meglio: la classe ResourceManager fornisce un accesso uniforme alle risorse del pacchetto, indipendentemente dal fatto che tali risorse esistano come file e directory o siano compresse in un archivio di qualche tipo
vrdhn

4
Suggerimento brillante, grazie. Ho implementato un file standard aperto usandofrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst il

5
Come funzionerà per l'utilizzo del pacchetto quando non è installato? Sto solo testando localmente intendo
Claudiu,

11
In Python 3.7, importlib.resourcessostituisce pkg_resourcesa questo scopo (a causa di problemi di prestazioni).
benjimin

13

Fornire una soluzione funzionante oggi. Usa sicuramente questa API per non reinventare tutte quelle ruote.

È necessario un vero nome file per il filesystem. Le uova compresse verranno estratte in una directory della cache:

from pkg_resources import resource_filename, Requirement

path_to_vik_logo = resource_filename(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Restituisce un oggetto leggibile simile a un file per la risorsa specificata; può essere un file reale, uno StringIO o un oggetto simile. Lo stream è in "modalità binaria", nel senso che qualunque byte si trovi nella risorsa verrà letto così com'è.

from pkg_resources import resource_stream, Requirement

vik_logo_as_stream = resource_stream(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Individuazione dei pacchetti e accesso alle risorse tramite pkg_resources


10

Spesso non ha senso formulare una risposta che descriva in dettaglio il codice che non funziona così com'è, ma ritengo che ciò costituisca un'eccezione. Python 3.7 ha aggiunto importlib.resourcesche dovrebbe sostituire pkg_resources. Funzionerebbe per accedere ai file all'interno di pacchetti che non hanno barre nei loro nomi, ad es

foo/
    __init__.py
    module1.py
    module2.py
    data/   
       data.txt
    data2.txt

cioè si poteva accedere data2.txtpacchetto al suo interno foocon, ad esempio

importlib.resources.open_binary('foo', 'data2.txt')

ma fallirebbe con un'eccezione per

>>> importlib.resources.open_binary('foo', 'data/data.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/importlib/resources.py", line 87, in open_binary
    resource = _normalize_path(resource)
  File "/usr/lib/python3.7/importlib/resources.py", line 61, in _normalize_path
    raise ValueError('{!r} must be only a file name'.format(path))
ValueError: 'data/data2.txt' must be only a file name

Questo non può essere risolto se non con sistemazione __init__.pyin datae quindi utilizzando come un pacchetto:

importlib.resources.open_binary('foo.data', 'data.txt')

La ragione di questo comportamento è "è di progettazione" ; ma il design potrebbe cambiare ...


Hai un link migliore per "è di progettazione" rispetto a un video di YouTube - preferibilmente uno con testo?
Gerrit,

@gerrit il 2 contiene testo. "This was a deliberate choice, but I think you have a valid use case. @brettcannon what do you think? And if we allow this, should we make sure it gets into Python 3.7?"
Antti Haapala,

8

Hai bisogno di un nome per l'intero modulo, ti viene dato l'albero delle directory che non elenca quel dettaglio, per me questo ha funzionato:

import pkg_resources
print(    
    pkg_resources.resource_filename(__name__, 'data/data.txt')
)

In particolare, setuptools non sembra risolvere i file in base a una corrispondenza di nome con file di dati compressi, quindi dovresti includere il data/prefisso praticamente qualunque cosa. Puoi usarlo os.path.join('data', 'data.txt)se hai bisogno di separatori di directory alternativi, ma in genere non trovo problemi di compatibilità con i separatori di directory in stile unix codificati.


docs.python.org/3.6/distutils/… > Nota che qualsiasi percorso (file o directory) fornito nello script di installazione dovrebbe essere scritto usando la convenzione Unix, cioè separata da barra. Distutils si occuperà di convertire questa rappresentazione neutra in piattaforma in qualsiasi cosa sia appropriata sulla tua piattaforma attuale prima di utilizzare effettivamente il nome percorso. Questo rende il tuo script di installazione portatile su tutti i sistemi operativi, che ovviamente è uno dei principali obiettivi di Distutils. In questo spirito, tutti i nomi di percorso in questo documento sono separati da una barra.
Changyuheng,

6

Penso di aver cercato una risposta.

Faccio un modulo data_path.py, che importa negli altri miei moduli contenente:

data_path = os.path.join(os.path.dirname(__file__),'data')

E poi apro tutti i miei file con

open(os.path.join(data_path,'filename'), <param>)

2
Questo non funzionerà quando la risorsa si trova in una distribuzione di archivio (come un uovo compresso). Preferisco qualcosa del genere:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis

@ankostis setuptools è abbastanza intelligente da estrarre l'archivio se rileva che hai usato __file__da qualche parte. Nel mio caso uso una libreria che vuole davvero percorsi e non flussi. Ovviamente potrei scrivere temporaneamente i file su disco ma essendo pigro uso solo la funzione di setuptools.
letmaik,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.