順番に読み進めながら学べます

os と pathlib — ファイルパスとディレクトリ操作

Python の os.path と pathlib モジュールを基礎から解説します。OS の差を吸収するパス組み立て、ディレクトリの一覧と再帰探索、Path オブジェクトでのパス分解まで、ハンズオンで学べます。

ファイルパスとディレクトリを扱う 2 つのモジュール — 古くから使われる os.path と、より新しく書きやすい pathlib を整理します。OS の差を吸収するパス組み立て、ディレクトリの一覧と再帰探索、Path オブジェクトでのパス分解までを順に見ていきます。

os.path — OS の差を吸収するパス組み立て

ファイルパスの区切り文字は OS によって違います。Windows は \(バックスラッシュ)、Linux と macOS は / です。コードに直接 "data/sales/2024.csv" と書く と Linux と Mac では動きますが、Windows 上で実行したときに パスを誤認 することがあります。

os.path.join("data", "sales", "2024.csv") のように 要素ごとに区切って渡す と、Python が動いている OS に合わせて適切な区切り文字をその場で選んでくれます。

os.path.join が OS ごとに区切りを切り替える
os.path.join( 'data', 'sales', '2024.csv')Linux / Macdata/sales/2024.csvWindowsdata\sales\2024.csv
同じ Python コードでも、Linux / Mac では / 区切り、Windows では \ 区切りに展開される。手書きで区切り文字を埋め込まない ことが、移植性を保つコツ。
関数意味
os.path.join(*parts)OS の区切りでパスを連結するjoin('data', 'sales') → 'data/sales'
os.path.exists(p)パスが存在するかTrue / False
os.path.isfile(p)ファイルか(ディレクトリではない)True / False
os.path.isdir(p)ディレクトリかTrue / False
os.path.basename(p)末尾のファイル名・フォルダ名basename('data/x.csv') → 'x.csv'
os.path.dirname(p)末尾を取り除いた親パスdirname('data/x.csv') → 'data'
os.path.splitext(p)拡張子を分離するsplitext('x.csv') → ('x', '.csv')

売上 CSV ファイルのパスos.path で組み立てて、ファイルの中身を読み出します(左の 📂 ファイルから data/sales/2024_q1.csv などが見えます)。

① os を読み込み、3 つのパス要素 data / sales / 2024_q1.csv を OS の区切り文字でつないで 1 つの文字列にしてください

② パス全体をそのまま表示してください

③ プレロードした data/sales/2024_q1.csvopen(path, "r") で開き、先頭 3 行を表示してください

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

実践 2 — basename と splitext で名前と拡張子に分ける

1 行に詰め込まず、段階的に変数へ受け取りながら ファイル名と拡張子を分離します。basename でフルパスから末尾のファイル名を取り出し、splitext でその名前を (名前, 拡張子) のタプルに分けます。

1 行に書かず、段階的に変数を経由して ファイル名と拡張子を取り出します。

① os をインポートし、path = "data/sales/2024_q1.csv" を用意してください

os.path.basename(path) で末尾のファイル名を取り出して filename に入れ、表示してください

os.path.splitext(filename)(名前, 拡張子) のタプルを取り出して parts に入れ、表示してください

④ そのタプルを 分割代入stemsuffix に分け、stem suffix の形でスペース区切りで表示してください

Python エディタ

コードを実行してください

os.listdir と os.walk — ディレクトリの一覧と再帰探索

フォルダの中身を Python から取り出すとき、1 階層だけ見る なら os.listdirサブフォルダまで再帰的に見る なら os.walk を使います。os.listdir は指定したフォルダの直下にある名前(ファイルとサブフォルダ両方)をリストで返し、os.walk は配下のすべての階層を再帰的に辿って (現在のパス, サブフォルダ名のリスト, ファイル名のリスト) のタプルを 1 階層ずつ生成します。

os.listdir と os.walk の違い
os.listdir直下の名前のリストos.walk再帰的に全階層を走査
os.listdir は直下の名前だけ、os.walk は配下を全階層再帰的に辿る。深さの欲しさで使い分ける。
import os

# 1 階層: 'data' 直下の名前
print(os.listdir("data"))
# → ['sales', 'inventory']

# 再帰: 'data' 配下を全部辿る
for dirpath, dirnames, filenames in os.walk("data"):
    print(dirpath, filenames)
# → data ['sales', 'inventory'] []
#    data/sales [] ['2024_q1.csv', '2024_q2.csv']
#    data/inventory [] ['items.json']

`data/` ディレクトリの中身 を 2 通りの方法で表示します。まずは直下、次に再帰的にすべて。

① os を読み込んでください

os.listdir("data")data/直下にある名前 を並べ替えて表示してください(順序を揃えるため sorted で囲みます)

os.walk("data")data/ 配下を 再帰的に 辿り、各階層について (現在のパス, ファイル名のリスト) を 1 行ずつ表示してください(ファイル名のリストも sorted で揃えます)

