PythonでURLを作成するときにパスのコンポーネントを結合する方法


103

たとえば、プレフィックスパスを/js/foo.jsのようなリソースパスに結合したいとします。

結果のパスをサーバーのルートからの相対パスにしたいと思います。上記の例で、プレフィックスが「media」の場合、結果は/media/js/foo.jsになります。

os.path.joinはこれをうまく実行しますが、パスの結合方法はOSに依存します。この場合、ローカルファイルシステムではなく、ウェブをターゲットにしていることがわかります。

URLで使用されることがわかっているパスを使用しているときに、最良の代替手段はありますか?os.path.joinは十分に機能しますか?自分で巻くだけですか?


1
os.path.join動作しないでしょう。しかし、/文字による単純な結合はすべての場合に機能するはずです- /仕様によるHTTPの標準パス区切り文字です。
intgr 2009年

回答:


60

OPが投稿したコメントから、彼結合で「絶対URL」を保持したくないようです(これは;-の主要なジョブの1つです)。これはurlparse.urljoin避けることをお勧めします。 os.path.joinまったく同じ理由で、それも悪いでしょう。

したがって、私は次のようなものを使用します'/'.join(s.strip('/') for s in pieces)(リーディング/も無視する必要がある場合-リーディングピースを特別なケースにする必要がある場合は、もちろんそれも可能です;-)。


1
ありがとう。2番目の部分の先頭の「/」がそこにないことをそれほど要求しなくても構いませんでしたが、最初の部分の末尾の「/」を要求すると、この使用例ではurljoinが何もしていないように感じます私のために。少なくともjoin( "/ media"、 "js / foo.js")とjoin( "/ media /"、 "js / foo.js")が機能することを望みます。正しい答えであると思われるものに感謝します。
amjoconn 2009年

'/'の除去と結合が何かしてくれるといいのですが。
statueofmike

いや、これは、Windows上で仕事に行くのではありませんos.path.join('http://media.com', 'content')返すwourd http://media.com\content
SeF

154

使用できますurllib.parse.urljoin

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

しかし注意してください

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

その理由は、あなたは異なる結果を得る/js/foo.jsと、js/foo.js前者はそれがすでにWebサイトのルートから始まることを意味しスラッシュで始まるためです。

Python 2では、

from urlparse import urljoin

/js/foo.jsの先頭の「/」からストリップを取り除いていますが、os.path.joinの場合もそうです。メディアの後にスラッシュを要求するということは、とにかく自分で仕事の大部分をしなければならないことを意味します。
amjoconn 2009年

具体的には、接頭辞が/で終わる必要があり、ターゲットパスを/で始めることができないことを確認したら、単に連結することもできます。この場合、urljoinが本当に役立つかどうかわかりませんか?
amjoconn 2009年

3
@MedhatGayed urljoin'/'を削除することは私には明らかではありません。urlparse.urljoin('/media/', '/js/foo.js')戻り値を '/js/foo.js'として呼び出すと、重複する「/」ではなく、すべてのメディアを削除しました。実際urlparse.urljoin('/media//', 'js/foo.js')には実際には「/media//js/foo.js」を返すため、重複したものは削除されません。
amjoconn 14

8
末尾のないコンポーネントを結合する場合、urljoinは奇妙な動作をします/最初のコンポーネントをベースにストリップしてから、他の引数を結合します。私が期待するものではありません。
ピート

7
残念ながらurljoin、URLを結合するためのものではありません。HTMLドキュメントなどで見られる相対URLを解決するためのもの
OrangeDog

46

あなたが言うようos.path.joinに、現在のOSに基づいてパスを結合します。posixpath名前空間の下のposixシステムで使用される基本モジュールですos.path

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

したがってposixpath.join、代わりにURLをインポートして使用することができます。URLは利用可能で、どのプラットフォームも機能します

編集: @ピートの提案は良いものです、読みやすくするためにインポートにエイリアスを付けることができます

from posixpath import join as urljoin

編集:のソースを見ると、これはより明確になったか、少なくとも理解に役立ったと思いますos.py(ここのコードはPython 2.7.11からのもので、さらにいくつかのビットをトリミングしました)。os.py名前空間で使用するパスモジュールを選択する条件付きインポートがありますos.path。すべての基礎となるモジュール(posixpathntpathos2emxpathriscospath)それが中にインポートすることができるos.py、ようにエイリアスpathがあるとすべてのシステムで使用されるように存在します。現在のOSに基づいて、実行時にos.py名前空間os.pathで使用するモジュールの1つを選択するだけです。

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

4
from posixpath import join as urljoinそれを読みやすいものにうまくエイリアスします。
ピート

29

これはうまく仕事をします:

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

9

urllibパッケージのbasejoin関数があなたが探しているものかもしれません。

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

編集:私は前に気づかなかったが、urllib.basejoinは直接urlparse.urljoinにマップするようで、後者が優先されます。


9

furlを使用pip install furlすると、次の ようになります。

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

1
あなたは結果が文字列になりたい場合は、追加することができます.url終わり:furl.furl('/media/path/').add(path='js/foo.js').url
エヤルレビン

furl

それは何をする方が良いfurl('/media/path/').add(path=furl('/js/foo.js').path).urlためfurl('/media/path/').add(path='/js/foo.js').urlである/media/path//js/foo.js
バルトロ-otrit

5

これはOPが要求するよりも少し多いことを知っていますが、次のURLへのピースがあり、それらに参加する簡単な方法を探していました。

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

周りを見回して:

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

したがって、他の回答ですでに回答されているパス結合に加えて、私が探していたものを取得するには、次のようにしました:

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

ドキュメントによると、5部のタプルが必要です。

次のタプル形式:

スキーム0 URLスキーム指定子の空の文字列

netloc 1ネットワークロケーションパーツの空の文字列

パス2階層パスの空の文字列

クエリ3クエリコンポーネントの空の文字列

フラグメント4フラグメント識別子の空の文字列


5

Rune Kaagaardは、私にとって効果的で優れたコンパクトなソリューションを提供しました。

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

これにより、最後のスラッシュが存在する場合は保持しながら、末尾および末尾のスラッシュに関係なく、すべての引数を結合できます。


あなたは同様に、リストの内包表記を使用して、その最後の行少し短く、よりPython的を行うことができますreturn "/".join([str(x).strip("/") for x in args]) + trailing_slash
ダン・コーツ

3

Alex Martelliの応答をわずかに改善するために、以下は余分なスラッシュをクリーンアップするだけでなく、末尾の(終了)スラッシュも保持します。

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

ただし、読み方は簡単ではなく、余分な複数のスラッシュがクリーンアップされません。


3

上記のすべての解決策で気に入らないことがわかったので、自分で解決策を思いつきました。このバージョンでは、パーツが単一のスラッシュで結合され、先頭と末尾のスラッシュだけが残されます。いいえpip installurllib.parse.urljoin変ではありません。

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

0

furlregexの使用(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'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.