あしあと

自分自身のログ(足跡)となります。ソフトウェアエンジニアです。ブログはテック系の内容が少し多めです。

Pythonのos.path.joinの仕様にちょっとハマった

Pythonでパスを結合しようと思って、こんな感じでコードを書いていたのですが、どうにも思った通りに動かない…

Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from os.path import join
>>> a = 'a'
>>> b = '/b'
>>> print(join(a, b))
/b

なんでaは結合されないのだろー、と悩みました。

で、実際にPythonのos.py(厳密にはos.pathはposixpath.py)を見ると、以下のようなコードでした。 (必要な箇所のみ抜粋)

import os

def get_sep(path):
    if isinstance(path, bytes):
        return b'/'
    else:
        return '/'

def join(a, *p):
    """Join two or more pathname components, inserting '/' as needed.
    If any component is an absolute path, all previous path components
    will be discarded.  An empty last part will result in a path that
    ends with a separator."""
    a = os.fspath(a)
    sep = get_sep(a)
    path = a
    try:
        if not p:
            path[:0] + sep  #23780: Ensure compatible data type even if p is null.
        for b in map(os.fspath, p):
            if b.startswith(sep):
                path = b
            elif not path or path.endswith(sep):
                path += b
            else:
                path += sep + b
    except (TypeError, AttributeError, BytesWarning):
        genericpath._check_arg_types('join', a, *p)
        raise
    return path

細かく読むと if b.startswith(sep)で、セパレータ(posixでは/)で始まっていると先頭は無視する模様…. 確かにdocstringにも書いているが、この仕様はなんなのだろうか...

ちなみにPythonのドキュメントにもちゃんと書いていましたね。

11.2. os.path — 共通のパス名操作 — Python 3.6.5 ドキュメント

そして、Pythonをもう数年書いているのに知らない自分もちょっと情けないですが...