パスを文字列結合で作成するコードをよく見かけるが、これは複数の問題を引き起こす危険なアンチパターンだ。
文字列結合の問題点
# ❌ 危険なコード
directory = 'data'
filename = 'report.csv'
path = directory + '/' + filename # data/report.csv
一見動いているように見えるが、以下の問題がある。
Windows で動かない
Windows のパス区切り文字は \ であり、/ を使うと動作しないケースがある。
# Windows では以下のようなパスが必要
path = 'C:\\Users\\name\\data\\report.csv'
# しかし文字列結合で / を使うと
path = 'C:/Users/name' + '/' + 'data' # 混在して問題を起こすことがある
os.path.join() は実行環境に応じて適切な区切り文字を使う。
import os
path = os.path.join('C:\\Users\\name', 'data', 'report.csv')
# Windows: C:\Users\name\data\report.csv
# Unix: C:\Users\name/data/report.csv(そもそもこのパスは Unix では使わない)
末尾スラッシュの問題
ディレクトリ名の末尾にスラッシュがあるかどうかで結果が変わる。
# ❌ 末尾スラッシュがあると二重になる
directory = 'data/'
path = directory + '/' + 'file.txt' # data//file.txt
os.path.join() はこれを自動で処理する。
import os
path = os.path.join('data/', 'file.txt') # data/file.txt(正規化される)
絶対パスの上書き
os.path.join() は絶対パスを渡されると、それより前の引数を無視する。
import os
# 2番目の引数が絶対パスなので、1番目は無視される
path = os.path.join('/home/user', '/etc/passwd')
print(path) # /etc/passwd
これは仕様だが、ユーザー入力を受け取る場合はセキュリティリスクになる。
# ❌ ユーザーが絶対パスを入力すると意図しない場所を参照できる
user_input = '/etc/passwd'
path = os.path.join('/var/www/uploads', user_input) # /etc/passwd
対策として、入力値の検証が必要だ。
from pathlib import Path
def safe_join(base, user_path):
base = Path(base).resolve()
full = (base / user_path).resolve()
if not str(full).startswith(str(base)):
raise ValueError('Invalid path')
return full
pathlib を使う
pathlib の / 演算子は直感的で安全だ。
from pathlib import Path
base = Path('data')
path = base / 'subdir' / 'file.txt'
print(path) # data/subdir/file.txt(OS に応じた区切り文字)
まとめ
文字列結合
OS 依存、末尾スラッシュで壊れる、パスの正規化なし、セキュリティリスク
os.path.join / pathlib
OS 非依存、自動正規化、可読性が高い、安全
パスを扱う際は必ず os.path.join() か pathlib を使うべきだ。