日々のスクリプトや自動化では、ファイルやディレクトリの存在確認、一覧取得、削除の3つが基礎になります。
本記事ではPython初心者の方に向けて、標準ライブラリのみでできる安全で実用的な方法を体系的に解説します。
os系とpathlib系の両アプローチを比較しながら、例外処理や誤削除防止のコツまで丁寧に説明します。
ファイル/ディレクトリの存在確認
存在確認の基本は「何を知りたいか」で使い分けることです。
ファイルがあるか、ディレクトリかどうか、シンボリックリンクをどう扱うかで適切な関数が変わります。
初心者の方は、まずはos.path
とpathlib.Path
の両方での書き方を覚えるとよいです。
os.path.exists/isfile/isdirの使い分け
os.path
は従来からある手堅いAPIです。
違いをまとめると次のようになります。
関数 | 返り値 | 主な用途 | 注意点 |
---|---|---|---|
os.path.exists(path) | True/False | パスが存在するか(ファイル/ディレクトリ/リンク含む) | 壊れたシンボリックリンクはFalseになります(lexistsならTrue) |
os.path.isfile(path) | True/False | 通常ファイルかどうかを判定 | ディレクトリや壊れたリンクはFalse |
os.path.isdir(path) | True/False | ディレクトリかどうかを判定 | ファイルや壊れたリンクはFalse |
壊れたシンボリックリンクも存在として扱いたい場合はos.path.lexists(path)
を使います。
実例(一時ディレクトリで安全に検証)
# exists, isfile, isdir の違いを確認するデモ
# 一時ディレクトリ内でファイルとサブディレクトリを作成して判定します
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
file_path = os.path.join(tmp, "data.txt")
dir_path = os.path.join(tmp, "subdir")
# テスト用ファイルとディレクトリを作成
with open(file_path, "w", encoding="utf-8") as f:
f.write("hello")
os.mkdir(dir_path)
print("tmp =", tmp)
print("exists(file):", os.path.exists(file_path)) # True
print("isfile(file):", os.path.isfile(file_path)) # True
print("isdir(file):", os.path.isdir(file_path)) # False
print("exists(dir):", os.path.exists(dir_path)) # True
print("isfile(dir):", os.path.isfile(dir_path)) # False
print("isdir(dir):", os.path.isdir(dir_path)) # True
# 存在しないパス
missing = os.path.join(tmp, "missing.txt")
print("exists(missing):", os.path.exists(missing)) # False
tmp = /tmp/tmpabcd1234
exists(file): True
isfile(file): True
isdir(file): False
exists(dir): True
isfile(dir): False
isdir(dir): True
exists(missing): False
pathlib.Path.exists/is_file/is_dirの使い分け
pathlib
はパスをオブジェクトとして扱えるモダンAPIです。
可読性が高く、パス結合や走査が直感的なので積極的に使うことをおすすめします。
メソッド | 返り値 | 主な用途 | 注意点 |
---|---|---|---|
Path.exists() | True/False | パスが存在するか | 壊れたシンボリックリンクはFalse |
Path.is_file() | True/False | 通常ファイルかどうか | ディレクトリはFalse |
Path.is_dir() | True/False | ディレクトリかどうか | ファイルはFalse |
# pathlib 版の存在確認デモ
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp)
p_file = base / "note.txt"
p_dir = base / "docs"
p_file.write_text("memo", encoding="utf-8")
p_dir.mkdir()
print("base =", base)
print("exists(file):", p_file.exists()) # True
print("is_file(file):", p_file.is_file()) # True
print("is_dir(file):", p_file.is_dir()) # False
print("exists(dir):", p_dir.exists()) # True
print("is_file(dir):", p_dir.is_file()) # False
print("is_dir(dir):", p_dir.is_dir()) # True
p_missing = base / "none.txt"
print("exists(missing):", p_missing.exists()) # False
base = /tmp/tmpwxyz5678
exists(file): True
is_file(file): True
is_dir(file): False
exists(dir): True
is_file(dir): False
is_dir(dir): True
exists(missing): False
例外(FileNotFoundError)との違いと注意
「存在確認してから操作」には競合状態(TOCTOU)の落とし穴があります。
確認した直後に別プロセスが削除し、操作時にFileNotFoundError
になることがあるためです。
実運用では事前チェックより「やってみて例外を捕まえる」方式が堅牢です。
# よくあるNGパターン: 先にexistsで確認してから開く
# 直後に別プロセスが削除した場合、openで FileNotFoundError になる可能性があります
from pathlib import Path
p = Path("maybe_deleted.txt")
if p.exists(): # ここでTrueでも、この直後に削除され得る
try:
with p.open("r", encoding="utf-8") as f:
data = f.read()
except FileNotFoundError:
print("消えていました") # 競合に弱い
# 推奨パターン: 例外処理で包む(競合に強い)
from pathlib import Path
p = Path("maybe_deleted.txt")
try:
with p.open("r", encoding="utf-8") as f:
data = f.read()
except FileNotFoundError:
print("そもそも存在しない/直前に削除されたため開けませんでした")
存在確認は「UI表示や分岐のための目安」に留め、実操作は例外処理で守るのが安全です。
ファイル/ディレクトリの一覧取得
一覧取得は用途に応じて使い分けます。
名前だけほしいならos.listdir
、属性も含め効率よく取得したいならos.scandir
、直感的なパス操作をしたいならpathlib.Path.iterdir
、パターンで探すならglob/rglob
が基本です。
os.listdirで一覧取得(名前のみ)
os.listdir(path)
は指定ディレクトリ直下の「名前」だけを返します。
サブディレクトリの中身は含みません。
# os.listdir の基本。直下のエントリ名のみを取得します。
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
open(os.path.join(tmp, "a.txt"), "w").close()
os.mkdir(os.path.join(tmp, "images"))
open(os.path.join(tmp, "b.log"), "w").close()
print("listdir:", os.listdir(tmp)) # 並び順は環境依存
listdir: ['a.txt', 'images', 'b.log']
os.scandirで効率的に取得(属性付き)
os.scandir(path)
はDirEntry
オブジェクトを返します。
型判定(is_file()
, is_dir()
)やサイズ(stat()
)を必要に応じて遅延取得できるため、大量の項目を扱う場合に効率的です。
# os.scandir で名前・種別・サイズを表示する例
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
# サンプルデータ
with open(os.path.join(tmp, "readme.txt"), "w", encoding="utf-8") as f:
f.write("Hello\n")
os.mkdir(os.path.join(tmp, "data"))
with open(os.path.join(tmp, "data.bin"), "wb") as f:
f.write(b"\x00" * 10)
with os.scandir(tmp) as it:
for entry in it:
kind = "dir" if entry.is_dir() else ("file" if entry.is_file() else "other")
size = entry.stat().st_size if entry.is_file() else "-"
print(f"{entry.name:12} {kind:4} size={size}")
readme.txt file size=6
data dir size=-
data.bin file size=10
pathlib.Path.iterdirで直下を列挙
Path.iterdir()
は直下のPath
オブジェクトを生成します。
パス結合やフィルタをメソッドチェーンで書きやすいのが利点です。
# pathlib の iterdir で直下の Path を列挙
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as tmp:
p = Path(tmp)
(p / "doc.md").write_text("# title\n", encoding="utf-8")
(p / "out").mkdir()
(p / "notes.txt").write_text("n", encoding="utf-8")
for child in p.iterdir():
print(child.name, "->", "dir" if child.is_dir() else "file")
doc.md -> file
out -> dir
notes.txt -> file
glob/rglobでパターン検索
ファイル名のパターン(ワイルドカード)で探すにはglob
が便利です。
**
を使うと再帰的検索ができます。
# glob モジュールの基本と再帰検索
import glob
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
open(os.path.join(tmp, "a.txt"), "w").close()
open(os.path.join(tmp, "b.log"), "w").close()
os.mkdir(os.path.join(tmp, "sub"))
open(os.path.join(tmp, "sub", "c.txt"), "w").close()
print("*.txt:", sorted(glob.glob(os.path.join(tmp, "*.txt"))))
print("**/*.txt (recursive):", sorted(glob.glob(os.path.join(tmp, "**", "*.txt"), recursive=True)))
*.txt: ['/tmp/tmpq1/a.txt']
**/*.txt (recursive): ['/tmp/tmpq1/a.txt', '/tmp/tmpq1/sub/c.txt']
pathlib
でも同様にPath.glob()
とPath.rglob()
が使えます。
# pathlib の glob / rglob
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp)
(base / "x1.py").write_text("print(1)\n", encoding="utf-8")
(base / "pkg").mkdir()
(base / "pkg" / "x2.py").write_text("print(2)\n", encoding="utf-8")
print("glob('*.py'):", [p.name for p in base.glob("*.py")])
print("rglob('*.py'):", sorted(p.name for p in base.rglob("*.py")))
glob('*.py'): ['x1.py']
rglob('*.py'): ['x1.py', 'x2.py']
ファイルの削除
ファイル削除は「小さな操作でも不可逆」です。
取り消しはできないため、例外処理やドライラン(予行演習)の併用を検討してください。
os.removeとpathlib.Path.unlinkの基本
ファイル削除はos.remove(path)
またはPath.unlink()
を使います。
どちらも通常ファイルに対して動作し、存在しなければFileNotFoundError
、権限がなければPermissionError
が発生します。
# ファイル削除の基本(存在するファイルを削除)
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
target = os.path.join(tmp, "temp.txt")
open(target, "w").close()
print("before exists:", os.path.exists(target)) # True
os.remove(target) # または pathlib.Path(target).unlink()
print("after exists :", os.path.exists(target)) # False
before exists: True
after exists : False
削除前の存在確認と例外処理
削除も「やってみて例外を捕まえる」方式が堅牢です。
事前にexists()
で確認しても、直後に他プロセスが削除している可能性があります。
# 推奨: 例外を捕まえて安全に削除する関数
from pathlib import Path
def remove_file_safe(path: Path) -> bool:
"""
指定ファイルを削除する。成功: True, 既にない: False を返す。
PermissionError などは上位に伝播(ログに残すなど)。
"""
try:
path.unlink()
return True
except FileNotFoundError:
return False # 既にない
# except IsADirectoryError: # ディレクトリに対して呼ぶと出る
# ... (用途に応じて処理)
# デモ
if __name__ == "__main__":
import tempfile
with tempfile.TemporaryDirectory() as tmp:
p = Path(tmp) / "to_delete.txt"
p.write_text("x", encoding="utf-8")
print("deleted:", remove_file_safe(p)) # True
print("deleted again:", remove_file_safe(p)) # False
deleted: True
deleted again: False
補足として、Windowsでは開いているファイルは削除できずPermissionError
になります(Linuxでは削除は可能だが参照は残る)。
環境差も考慮して例外処理でカバーすると安心です。
ディレクトリの削除
ディレクトリ削除は空かどうか
で使う関数が異なります。
空ならrmdir
、中身ごとならshutil.rmtree
が基本です。
空ディレクトリ削除(os.rmdir/Path.rmdir)
空のディレクトリはos.rmdir(path)
またはPath(path).rmdir()
で削除します。
中身があるとOSError
(具体的にはNotEmpty
)になります。
# 空ディレクトリの削除
import os
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as tmp:
empty_dir = Path(tmp) / "empty"
empty_dir.mkdir()
print("exists before:", empty_dir.exists()) # True
empty_dir.rmdir() # os.rmdir(str(empty_dir)) でも可
print("exists after :", empty_dir.exists()) # False
exists before: True
exists after : False
中身ごと削除(shutil.rmtree)
中身があるディレクトリはshutil.rmtree(path)
で再帰的に削除します。
非常に強力で取り消し不能なので細心の注意が必要です。
# ディレクトリを中身ごと削除
from pathlib import Path
import shutil
import tempfile
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp) / "project"
(base / "src").mkdir(parents=True)
(base / "src" / "main.py").write_text("print('hi')\n", encoding="utf-8")
(base / "data").mkdir()
(base / "data" / "input.txt").write_text("data\n", encoding="utf-8")
print("exists before:", base.exists()) # True
shutil.rmtree(base) # 中身ごと消える
print("exists after :", base.exists()) # False
exists before: True
exists after : False
注意点として、ディレクトリを指すシンボリックリンクをrmtree
に渡すと、リンク自体を削除し、リンク先は削除しません(通常の安全な挙動)。
特殊なファイル(デバイスファイル等)や権限の制限があると例外になります。
誤削除防止のチェック(ドライラン/対象絞り込み)
本番でrmtree
を実行する前に「何が消えるか」を確認するドライランが有効です。
また、許可されたディレクトリ配下だけを対象にすることでパス指定ミスを減らせます。
# ドライラン付きで安全に削除するユーティリティ
from pathlib import Path
import shutil
def is_relative_to(child: Path, parent: Path) -> bool:
"""Python 3.9未満互換: child が parent 配下なら True"""
try:
child.resolve().relative_to(parent.resolve())
return True
except Exception:
return False
def remove_tree_safe(target: Path, allow_base: Path, dry_run: bool = True) -> None:
"""
allow_base 配下の target だけを削除対象とし、dry_run=True のときは一覧を表示して削除しない。
"""
if not is_relative_to(target, allow_base):
raise ValueError(f"危険: {target} は許可ベース {allow_base} の配下ではありません")
if not target.exists():
print(f"[info] {target} は存在しません")
return
if dry_run:
print("[DRY-RUN] 次を削除予定:")
for p in sorted(target.rglob("*"), key=lambda x: (x.is_file(), str(x))):
kind = "DIR" if p.is_dir() else "FILE"
print(f" {kind}: {p}")
print("[DRY-RUN] 実削除は行っていません")
else:
shutil.rmtree(target)
print(f"[done] 削除しました: {target}")
# デモ
if __name__ == "__main__":
import tempfile
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp)
t = base / "sandbox"
(t / "logs").mkdir(parents=True)
(t / "logs" / "app.log").write_text("log\n", encoding="utf-8")
(t / "data").mkdir()
(t / "data" / "1.txt").write_text("1\n", encoding="utf-8")
# まずドライラン
remove_tree_safe(t, allow_base=base, dry_run=True)
# 次に本当に削除
remove_tree_safe(t, allow_base=base, dry_run=False)
[DRY-RUN] 次を削除予定:
DIR: /tmp/tmpabcd/sandbox/data
FILE: /tmp/tmpabcd/sandbox/data/1.txt
DIR: /tmp/tmpabcd/sandbox/logs
FILE: /tmp/tmpabcd/sandbox/logs/app.log
[DRY-RUN] 実削除は行っていません
[done] 削除しました: /tmp/tmpabcd/sandbox
絶対パスの誤指定(例: /
やC:\
)は壊滅的です。
上記のようなベース配下チェックや、対象件数・合計サイズの表示、ユーザー確認(Yes/No)を併用すると安全性が高まります。
まとめ
本記事では、Python標準ライブラリを用いた存在確認・一覧取得・削除を整理しました。
用途に応じて適切なAPIを選び、実操作は例外処理で守ることが最大のポイントです。
特に削除処理は取り返しがつかないため、ドライラン・ベース配下チェック・ユーザー確認などの安全策を取りましょう。
実務ではpathlib
の表現力が役立ちますが、os
系APIの挙動も理解しておくと既存コードの読解や移行がスムーズです。
今後は本記事を土台に、ログ回収やバックアップスクリプトなど、信頼性の高い自動化に発展させていってください。