web-dev-qa-db-fra.com

Comment joindre des composants d'un chemin lorsque vous créez une URL dans Python

Par exemple, je veux joindre un chemin de préfixe à des chemins de ressources comme /js/foo.js.

Je veux que le chemin résultant soit relatif à la racine du serveur. Dans l'exemple ci-dessus, si le préfixe était "media", je voudrais que le résultat soit /media/js/foo.js.

os.path.join le fait très bien, mais la façon dont il joint les chemins dépend du système d'exploitation. Dans ce cas, je sais que je cible le Web, pas le système de fichiers local.

Existe-t-il une meilleure alternative lorsque vous travaillez avec des chemins dont vous savez qu'ils seront utilisés dans les URL? Os.path.join fonctionnera-t-il assez bien? Dois-je simplement rouler le mien?

82
amjoconn

Puisque, d'après les commentaires postés par l'OP, il semble qu'il ne veut pas vouloir conserver les "URL absolues" dans la jointure (qui est l'un des emplois clés de urlparse.urljoin ;-) , Je recommanderais d'éviter cela. os.path.join Serait également mauvais, pour exactement la même raison.

Donc, j'utiliserais quelque chose comme '/'.join(s.strip('/') for s in pieces) (si le premier / Doit également être ignoré - si le premier morceau doit être dans un boîtier spécial, c'est également possible bien sûr ;-).

50
Alex Martelli

Python2

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

Mais attention,

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

ainsi que

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

Python3

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

La raison pour laquelle vous obtenez des résultats différents de /js/foo.js et js/foo.js est parce que le premier commence par une barre oblique qui signifie qu'il commence déjà à la racine du site Web.

130
Ben James

Comme tu dis, os.path.join joint les chemins en fonction du système d'exploitation actuel. posixpath est le module sous-jacent utilisé sur les systèmes posix sous l'espace de noms os.path:

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

Vous pouvez donc simplement importer et utiliser posixpath.join à la place pour les URL, qui est disponible et fonctionnera sur n'importe quelle plateforme .

Edit: @ La suggestion de Pete est bonne, vous pouvez alias l'importation pour une meilleure lisibilité

from posixpath import join as urljoin

Edit: Je pense que c'est plus clair, ou du moins m'a aidé à comprendre, si vous regardez la source de os.py (le code ici est de Python 2.7.11, plus j'ai coupé quelques bits). Il y a des importations conditionnelles dans os.py qui sélectionne le module de chemin d'accès à utiliser dans l'espace de noms os.path. Tous les modules sous-jacents (posixpath, ntpath, os2emxpath, riscospath) qui peuvent être importés dans os.py, alias path, existe et existe pour être utilisé sur tous les systèmes. os.py ne fait que choisir l'un des modules à utiliser dans l'espace de noms os.path au moment de l'exécution en fonction du système d'exploitation actuel.

# 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'
41
GP89

Cela fait bien le travail:

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))
25
Rune Kaagaard

La fonction basejoin du package rllib pourrait être ce que vous recherchez.

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

Edit: je ne l'avais pas remarqué auparavant, mais urllib.basejoin semble correspondre directement à urlparse.urljoin, ce qui rend ce dernier préféré.

9
mwcz

Utilisation de furl, pip install furl ce sera:

 furl.furl('/media/path/').add(path='js/foo.js')
7
Vasili Pascal

Je sais que c'est un peu plus que ce que l'OP demandait, mais j'avais les morceaux à l'url suivante, et je cherchais un moyen simple de les rejoindre:

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

Faire quelques recherches:

>>> 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'>

Donc, en plus du chemin d'accès qui a déjà été répondu dans les autres réponses, Pour obtenir ce que je cherchais, j'ai fait ce qui suit:

>>> 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'

Selon la documentation il faut EXACTEMENT un Tuple en 5 parties.

Avec le format Tuple suivant:

schéma 0 URL spécificateur de schéma chaîne vide

netloc 1 Network location part chaîne vide

chemin 2 Chaîne vide du chemin hiérarchique

requête 3 Chaîne vide du composant de requête

fragment 4 Identificateur de fragment chaîne vide

5
jmunsch

Pour améliorer légèrement la réponse d'Alex Martelli, ce qui suit non seulement nettoiera les barres obliques supplémentaires, mais préservera également les barres obliques de fin (qui se terminent), ce qui peut parfois être utile:

>>> 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/

Ce n'est pas aussi facile à lire et ne nettoiera pas plusieurs barres obliques supplémentaires.

3
Florent Thiery

Rune Kaagaard a fourni une excellente solution compacte qui a fonctionné pour moi, je l'ai développée un peu:

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

Cela permet à tous les arguments d'être joints indépendamment des barres obliques de fin et de fin tout en conservant la dernière barre oblique si elle est présente.

2
futuere