Python エディタ

コードを実行してください

glob — パターンマッチでファイルを集める

拡張子が `.csv` のものだけ のように、条件に合うファイルだけ集めたいときは glob モジュールが最短です。*(任意の文字列)や **(任意の階層)といったワイルドカードで対象パターンを書くと、それに合うパスのリストが返ります。

glob のワイルドカード
*同じ階層内の任意の文字列**任意の階層をまたぐ(recursive=True)
* は同一階層内の任意の文字列、** は任意の階層をまたぐ(recursive=True 必須)。
import glob

# data/sales 直下の CSV ファイル
print(glob.glob("data/sales/*.csv"))
# → ['data/sales/2024_q1.csv', 'data/sales/2024_q2.csv']

# data 配下を再帰的に検索(** + recursive=True)
print(glob.glob("data/**/*.csv", recursive=True))
# → ['data/sales/2024_q1.csv', 'data/sales/2024_q2.csv']

glob のワイルドカード ** は recursive=True とセット

glob.glob("data/**/*.csv") のパターンにある 二重アスタリスク は、何階層でも辿るワイルドカードです。ただし recursive=True を渡さないと普通の * と同じ扱いになり、深いフォルダが見つかりません。再帰検索したいときは必ずこの引数を付けます。

`data/sales/` の CSV ファイル を glob でまとめて取り出します。

① glob を読み込んでください

glob.glob("data/sales/*.csv") で sales 配下の CSV を取り出し、sorted で並べ替えて変数 csv_files に入れてください

③ 取れたパスを 1 件ずつ表示してください

見つかったファイル数: ◯ の形で件数を表示してください

Python エディタ

コードを実行してください

pathlib.Path — オブジェクト指向のパス操作

os.path文字列でパスを扱う ライブラリでしたが、Python 3.4 以降は パス自体を 1 つのオブジェクトとして扱う pathlib.Path が推奨されています。Path("data/sales/2024_q1.csv") のように作ると、.parent で親フォルダ.name で末尾.stem で拡張子なしの名前.suffix で拡張子 といった属性で各部分にアクセスできます。

Path オブジェクトの属性
Path('data/sales/2024_q1.csv').parentPath('data/sales').name'2024_q1.csv'.stem'2024_q1'.suffix'.csv'
1 つの Path から .parent / .name / .stem / .suffix で各部分を取り出せる。os.path.dirname / basename / splitext を別々に呼ぶより読みやすい。
from pathlib import Path

p = Path("data") / "sales" / "2024_q1.csv"   # / 演算子で連結
print(p)               # data/sales/2024_q1.csv
print(p.parent)        # data/sales
print(p.name)          # 2024_q1.csv
print(p.stem)          # 2024_q1
print(p.suffix)        # .csv
print(p.exists())      # True

# 中身を読む(with open のラッパー)
print(p.read_text())   # CSV の中身

# サブフォルダの一覧(os.walk 相当)
for sub in Path("data").rglob("*.csv"):
    print(sub)

`os.path` は文字列ベース、`pathlib.Path` はオブジェクトベース で同じ操作を提供します。下の表でやりたいことごとに対応関係を整理します。

やりたいことos.path 系pathlib 系
連結os.path.join('data', 'x.csv')Path('data') / 'x.csv'
親フォルダos.path.dirname(p)p.parent
ファイル名os.path.basename(p)p.name
拡張子なしのファイル名os.path.splitext(p)[0] の処理p.stem
拡張子os.path.splitext(p)[1]p.suffix
存在確認os.path.exists(p)p.exists()
再帰検索glob.glob('**/*.csv', recursive=True)Path('.').rglob('*.csv')
読み込みwith open(p) as f: f.read()p.read_text()

新しいコードは pathlib を選ぶ

新規コードは pathlib を使うのが推奨 です。古いライブラリの API が文字列パスを要求する場面(例: 一部の DB ドライバ)では str(p) で変換すれば渡せます。os.path が消えるわけではないので、既存コードを読むためにも両方の対応関係を知っておくと安心です。

pathlib.Path で売上 CSV を扱います。文字列を直接結合するのではなく、Path オブジェクトの演算子と属性を使って同じ結果を得ます。

① pathlib から Path クラスを読み込んでください

data / sales / 2024_q1.csv の 3 要素から、Path の連結演算子で 1 つの Path オブジェクトを作ってください(os.path.join の代わり)

③ Path オブジェクトの 拡張子なしの名前拡張子 を、属性アクセスで取り出してスペース区切りで表示してください

④ Path オブジェクトのメソッドで、ファイルの中身を一気に文字列として読み出して表示してください(with open(...) を書かなくて済む方法を使います)

Python エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1Windows でも Linux でも壊れないパス組み立て として推奨される書き方はどれですか?

Q2次のうち、フォルダの中身を再帰的に全階層辿る のに最も向いているのはどれですか?

Q3p = Path("data/sales/2024_q1.csv") のとき、p.stem の値はどれですか?