Come fare riferimento a percorsi relativi di risorse quando si lavora con un repository di codice


188

Stiamo lavorando con un repository di codici che viene distribuito su Windows e Linux, a volte in directory diverse. Come dovrebbe uno dei moduli all'interno del progetto fare riferimento a una delle risorse non Python nel progetto (file CSV, ecc.)?

Se facciamo qualcosa del tipo:

thefile=open('test.csv')

o:

thefile=open('../somedirectory/test.csv')

Funzionerà solo quando lo script viene eseguito da una directory specifica o da un sottoinsieme delle directory.

Quello che vorrei fare è qualcosa del tipo:

path=getBasePathOfProject()+'/somedirectory/test.csv'
thefile=open(path)

È possibile?

Risposte:


255

Prova a utilizzare un nome file relativo al percorso dei file corrente. Esempio per './my_file':

fn = os.path.join(os.path.dirname(__file__), 'my_file')

In Python 3.4+ puoi anche usare pathlib :

fn = pathlib.Path(__file__).parent / 'my_file'

3
Penso che questa soluzione funzionerà solo se la risorsa si trova nella stessa directory del file Python o in una sua sottodirectory. Come risolverlo quando si ha la seguente struttura ad albero: / Project_Root_dir / python_files_dir / Alcuni altri sottodirigi qui py_file.py / resources / alcuni sottodadi qui resource_file.csv
olamundo

1
Siamo spiacenti, l'albero dei file è stato confuso su quell'ultimo messaggio ... secondo tentativo: hai il tuo file su /Project_Root_dir/python_files_dir/some_subdirs/py_file.py e hai il tuo file di risorse su /Project_Root_dir/resources/some_subdirs/resource_file.csv
olamundo,

28
Dovresti essere in grado di accedere alla directory principale usando join (pippo, '..'). Quindi, da / root / python_files / module / myfile, usa os.path.join (os.path.dirname ( __file__), '..', '..', 'resources')
c089,

7
os.pardirè leggermente migliore di '..', sebbene i due siano equivalenti sia su POSIX che su Windows.
davidchambers,

4
@cedbeu: è equivalente su ogni sistema in cui mi sia mai imbattuto e penso che ogni sistema python funzioni oggi (per favore correggimi se sbaglio qui). Tuttavia, se si prevede che Python venga trasferito su un sistema utilizzando un separatore di percorso diverso in futuro e si desidera che il codice sia pronto per esso, os.pardir sarà più portabile. Direi che ogni programmatore, anche uno che non legge mai alcun pitone, conosce il significato di "..", mentre "os.pardir" è un livello di riferimento indiretto che si dovrebbe cercare nella documentazione, quindi personalmente ' d attenersi a "..".
c089,

41

Se stai usando gli strumenti di installazione o distribuisci (un'installazione setup.py), il modo "giusto" per accedere a queste risorse impacchettate sembra usare package_resources.

Nel tuo caso l'esempio sarebbe

import pkg_resources
my_data = pkg_resources.resource_string(__name__, "foo.dat")

Che naturalmente legge la risorsa e i dati binari letti sarebbero il valore di my_data

Se hai solo bisogno del nome file puoi anche usare

resource_filename(package_or_requirement, resource_name)

Esempio:

resource_filename("MyPackage","foo.dat")

Il vantaggio è che è garantito per funzionare anche se si tratta di una distribuzione di archivio come un uovo.

Vedi http://packages.python.org/distribute/pkg_resources.html#resourcemanager-api


3
So che questa è una vecchia risposta, il mio modo preferito è (/ era forse?) Di usare pkg_resources, ma con la scomparsa delle uova zippate, c'è qualche danno nell'usare __file__come ai bei vecchi tempi?
Pykler

1
Questo è un approccio solido. Anche se la convenzione dell'uovo sta scomparendo, setuptools non lo è e molti stanno ancora installando deps contro repository git in cui l'uovo è costruito in fase di esecuzione
deepelement

18

In Python, i percorsi sono relativi alla directory di lavoro corrente , che nella maggior parte dei casi è la directory da cui si esegue il programma. La directory di lavoro corrente è molto probabile che non come stessi la directory del file del modulo, in modo da utilizzare un percorso relativo al file di modulo corrente è sempre una cattiva scelta.

L'uso del percorso assoluto dovrebbe essere la soluzione migliore:

import os
package_dir = os.path.dirname(os.path.abspath(__file__))
thefile = os.path.join(package_dir,'test.cvs')

15

Uso spesso qualcosa di simile a questo:

import os
DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'datadir'))

