Perché os.path.join () non funziona in questo caso?


325

Il codice seguente non si unirà, quando il debug il comando non memorizza l'intero percorso ma solo l'ultima voce.

os.path.join('/home/build/test/sandboxes/', todaystr, '/new_sandbox/')

Quando provo questo memorizza solo la /new_sandbox/parte del codice.

Risposte:


426

Le ultime stringhe non dovrebbero iniziare con una barra. Se iniziano con una barra, vengono considerati un "percorso assoluto" e tutto ciò che li precede viene scartato.

Citando i documenti Python peros.path.join :

Se un componente è un percorso assoluto, tutti i componenti precedenti vengono eliminati e l'unione continua dal componente percorso assoluto.

Nota su Windows, il comportamento in relazione alle lettere di unità, che sembra essere cambiato rispetto alle versioni precedenti di Python:

Su Windows, la lettera di unità non viene ripristinata quando r'\foo'viene rilevato un componente percorso assoluto (ad esempio, ). Se un componente contiene una lettera di unità, tutti i componenti precedenti vengono eliminati e la lettera di unità viene reimpostata. Si noti che poiché esiste una directory corrente per ogni unità, os.path.join("c:", "foo")rappresenta un percorso relativo alla directory corrente su drive C:( c:foo), non c:\foo.


85
-1: nessuna stringa deve includere un "/". Un intero punto di os.path.join è prevenire l'inserimento di barre nel percorso.
S.Lott

6
Il problema con str.join () è, ovviamente, che non eliminerà le doppie barre. Penso che questo sia lo scopo principale per le persone che usano os.path.join. es. '/'.join(['/etc/', '/ conf']) genera tre barre: '/ etc /// conf'
Dustin Rasener

17
@DustinRasener È possibile utilizzare os.path.normpathper raggiungere tale obiettivo.
Gareth Latty,

5
nessun indizio sul perché le persone siano frustrate dal comportamento os.path.join. In altre lingue, la libreria / metodo path-join equivalente si comporta esattamente allo stesso modo. È più sicuro e ha più senso.
Don Cheadle,

19
Questo è frustrante perché è magia implicita , contrariamente all'euristica cardinale di "Esplicito è meglio che implicito". E lo è . I progettisti di lingue possono credere di conoscerlo meglio, ma esistono ragioni ovvie e dimostrabilmente sicure per occasionalmente voler fare questo. Adesso non possiamo. Questo è il motivo per cui non possiamo avere cose buone.
Cecil Curry,

151

L'idea os.path.join()è di rendere il tuo programma multipiattaforma (linux / windows / etc).

Persino una barra la rovina.

Quindi ha senso solo quando viene utilizzato con una sorta di punto di riferimento come os.environ['HOME']o os.path.dirname(__file__).


75

os.path.join()può essere utilizzato insieme a os.path.sepper creare un percorso assoluto piuttosto che relativo.

os.path.join(os.path.sep, 'home','build','test','sandboxes',todaystr,'new_sandbox')

8
L'uso di os.path.sepcome primo elemento per costruire un percorso assoluto è meglio di qualsiasi altra risposta qui! Il punto centrale dell'uso os.pathpiuttosto che dei metodi str di base è evitare la scrittura /. Anche mettere ogni sottodirectory come nuovo argomento e rimuovere tutte le barre è fantastico. Probabilmente sarebbe una buona idea assicurarsi con un segno di spunta che todaystrnon inizia con una barra! ;)
snooze92,

3
Funziona anche su Windows (Python 2.7.6). Non si integrava con 'C: \' e si univa alle sottodirectory.
rickfoosusa,


21

Per capire perché questo comportamento sorprendente non è del tutto terribile, considera come argomento un'applicazione che accetta un nome di file di configurazione:

config_root = "/etc/myapp.conf/"
file_name = os.path.join(config_root, sys.argv[1])

Se l'applicazione viene eseguita con:

$ myapp foo.conf

/etc/myapp.conf/foo.confVerrà utilizzato il file di configurazione .

Ma considera cosa succede se l'applicazione viene chiamata con:

$ myapp /some/path/bar.conf

Quindi myapp dovrebbe utilizzare il file di configurazione in /some/path/bar.conf(e non /etc/myapp.conf/some/path/bar.confo simile).

Potrebbe non essere eccezionale, ma credo che questa sia la motivazione per il comportamento del percorso assoluto.


