Pourquoi os.path.join () ne fonctionne-t-il pas dans ce cas?

325

Le code ci-dessous ne se joindra pas, lors du débogage, la commande ne stocke pas le chemin complet mais uniquement la dernière entrée.

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

Lorsque je teste cela, il ne stocke que la /new_sandbox/partie du code.

chrissygormley
la source

Réponses:

426

Ces dernières chaînes ne doivent pas commencer par une barre oblique. S'ils commencent par une barre oblique, ils sont alors considérés comme un "chemin absolu" et tout ce qui se trouve devant eux est rejeté.

Citant les documents Python pouros.path.join :

Si un composant est un chemin absolu, tous les composants précédents sont supprimés et la jointure continue à partir du composant de chemin absolu.

Remarque sur Windows, le comportement par rapport aux lettres de lecteur, qui semble avoir changé par rapport aux versions antérieures de Python:

Sous Windows, la lettre de lecteur n'est pas réinitialisée lorsqu'un composant de chemin absolu (par exemple, r'\foo') est rencontré. Si un composant contient une lettre de lecteur, tous les composants précédents sont jetés et la lettre de lecteur est réinitialisée. Notez qu'étant donné qu'il existe un répertoire en cours pour chaque lecteur, il os.path.join("c:", "foo")représente un chemin relatif au répertoire en cours sur le lecteur C:( c:foo), non c:\foo.

Craig McQueen
la source
85
-1: Aucune chaîne ne doit inclure de "/". Un point entier de os.path.join est d'empêcher de mettre des barres obliques dans le chemin.
S.Lott
6
Le problème avec str.join () est, bien sûr, qu'il n'éliminera pas les doubles barres obliques. Je pense que c'est le but principal pour les gens qui utilisent os.path.join. Par exemple, '/'.join(['/etc/', '/ conf']) entraîne trois barres obliques: '/ etc /// conf'
Dustin Rasener
17
@DustinRasener Vous pouvez utiliser os.path.normpathpour atteindre cet objectif.
Gareth Latty
5
aucune idée pourquoi les gens sont frustrés par le comportement os.path.join. Dans d'autres langues, la bibliothèque / méthode de jointure par chemin équivalent se comporte exactement de la même manière. C'est plus sûr et plus logique.
Don Cheadle
19
C'est frustrant car c'est de la magie implicite , contrairement à l' heuristique cardinale "Explicit vaut mieux qu'implicite". Et ça l' est . Les concepteurs de langage peuvent croire qu'ils savent mieux, mais il existe des raisons évidentes et manifestement sûres de vouloir parfois le faire. Maintenant, nous ne pouvons plus. C'est pourquoi nous ne pouvons pas avoir de bonnes choses.
Cecil Curry
151

L'idée os.path.join()est de rendre votre programme multi-plateforme (linux / windows / etc).

Même une barre oblique la ruine.

Cela n'a donc de sens que lorsqu'il est utilisé avec une sorte de point de référence comme os.environ['HOME']ou os.path.dirname(__file__).

Antony Hatchkins
la source
75

os.path.join()peut être utilisé conjointement avec os.path.seppour créer un chemin absolu plutôt que relatif.

os.path.join(os.path.sep, 'home','build','test','sandboxes',todaystr,'new_sandbox')
ghammond
la source
8
L'utilisation de os.path.sepcomme premier élément pour construire un chemin absolu est meilleure que toute autre réponse ici! L'intérêt d'utiliser os.pathdes méthodes str plutôt que de base est d'éviter d'écrire /. Placer chaque sous-répertoire en tant que nouvel argument et supprimer toutes les barres obliques est également excellent. Ce serait probablement une bonne idée de s'assurer avec un chèque qui todaystrne commence pas par une barre oblique! ;)
snooze92
3
Cela fonctionne également sur Windows (python 2.7.6). Il n'interfère pas avec 'C: \' et rejoint les sous-répertoires.
rickfoosusa
23

N'utilisez pas de barres obliques au début des composants de chemin, sauf lorsque vous vous référez au répertoire racine:

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

voir aussi: http://docs.python.org/library/os.path.html#os.path.join

miku
la source
21

Pour aider à comprendre pourquoi ce comportement surprenant n'est pas entièrement terrible, considérez une application qui accepte un nom de fichier de configuration comme argument:

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

Si l'application est exécutée avec:

$ myapp foo.conf

Le fichier de configuration /etc/myapp.conf/foo.confsera utilisé.

Mais considérez ce qui se passe si l'application est appelée avec:

$ myapp /some/path/bar.conf

Ensuite myapp , utilisez le fichier de configuration à /some/path/bar.conf(et non /etc/myapp.conf/some/path/bar.confou similaire).

Ce n'est peut-être pas génial, mais je crois que c'est la motivation pour le comportement absolu du chemin.

David Wolever
la source
Merci! J'avais toujours détesté ce comportement jusqu'à la lecture de votre réponse! C'est documenté dans docs.python.org/3.5/library/os.path.html#os.path.join , mais pas la motivation pour cela.
Eli_B
Ce moment où vous avez besoin exactement de la solution que beaucoup de gens jugent terrible.
ashrasmun
12

C'est parce que votre '/new_sandbox/'commence par un /et est donc supposé être relatif au répertoire racine. Retirez l'interligne /.

ambre
la source
8

Pour rendre votre fonction plus portable, utilisez-la comme telle:

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

ou

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

Essayez la combinaison de split("/")et *pour les chaînes avec des jointures existantes.

import os

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

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


Comment ça fonctionne...

split("/") transforme le chemin existant en liste: ['', 'home', 'build', 'test', 'sandboxes', '']

* devant la liste éclate chaque élément de la liste de son propre paramètre

openwonk
la source
3

Essayez avec new_sandboxseulement

os.path.join('/home/build/test/sandboxes/', todaystr, 'new_sandbox')
TU
la source
2

faites comme ça, sans trop de barres obliques

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

Notez qu'un problème similaire peut vous mordre si vous utilisez os.path.join()pour inclure une extension qui inclut déjà un point, ce qui se produit automatiquement lorsque vous utilisez os.path.splitext(). Dans cet exemple:

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

Même si extensionpeut - être .jpgvous vous retrouvez avec un dossier nommé « toto » plutôt que d' un fichier appelé « foobar.jpg ». Pour éviter cela, vous devez ajouter l'extension séparément:

return os.path.join("avatars", instance.username, prefix) + extension
shacker
la source
0

vous pouvez striple '/':

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

Je recommanderais de retirer de la deuxième chaîne et des chaînes suivantes la chaîne os.path.sep, en les empêchant d'être interprétés comme des chemins absolus:

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)
Igor Fobia
la source
0
os.path.join("a", *"/b".split(os.sep))
'a/b'

une version plus complète:

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")
Neil McGill
la source
Et si os.sep l'est réellement "\"? Alors votre premier exemple devient os.path.join("a", *"/b".split("\\")), ce qui donne "/b"... Je doute que ce soit le résultat escompté.
NichtJens
1
Mise à jour - Je suppose que vous devez donner un indice car le chemin sep que vous utilisez localement indépendant de celui de l'OS que vous utilisez
Neil McGill
1
Oui. Alternativement, on pourrait diviser les deux options couramment utilisées ... mais un autre système d'exploitation pourrait en proposer un troisième.
NichtJens