web-dev-qa-db-ja.com

PythonでURLを構築しているときにパスのコンポーネントを結合する方法

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

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

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

URLで使用されることがわかっているパスを使用している場合、最適な代替手段はありますか? os.path.joinは十分に機能しますか?自分で転がす必要がありますか?

82
amjoconn

OPが投稿したコメントから、彼はdoes n't結合で「絶対URL」を保持したいようです(これは_urlparse.urljoin_;-の主要な仕事の1つです)。それを避けることをお勧めします。 _os.path.join_も、まったく同じ理由で悪いでしょう。

したがって、私は'/'.join(s.strip('/') for s in pieces)のようなものを使用します(先頭の_/_も無視する必要がある場合-先頭の部分を特殊なケースにする必要がある場合は、もちろん実現可能です;-)。

50
Alex Martelli

Python2

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

ただし注意

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

同様に

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

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

130
Ben James

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

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

そのため、URLの代わりにposixpath.joinをインポートして使用することができます。これは利用可能であり、任意のプラットフォームで動作します

編集: @Peteの提案は良いものです。読みやすさを高めるためにインポートをエイリアスできます

from posixpath import join as urljoin

編集:os.py(ここのコードはPython 2.7からのソースを調べると、これがより明確になり、少なくとも理解に役立ったと思います。 11、プラス私はいくつかのビットをトリミングしました)。 os.pyには、名前空間os.pathで使用するパスモジュールを選択する条件付きインポートがあります。 posixpathとしてエイリアスされたos2emxpathにインポートできるすべての基礎モジュール(ntpathriscospathos.pypath)がありますすべてのシステムで使用するために存在します。 os.pyは、現在のOSに基づいて実行時に名前空間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'
41
GP89

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

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

rllibパッケージの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
mwcz

Furlを使用して、pip install furl そうなる:

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

私はこれが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'

documentation によれば、まさに5部のタプルが必要です。

次のタプル形式:

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

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

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

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

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

5
jmunsch

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
Florent Thiery

Rune Kaagaardは、私にとってうまく機能する素晴らしいコンパクトなソリューションを提供してくれたので、少し拡張しました。

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

これにより、末尾のスラッシュと終了のスラッシュに関係なく、最後のスラッシュを保持しながらすべての引数を結合できます。

2
futuere