基準のディレクトリが結合後に残らないのはなぜか
基準となるディレクトリに別のパスを結合するとき、先に渡したディレクトリが必ず残ると考えていないだろうか。Pythonのos.path.join()と、pathlibのパスオブジェクトでは、その前提は成立しない。途中に絶対パスがあると、それ以前の部分を無視するという規則がある。
たとえば、素材のos.path.join()の説明には、'/home/foo'に'bar'を結合する例と、'/home/bar'を結合する例が並んでいる。後者では、後から渡した絶対パスが結合の起点になる。引数の順番だけでなく、後続の引数が相対パスか絶対パスかを見る必要がある。
これはpathlibへ置き換えれば解消する違いでもない。pathlibのスラッシュ演算子にも、引数が絶対パスなら前のパスを無視すると明記されている。両者を選ぶ前に、「結合する」とは単に文字列を後ろへ足すことではない、と押さえておきたい。
公式は絶対パスとWindowsのルートをどう扱うか
os.path.join()の公式説明は、絶対パスを受け取った場合の扱いを次のように定めている。
If a segment is an absolute path (which on Windows requires both a drive and
a root), then all previous segments are ignored and joining continues from the
absolute path segment.
出典: https://docs.python.org/3/library/os.path.html#os.path.join (取得日 2026-10-15)
ここで注目したいのは、Windowsでは絶対パスにドライブとルートの両方が必要だという補足である。同じ説明には、ルート付きのパス要素が来てもドライブはリセットされないこと、異なるドライブの要素または絶対パスが来ると、それ以前の要素を無視してドライブをリセットすることも書かれている。
pathlibのOperators節も、スラッシュ演算子について同じ規則を示している。
If the argument is an absolute path, the previous path is ignored.
出典: https://docs.python.org/3/library/pathlib.html (取得日 2026-10-15)
さらに、Windowsのルート付き相対パスではドライブをリセットしないと説明している。素材にある次の式は、その扱いを示す公式例の入力部分である。実行結果ではなく、ドキュメントに掲載された式を引用する。
PureWindowsPath ( 'c:/Windows' , '/Program Files' )
この例について公式が示すのは、ドライブがc:のまま、ルートからのパスがProgram Filesになるという挙動だ。「先頭にスラッシュがあるか」だけで、ドライブを含む以前の指定がすべて捨てられると判断してはいけない。
パス結合の既定を比較する
比較すると、絶対パスによる置き換えと、Windowsのドライブ保持は共通している。一方、どのOSのパス規則を使うか、どの形で操作を記述するかには選択肢がある。
| 比較対象 | パス規則の選択 | 後続が絶対パスの場合 | Windowsのルート付き相対パス |
|---|---|---|---|
os.path.join() |
Pythonを実行しているOSに適した規則 | 以前の要素を無視して結合を続ける | ドライブはリセットしない |
ntpath.join() |
Windows形式を明示して扱う | 以前の要素を無視して結合を続ける | ドライブはリセットしない |
Pathの/演算子 |
実行環境のプラットフォーム | 前のパスを無視する | ドライブはリセットしない |
PureWindowsPathの構築・/演算子 |
Windows形式を明示して扱う | 以前のパスを無視する | ドライブはリセットしない |
os.pathの公式説明は、実行中のOSと異なる形式を操作したい場合、UNIX形式にはposixpath、Windows形式にはntpathを直接使えるとしている。これらのインターフェースは共通である。
pathlibでは、Pathが実行環境に合う具象パスを作る。それに対してPurePosixPathとPureWindowsPathは、ファイルシステムへアクセスしないパス操作のためのクラスである。公式には、これらの純粋パスクラスは実行中のシステムにかかわらず生成できるとある。手元のOSと、処理したいパスの形式を分けて考えられる点が選択の手がかりになる。
文字列を扱うか、パスオブジェクトを扱うか
文字列を使ったパス操作を選ぶなら、os.pathが候補になる。pathlibの公式説明も、文字列に対する低水準のパス操作にはos.pathを使えると案内している。既存の処理でパスをどのように受け取り、どのように渡しているかに合わせて選びたい。
パスをオブジェクトとして扱い、ファイルシステムへの操作も行うなら、Pathが候補になる。pathlibの公式説明は、クラスの選択に迷う場合にはPathが適している可能性が高いとし、純粋パスに加えて入出力操作を提供する具象パスとして説明している。
一方、Windows形式のパスをUnix上で組み立てたい場合や、OSへアクセスする操作を持たせずにパスだけを扱いたい場合には、純粋パスクラスが選択肢になる。ここでの違いは、絶対パスを後ろに渡したときの保護ではなく、扱うパスの形式と利用する操作である。/という書き方を選んでも、前のディレクトリを必ず残す規則には変わらない。
ドライブ相対パスと正規化を混同しない
Windowsでは、ドライブを指定しただけのパスにも注意が必要だ。os.path.join()の公式説明は、os.path.join("c:", "foo")がCドライブのカレントディレクトリに対する相対パスを表すと明記している。ドライブごとにカレントディレクトリがあるためであり、ドライブのルートを指定したパスとは区別される。
また、結合と正規化も同じ処理として扱わないほうがよい。os.path.normpath()の説明では、冗長な区切り文字や上位参照を畳み込むとしている。ただし、これは文字列操作であり、シンボリックリンクを含むパスの意味を変える場合があるとも記されている。
対して、pathlibの純粋パスでは、余分なスラッシュと単独のドットは畳み込まれるが、..と先頭の二重スラッシュは保持される。公式は、その理由としてシンボリックリンクやUNCパスなどで意味が変わることを挙げている。パスオブジェクトを作っただけで、上位参照まで解決されたと受け取るべきではない。
したがって、比較する際には「どこから結合が始まるか」と「どの表記が畳み込まれるか」を分けて確認したい。絶対パスの扱いが共通していても、正規化まで同じ処理になるという根拠にはならない。
自分の環境で確かめる手順
以下は読者が自分で実行するためのコマンドであり、実行結果は掲載していない。最初は実行環境のos.path、次はPOSIX形式を明示した純粋パス、最後はWindows形式を明示した両方のAPIを確認する。特に最後のコマンドでは、ルート付きの要素と、ドライブだけを指定した要素を分けて観察してほしい。
python -c "import os; print(os.path.join('/home/foo', 'bar')); print(os.path.join('/home/foo', '/home/bar'))"
python -c "from pathlib import PurePosixPath; print(PurePosixPath('/etc') / '/usr'); print(PurePosixPath('foo/../bar'))"
python -c "import ntpath; from pathlib import PureWindowsPath; print(ntpath.join('c:/Windows', '/Program Files')); print(PureWindowsPath('c:/Windows', '/Program Files')); print(ntpath.join('c:', 'foo'))"

コメント