So verbinden Sie Komponenten eines Pfads, wenn Sie eine URL in Python erstellen

103

Zum Beispiel möchte ich Ressourcenpfaden wie /js/foo.js einen Präfixpfad hinzufügen.

Ich möchte, dass der resultierende Pfad relativ zum Stammverzeichnis des Servers ist. Wenn das Präfix im obigen Beispiel "media" wäre, möchte ich, dass das Ergebnis /media/js/foo.js ist.

os.path.join macht das wirklich gut, aber wie es Pfade verbindet, hängt vom Betriebssystem ab. In diesem Fall weiß ich, dass ich auf das Web abziele, nicht auf das lokale Dateisystem.

Gibt es eine beste Alternative, wenn Sie mit Pfaden arbeiten, von denen Sie wissen, dass sie in URLs verwendet werden? Funktioniert os.path.join gut genug? Soll ich nur meine eigenen rollen?

Amjoconn
quelle
1
os.path.joinwird nicht funktionieren. Das einfache Verbinden durch das /Zeichen sollte jedoch in allen Fällen funktionieren - /ist das Standardpfadtrennzeichen in HTTP gemäß der Spezifikation.
Intgr

Antworten:

60

Da aus den Kommentaren des OP hervorgeht, dass er anscheinend keine "absoluten URLs" im Join beibehalten möchte (was eine der Schlüsselaufgaben von urlparse.urljoin;-) ist, würde ich empfehlen, dies zu vermeiden. os.path.joinwäre auch schlecht, aus genau dem gleichen Grund.

Also würde ich so etwas verwenden '/'.join(s.strip('/') for s in pieces)(wenn die Führung /auch ignoriert werden muss - wenn die Hauptrolle in einem speziellen Gehäuse sein muss, ist das natürlich auch machbar ;-).

Alex Martelli
quelle
1
Vielen Dank. Es machte mir nicht so viel aus, zu verlangen, dass das führende '/' im zweiten Teil nicht vorhanden sein konnte, aber wenn ich das nachfolgende '/' im ersten Teil verlange, habe ich das Gefühl, dass in diesem Anwendungsfall urljoin nichts tut für mich. Ich möchte mindestens beitreten ("/ media", "js / foo.js") und beitreten ("/ media /", "js / foo.js"), um zu arbeiten. Vielen Dank für die scheinbar richtige Antwort: Rollen Sie Ihre eigenen.
Amjoconn
Ich hoffte, dass etwas das '/' Strippen und Beitreten für mich tun würde.
Statue von Mike
Nein, das wird unter Windows nicht funktionieren, wo os.path.join('http://media.com', 'content') Wourd zurückkehrt http://media.com\content.
18.
154

Sie können verwenden urllib.parse.urljoin:

>>> from urllib.parse import urljoin
>>> urljoin('/media/path/', 'js/foo.js')
'/media/path/js/foo.js'

Aber Vorsicht :

>>> urljoin('/media/path', 'js/foo.js')
'/media/js/foo.js'
>>> urljoin('/media/path', '/js/foo.js')
'/js/foo.js'

Der Grund , warum Sie erhalten unterschiedliche Ergebnisse aus /js/foo.jsund js/foo.jsist , da erstere mit einem Schrägstrich beginnt , das bedeutet , dass es beginnt bereits auf der Website root.

Auf Python 2 müssen Sie tun

from urlparse import urljoin
Ben James
quelle
Ich habe also den Streifen vom führenden "/" auf /js/foo.js entfernt, aber es scheint, dass dies auch bei os.path.join der Fall wäre. Wenn ich den Schrägstrich nach den Medien benötige, muss ich den größten Teil der Arbeit sowieso selbst erledigen.
Amjoconn
Insbesondere wenn ich habe, dass das Präfix mit / enden muss und der Zielpfad nicht mit / beginnen kann, kann ich es genauso gut einfach verketten. In diesem Fall bin ich mir nicht sicher, ob urljoin wirklich hilft?
Amjoconn
3
@MedhatGayed Mir ist nicht klar, wer urljoinjemals '/' entfernt. Wenn ich es mit urlparse.urljoin('/media/', '/js/foo.js')dem zurückgegebenen Wert aufrufe, ist es '/js/foo.js'. Es wurden alle Medien entfernt, nicht das Duplikat '/'. Tatsächlich wird urlparse.urljoin('/media//', 'js/foo.js')"/media//js/foo.js" zurückgegeben, sodass keine Duplikate entfernt werden.
Amjoconn
8
urljoin hat ein seltsames Verhalten, wenn Sie Komponenten verbinden, die nicht mit / enden. Es entfernt die erste Komponente von ihrer Basis und verbindet dann die anderen Argumente mit. Nicht das, was ich erwarten würde.
Pete
7
Ist leider urljoinnicht zum Beitreten von URLs. Es dient zum Auflösen relativer URLs, wie sie in HTML-Dokumenten usw. zu finden sind
OrangeDog
46