# if you have more paths to set, you might want to shorten this as
here = lambda x: os.path.abspath(os.path.join(os.path.dirname(__file__), x))
DATA_DIR = here('datadir') 

pathjoin = os.path.join
# ...
# later in script
for fn in os.listdir(DATA_DIR):
    f = open(pathjoin(DATA_DIR, fn))
    # ...

La variabile

__file__

contiene il nome del file dello script in cui scrivi quel codice, quindi puoi creare percorsi relativi allo script, ma ancora scritti con percorsi assoluti. Funziona abbastanza bene per diversi motivi:

  • il percorso è assoluto, ma comunque relativo
  • il progetto può ancora essere distribuito in un relativo contenitore

Ma devi cercare la compatibilità della piattaforma: os.pathsep di Windows è diverso da UNIX.


5
import os
cwd = os.getcwd()
path = os.path.join(cwd, "my_file")
f = open(path)

Cerchi anche di normalizzare il tuo cwdutilizzo os.path.abspath(os.getcwd()). Maggiori informazioni qui .


3
pochissimi casi d'uso in cui cwdè il percorso di un modulo, però
cedbeu

non funziona all'interno di un pacchetto, solo dalla stessa dir (o dalla directory di lavoro) impostata dallo script.
alexandra,

Questo non funzionerà se l'utente esegue il programma usando il percorso assoluto da un'altra directory. es. python3 /usr/someone/test.py
sgrpwr

2

È possibile utilizzare la __file__variabile build in . Contiene il percorso del file corrente. Vorrei implementare getBaseOfProject in un modulo nella radice del tuo progetto. Lì avrei preso parte al percorso __file__e lo avrei restituito. Questo metodo può quindi essere utilizzato ovunque nel progetto.


0

Sono rimasto un po 'perplesso qui. Volevo impacchettare alcuni file di risorse in un file wheel e accedervi. La confezione ha utilizzato il file manifest, ma pip install non lo stava installando a meno che non fosse una sottodirectory. Sperando che questi colpi di scena possano aiutare

├── cnn_client
   ├── image_preprocessor.py
   ├── __init__.py
   ├── resources
      ├── mscoco_complete_label_map.pbtxt
      ├── retinanet_complete_label_map.pbtxt
      └── retinanet_label_map.py
   ├── tf_client.py

MANIFEST.in

recursive-include cnn_client/resources *

Creato un weel usando setup.py standard. pip ha installato il file della ruota. Dopo l'installazione verificato se le risorse sono installate. Loro sono

ls /usr/local/lib/python2.7/dist-packages/cnn_client/resources

mscoco_complete_label_map.pbtxt
retinanet_complete_label_map.pbtxt 
 retinanet_label_map.py  

In tfclient.py per accedere a questi file. a partire dal

templates_dir = os.path.join(os.path.dirname(__file__), 'resources')
 file_path = os.path.join(templates_dir, \
            'mscoco_complete_label_map.pbtxt')
        s = open(file_path, 'r').read()

E funziona


-5

Ho trascorso molto tempo a capire la risposta a questo, ma alla fine l'ho ottenuta (ed è davvero molto semplice):

import sys
import os
sys.path.append(os.getcwd() + '/your/subfolder/of/choice')

# now import whatever other modules you want, both the standard ones,
# as the ones supplied in your subfolders

In questo modo il percorso relativo della tua sottocartella verrà aggiunto alle directory in cui Python può apparire. È piuttosto veloce e sporco, ma funziona come un fascino :)


6
Funzionerà solo se stai eseguendo il programma Python dalla stessa directory del file .py in questione. E in quel caso, potresti fare open('your/subfolder/of/choice')comunque.
Paul Fisher,

4
e l'OP ha menzionato che il codice deve funzionare su Windows e Linux. Questo no.
user183037
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.