Grazie! Ho sempre odiato questo comportamento fino a quando non ho letto la tua risposta! È documentato in docs.python.org/3.5/library/os.path.html#os.path.join , ma non la motivazione per questo.
Eli_B,

Questo momento in cui hai bisogno esattamente della soluzione che molte persone ritengono terribile.
ashrasmun,

12

È perché '/new_sandbox/'inizia con a /e quindi si presume sia relativo alla directory principale. Rimuovi il comando /.


8

Per rendere la tua funzione più portatile, utilizzala come tale:

os.path.join(os.sep, 'home', 'build', 'test', 'sandboxes', todaystr, 'new_sandbox')

o

os.path.join(os.environ.get("HOME"), 'test', 'sandboxes', todaystr, 'new_sandbox')

8

Prova la combinazione di split("/")e *per stringhe con join esistenti.

import os

home = '/home/build/test/sandboxes/'
todaystr = '042118'
new = '/new_sandbox/'

os.path.join(*home.split("/"), todaystr, *new.split("/"))


Come funziona...

split("/") trasforma il percorso esistente in elenco: ['', 'home', 'build', 'test', 'sandboxes', '']

* davanti all'elenco si suddivide ogni elemento dell'elenco con il proprio parametro


3

Prova new_sandboxsolo con

os.path.join('/home/build/test/sandboxes/', todaystr, 'new_sandbox')

2

fallo in questo modo, senza troppe barre in più

root="/home"
os.path.join(root,"build","test","sandboxes",todaystr,"new_sandbox")

0

Si noti che un problema simile può mordere se si utilizza os.path.join()per includere un'estensione che include già un punto, che è ciò che accade automaticamente quando si utilizza os.path.splitext(). In questo esempio:

components = os.path.splitext(filename)
prefix = components[0]
extension = components[1]
return os.path.join("avatars", instance.username, prefix, extension)

Anche se extensionpotresti essere .jpgtu a finire con una cartella chiamata "foobar" piuttosto che un file chiamato "foobar.jpg". Per evitare ciò, è necessario aggiungere l'estensione separatamente:

return os.path.join("avatars", instance.username, prefix) + extension

0

è possibile stripil '/':

>>> os.path.join('/home/build/test/sandboxes/', todaystr, '/new_sandbox/'.strip('/'))
'/home/build/test/sandboxes/04122019/new_sandbox'

0

Consiglio di rimuovere la stringa dalla seconda e dalle stringhe seguenti os.path.sep, impedendo che vengano interpretate come percorsi assoluti:

first_path_str = '/home/build/test/sandboxes/'
original_other_path_to_append_ls = [todaystr, '/new_sandbox/']
other_path_to_append_ls = [
    i_path.strip(os.path.sep) for i_path in original_other_path_to_append_ls
]
output_path = os.path.join(first_path_str, *other_path_to_append_ls)

0
os.path.join("a", *"/b".split(os.sep))
'a/b'

una versione più completa:

import os

def join (p, f, sep = os.sep):
    f = os.path.normpath(f)
    if p == "":
        return (f);
    else:
        p = os.path.normpath(p)
        return (os.path.join(p, *f.split(os.sep)))

def test (p, f, sep = os.sep):
    print("os.path.join({}, {}) => {}".format(p, f, os.path.join(p, f)))
    print("        join({}, {}) => {}".format(p, f, join(p, f, sep)))

if __name__ == "__main__":
    # /a/b/c for all
    test("\\a\\b", "\\c", "\\") # optionally pass in the sep you are using locally
    test("/a/b", "/c", "/")
    test("/a/b", "c")
    test("/a/b/", "c")
    test("", "/c")
    test("", "c")

Cosa succede se os.sep è effettivamente "\"? Quindi il tuo primo esempio diventa os.path.join("a", *"/b".split("\\")), il che produce "/b"... Dubito che sia il risultato desiderato.
NichtJens,

1
Aggiornato - Suppongo che tu debba dare un suggerimento come il percorso che stai usando localmente indipendente da quello del sistema operativo su cui stai eseguendo
Neil McGill

1
Sì. In alternativa, si potrebbe dividere su entrambe le opzioni comunemente usate ... ma poi alcuni altri sistemi operativi potrebbero inventarne un terzo.
NichtJens,
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.