Wie Sie sagen, os.path.joinverbindet Pfade basierend auf dem aktuellen Betriebssystem. posixpathist das zugrunde liegende Modul, das auf Posix-Systemen unter dem Namespace verwendet wird os.path:

>>> os.path.join is posixpath.join
True
>>> posixpath.join('/media/', 'js/foo.js')
'/media/js/foo.js'

Sie können also einfach posixpath.joinURLs importieren und verwenden , die verfügbar sind und auf jeder Plattform funktionieren .

Bearbeiten: @ Petes Vorschlag ist gut, Sie können den Import für eine bessere Lesbarkeit aliasen

from posixpath import join as urljoin

Bearbeiten: Ich denke, dies wird klarer gemacht oder hat mir zumindest geholfen zu verstehen, wenn Sie sich die Quelle von ansehen os.py(der Code hier stammt aus Python 2.7.11, und ich habe einige Bits gekürzt). Es gibt bedingte Importe os.py, die auswählen, welches Pfadmodul im Namespace verwendet werden soll os.path. Alle die zugrunde liegende Module ( posixpath, ntpath, os2emxpath, riscospath) , die in eingeführt werden kann os.py, der Alias pathgibt es und existiert auf allen Systemen verwendet werden. os.pywählt nur eines der Module aus, die os.pathzur Laufzeit im Namespace verwendet werden sollen, basierend auf dem aktuellen Betriebssystem.

# os.py
import sys, errno

_names = sys.builtin_module_names

if 'posix' in _names:
    # ...
    from posix import *
    # ...
    import posixpath as path
    # ...

elif 'nt' in _names:
    # ...
    from nt import *
    # ...
    import ntpath as path
    # ...

elif 'os2' in _names:
    # ...
    from os2 import *
    # ...
    if sys.version.find('EMX GCC') == -1:
        import ntpath as path
    else:
        import os2emxpath as path
        from _emx_link import link
    # ...

elif 'ce' in _names:
    # ...
    from ce import *
    # ...
    # We can use the standard Windows path.
    import ntpath as path

elif 'riscos' in _names:
    # ...
    from riscos import *
    # ...
    import riscospath as path
    # ...

else:
    raise ImportError, 'no os specific module found'
GP89
quelle
4
from posixpath import join as urljoinschön alias es zu etwas leicht zu lesen.
Pete
29

Das macht den Job gut:

def urljoin(*args):
    """
    Joins given arguments into an url. Trailing but not leading slashes are
    stripped for each argument.
    """

    return "/".join(map(lambda x: str(x).rstrip('/'), args))
Rune Kaagaard
quelle
9

Die basejoin- Funktion im urllib- Paket könnte genau das sein, wonach Sie suchen.

basejoin = urljoin(base, url, allow_fragments=True)
    Join a base URL and a possibly relative URL to form an absolute
    interpretation of the latter.

Bearbeiten: Ich habe es vorher nicht bemerkt, aber urllib.basejoin scheint direkt auf urlparse.urljoin abzubilden, was letzteres bevorzugt.

mwcz
quelle
9

Mit furl wird pip install furles sein:

 furl.furl('/media/path/').add(path='js/foo.js')
Vasili Pascal
quelle
1
Wenn Sie möchten, dass das Ergebnis eine Zeichenfolge ist, können Sie .urlam Ende hinzufügen :furl.furl('/media/path/').add(path='js/foo.js').url
Eyal Levin
furl funktioniert besser beim Verbinden von URLs als urlparse.urljoin in Python 2 atleast (y)
Ciasto piekarz
Es ist besser zu tun, furl('/media/path/').add(path=furl('/js/foo.js').path).urlweil furl('/media/path/').add(path='/js/foo.js').urlist/media/path//js/foo.js
bartolo-otrit
5

Ich weiß, dass dies etwas mehr ist, als das OP verlangt hat. Ich hatte jedoch die Teile unter der folgenden URL und suchte nach einer einfachen Möglichkeit, sie zu verbinden:

>>> url = 'https://api.foo.com/orders/bartag?spamStatus=awaiting_spam&page=1&pageSize=250'

Schauen Sie sich um:

