Python のファイルパス操作でやりがちなアンチパターン

ファイルパス操作には多くの落とし穴がある。ここでは実務でよく見かけるアンチパターンとその改善方法を紹介する。

文字列結合でパスを作る

最もよく見かけるアンチパターンは、+ や f-string でパスを結合することだ。

# ❌ アンチパターン
path = 'data/' + filename
path = f'{directory}/{filename}'
path = directory + '\\' + filename

この方法には複数の問題がある。

OS によってパス区切り文字が異なる(Windows は \、Unix 系は /
末尾のスラッシュの有無で結果が変わる
パスの正規化が行われない

正しくは os.path.join()pathlib を使う。

# ✅ 正しい方法
import os
from pathlib import Path

path = os.path.join(directory, filename)
path = Path(directory) / filename

ハードコードされたパス区切り文字

スラッシュやバックスラッシュを直接書くと、異なる OS で動かない。

# ❌ アンチパターン
config_path = 'config\\settings.ini'  # Windows でしか動かない
log_path = '/var/log/app.log'  # Unix 系でしか動かない

クロスプラットフォームで動かすには os.seppathlib を使う。

# ✅ 正しい方法
from pathlib import Path

config_path = Path('config') / 'settings.ini'

カレントディレクトリ依存のコード

相対パスを使うと、スクリプトの実行場所によって動作が変わる。

# ❌ アンチパターン
with open('config.json') as f:  # どこから実行するかで結果が変わる
    config = json.load(f)

スクリプトの場所を基準にするには __file__ を使う。

# ✅ 正しい方法
from pathlib import Path

script_dir = Path(__file__).resolve().parent
config_path = script_dir / 'config.json'

with open(config_path) as f:
    config = json.load(f)

存在確認と操作の間の競合

ファイルの存在を確認してから操作するまでの間に、状態が変わる可能性がある。

# ❌ アンチパターン(TOCTOU 競合)
if os.path.exists(filepath):
    os.remove(filepath)  # 確認後に別プロセスが削除していたらエラー

例外処理で対応するか、アトミックな操作を使う。

# ✅ 正しい方法
try:
    os.remove(filepath)
except FileNotFoundError:
    pass

# または Python 3.8 以降
from pathlib import Path
Path(filepath).unlink(missing_ok=True)

パスの比較を文字列で行う

パスの比較を文字列として行うと、同じファイルを指していても異なると判定されることがある。

# ❌ アンチパターン
path1 = '/home/user/../user/data.txt'
path2 = '/home/user/data.txt'
print(path1 == path2)  # False(同じファイルなのに)

パスを正規化してから比較する。

# ✅ 正しい方法
from pathlib import Path

path1 = Path('/home/user/../user/data.txt').resolve()
path2 = Path('/home/user/data.txt').resolve()
print(path1 == path2)  # True

ユーザー入力をそのまま使う

ユーザーからのパス入力をそのまま使うと、ディレクトリトラバーサル攻撃を受ける可能性がある。

# ❌ アンチパターン
filename = request.args.get('file')
path = os.path.join('/var/www/uploads', filename)
# filename が '../../../etc/passwd' だと危険

パスを正規化して、想定ディレクトリ内に収まっているか確認する。

# ✅ 正しい方法
from pathlib import Path

base_dir = Path('/var/www/uploads').resolve()
requested = (base_dir / filename).resolve()

if not str(requested).startswith(str(base_dir)):
    raise ValueError('不正なパス')