>>> split = urlparse.urlsplit(url)
>>> split
SplitResult(scheme='https', netloc='api.foo.com', path='/orders/bartag', query='spamStatus=awaiting_spam&page=1&pageSize=250', fragment='')
>>> type(split)
<class 'urlparse.SplitResult'>
>>> dir(split)
['__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_asdict', '_fields', '_make', '_replace', 'count', 'fragment', 'geturl', 'hostname', 'index', 'netloc', 'password', 'path', 'port', 'query', 'scheme', 'username']
>>> split[0]
'https'
>>> split = (split[:])
>>> type(split)
<type 'tuple'>

Zusätzlich zu dem Pfad, der bereits in den anderen Antworten beantwortet wurde, habe ich Folgendes getan , um das zu erhalten, wonach ich gesucht habe:

>>> split
('https', 'api.foo.com', '/orders/bartag', 'spamStatus=awaiting_spam&page=1&pageSize=250', '')
>>> unsplit = urlparse.urlunsplit(split)
>>> unsplit
'https://api.foo.com/orders/bartag?spamStatus=awaiting_spam&page=1&pageSize=250'

Laut Dokumentation dauert es genau ein 5-teiliges Tupel.

Mit folgendem Tupelformat:

Schema 0 URL-Schema-Spezifizierer leere Zeichenfolge

netloc 1 Netzwerkstandortteil leere Zeichenfolge

Pfad 2 Hierarchischer Pfad leere Zeichenfolge

Abfrage 3 Leere Zeichenfolge der Abfragekomponente

Fragment 4 Fragmentkennung leere Zeichenfolge

jmunsch
quelle
5

Rune Kaagaard bot eine großartige und kompakte Lösung, die für mich funktionierte. Ich habe sie ein wenig erweitert:

def urljoin(*args):
    trailing_slash = '/' if args[-1].endswith('/') else ''
    return "/".join(map(lambda x: str(x).strip('/'), args)) + trailing_slash

Auf diese Weise können alle Argumente unabhängig von abschließenden und endenden Schrägstrichen verknüpft werden, wobei der letzte Schrägstrich beibehalten wird, falls vorhanden.

Zukunft
quelle
Sie können diese letzte Zeile etwas kürzer und pythonischer gestalten, indem Sie ein Listenverständnis verwenden, wie:return "/".join([str(x).strip("/") for x in args]) + trailing_slash
Dan Coates
3

Um die Reaktion von Alex Martelli etwas zu verbessern, werden im Folgenden nicht nur zusätzliche Schrägstriche bereinigt, sondern auch nachfolgende (End-) Schrägstriche beibehalten, was manchmal nützlich sein kann:

>>> items = ["http://www.website.com", "/api", "v2/"]
>>> url = "/".join([(u.strip("/") if index + 1 < len(items) else u.lstrip("/")) for index, u in enumerate(items)])
>>> print(url)
http://www.website.com/api/v2/

Es ist jedoch nicht so einfach zu lesen und bereinigt nicht mehrere zusätzliche nachgestellte Schrägstriche.

Florent Thiery
quelle
3

Ich fand Dinge, die mir an all den oben genannten Lösungen nicht gefallen, und fand meine eigenen. Diese Version stellt sicher, dass Teile mit einem einzigen Schrägstrich verbunden werden und führende und nachfolgende Schrägstriche allein bleiben. Nein pip install, keine urllib.parse.urljoinVerrücktheit.

In [1]: from functools import reduce

In [2]: def join_slash(a, b):
   ...:     return a.rstrip('/') + '/' + b.lstrip('/')
   ...:

In [3]: def urljoin(*args):
   ...:     return reduce(join_slash, args) if args else ''
   ...:

In [4]: parts = ['https://foo-bar.quux.net', '/foo', 'bar', '/bat/', '/quux/']

In [5]: urljoin(*parts)
Out[5]: 'https://foo-bar.quux.net/foo/bar/bat/quux/'

In [6]: urljoin('https://quux.com/', '/path', 'to/file///', '//here/')
Out[6]: 'https://quux.com/path/to/file/here/'

In [7]: urljoin()
Out[7]: ''

In [8]: urljoin('//','beware', 'of/this///')
Out[8]: '/beware/of/this///'

In [9]: urljoin('/leading', 'and/', '/trailing/', 'slash/')
Out[9]: '/leading/and/trailing/slash/'
cbare
quelle
0

Mit Furl und Regex (Python 3)

>>> import re
>>> import furl
>>> p = re.compile(r'(\/)+')
>>> url = furl.furl('/media/path').add(path='/js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media/path').add(path='js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media/path/').add(path='js/foo.js').url
>>> url
'/media/path/js/foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
>>> url = furl.furl('/media///path///').add(path='//js///foo.js').url
>>> url
'/media///path/////js///foo.js'
>>> p.sub(r"\1", url)
'/media/path/js/foo.js'
Guillaume Cisco
quelle