Pythonの次のステップ:データ構造・内包表記・例外処理・クラス・ファイル操作を図解で理解する
前回の記事でif/for/関数/モジュールを学んだあなたへ。この記事を読み終えると、「ただ動くプログラム」から「整理された、壊れにくいプログラム」を書くための5つの武器が手に入ります。
📍 この記事の立ち位置
前回は「Pythonでプログラムを書き始めるための最低限の文法」を扱った。
今回はその一歩先——「複数のデータをまとめて扱う」「エラーが起きても崩れない」「処理をひとかたまりの設計として考える」ための記法を扱う。
カバーする5テーマはこちらだ。
- データ構造(リスト・辞書・タプル・セット) ——複数データをどう持つか
- 内包表記 ——forループを1行に圧縮する技
- 例外処理(try/except) ——エラーと上手に付き合う
- クラス ——データと処理をひとつの設計図にまとめる
- ファイル操作 ——データを読み書きして残す
① データ構造——「複数のデータをどう持つか」で選ぶ4種類
基本文法を覚えた直後に壁になるのが、「データをひとつの変数にまとめて持ちたい」という場面だ。
たとえば10人分の名前を変数10個で管理するのは、引き出しが10個ある棚を一個一個管理するようなもので、すぐに混乱する。
Pythonには「複数データをまとめる箱」として4種類のデータ構造が用意されている。
リスト(list)——順番があって、変更できる
最も頻繁に使うデータ構造だ。要素を [](角括弧) で囲み、カンマで区切る。
fruits = ["りんご", "バナナ", "みかん"]
print(fruits[0]) # → りんご(インデックスは0始まり)
print(fruits[-1]) # → みかん(後ろから数えるには負の数)
print(fruits[1:]) # → ['バナナ', 'みかん'](スライス:1番目以降)
# 要素の追加・削除
fruits.append("ぶどう") # 末尾に追加
fruits.insert(1, "メロン") # 指定位置に挿入
fruits.remove("バナナ") # 値を指定して削除
print(fruits)
# → ['りんご', 'メロン', 'みかん', 'ぶどう']
# リストの長さ
print(len(fruits)) # → 4
リストはいつでも中身を変えられる。「今後も追加・削除が起きる順番付きのデータ」はリスト一択だ。
辞書(dict)——名前で引き出す
辞書は「キーと値のペア」でデータを管理する。
本の索引のようなものだ——「項目名」さえわかれば、何ページにあるかを一瞬で調べられる。
user = {
"name": "田中",
"age": 28,
"email": "[email protected]"
}
print(user["name"]) # → 田中
print(user.get("age")) # → 28(キーが存在しないときも安全に取得できる)
# 追加・更新
user["team"] = "バックエンド" # 新しいキーを追加
user["age"] = 29 # 既存のキーを更新
# キー・値の一覧を取得
print(user.keys()) # → dict_keys(['name', 'age', 'email', 'team'])
print(user.values()) # → dict_values(['田中', 29, '[email protected]', 'バックエンド'])
# forで辞書を繰り返す
for key, value in user.items():
print(f"{key}: {value}")
辞書はインデックス(0,1,2…)ではなく意味のある名前でデータを管理したいときに使う。
「ユーザー情報」「設定値」「集計結果」など、構造化されたデータとの相性が抜群だ。
タプル(tuple)——変更不可のリスト
タプルは () で囲むリストだが、一度作ったら変更できない(イミュータブル)。
「絶対に変えてはいけないデータ」を守るための南京錠のようなものだ。
point = (35.6895, 139.6917) # 東京の緯度・経度
print(point[0]) # → 35.6895
# 変更しようとするとエラーになる
# point[0] = 0 # → TypeError: 'tuple' object does not support item assignment
# タプルのアンパック(複数の変数に一度に代入)
lat, lng = point
print(f"緯度: {lat}, 経度: {lng}")
セット(set)——重複のない集合
セットは 重複を自動的に除去する。数学の「集合」そのものだ。
tags = {"Python", "AI", "Python", "機械学習", "AI"}
print(tags) # → {'AI', 'Python', '機械学習'}(重複が消える・順序は不定)
# 集合演算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # → {3, 4} (積集合:両方に含まれる)
print(a | b) # → {1,2,3,4,5,6}(和集合:どちらかに含まれる)
print(a - b) # → {1, 2} (差集合:aだけに含まれる)
重複除去や「含まれているかどうかの高速チェック」が得意なのがセットの強みだ。
in 演算子による検索はリストより大幅に速い。
4種類の使い分けまとめ
| 種類 | 記法 | 順序 | 変更 | 重複 | 使いどころ |
|---|---|---|---|---|---|
| リスト | [1, 2, 3] |
あり | 可能 | あり | 順番付きデータ、動的に増減するデータ |
| 辞書 | {"key": val} |
あり(3.7+) | 可能 | キーは不可 | 名前でアクセスしたい構造化データ |
| タプル | (1, 2, 3) |
あり | 不可 | あり | 変えてはいけない座標・設定値 |
| セット | {1, 2, 3} |
なし | 可能 | なし | 重複除去、集合演算、高速な存在確認 |
② 内包表記——forループを1行に圧縮する「Pythonらしい」書き方
「リストの全要素を2倍にしたい」と思ったとき、素直に書くとこうなる。
numbers = [1, 2, 3, 4, 5]
doubled = []
for n in numbers:
doubled.append(n * 2)
print(doubled) # → [2, 4, 6, 8, 10]
これは正しい。でも、Pythonを書く人たちはもっとスマートな書き方を好む。
それが内包表記(リスト内包表記)だ。上の4行がこう書ける。
numbers = [1, 2, 3, 4, 5]
doubled = [n * 2 for n in numbers]
print(doubled) # → [2, 4, 6, 8, 10]
構造は [式 for 変数 in イテラブル] だ。
「各要素に対して何をするか」を1行で宣言できる。
条件付きフィルタリング
if を後ろに付けることで「条件を満たすものだけ処理する」が書ける。
scores = [82, 45, 91, 60, 73, 38, 88]
# 70点以上だけ取り出す
passed = [s for s in scores if s >= 70]
print(passed) # → [82, 91, 73, 88]
# 70点以上は"合格"、未満は"不合格"に変換
results = ["合格" if s >= 70 else "不合格" for s in scores]
print(results) # → ['合格', '不合格', '合格', '不合格', '合格', '不合格', '合格']
辞書内包表記・セット内包表記
同じ発想で辞書やセットも作れる。
words = ["apple", "banana", "cherry"]
# 単語とその文字数のペアを辞書にする
word_lengths = {w: len(w) for w in words}
print(word_lengths) # → {'apple': 5, 'banana': 6, 'cherry': 6}
# 重複を除いた文字数のセット
length_set = {len(w) for w in words}
print(length_set) # → {5, 6}
内包表記は「読める範囲内で使う」のがコツだ。
ネストが2段以上になってきたら素直にforループを使ったほうが、あとで読んで頭が痛くならない(筆者が保証する)。
③ 例外処理(try/except)——エラーと上手に付き合う
プログラムは必ずエラーが起きる。
ゼロ除算、存在しないファイルのオープン、ネットワーク接続失敗——現実の世界は想定外だらけだ。
try/except は「エラーが起きても崩れない」プログラムを書くための安全網だ。
道路工事の際に設置する「迂回路の看板」だと思えばいい。
基本構造
try:
result = 10 / 0 # ここでエラーが発生する
print(result) # ←ここは実行されない
except ZeroDivisionError:
print("ゼロで割ることはできません")
# → ゼロで割ることはできません(プログラムが落ちずに続く)
try ブロックの中でエラーが発生すると、except に処理がジャンプする。
except の後に書く ZeroDivisionError はエラーの種類(例外クラス)だ。
種類を指定することで「このエラーのときだけこの対処をする」という細かい制御ができる。
複数のエラーを扱う
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("エラー: ゼロ除算")
return None
except TypeError:
print("エラー: 数値以外が渡されました")
return None
else:
# エラーが起きなかったときだけ実行される
print(f"計算成功: {result}")
return result
finally:
# エラーの有無にかかわらず必ず実行される(後始末に使う)
print("--- 計算終了 ---")
safe_divide(10, 2) # → 計算成功: 5.0 / --- 計算終了 ---
safe_divide(10, 0) # → エラー: ゼロ除算 / --- 計算終了 ---
safe_divide(10, "a") # → エラー: 数値以外が渡されました / --- 計算終了 ---
try/except の処理フロー
よく使う例外クラスの一覧
| 例外クラス | 発生するタイミング | よくある原因 |
|---|---|---|
ZeroDivisionError | ゼロ除算 | x / 0 |
TypeError | 型が合わない | 数値に文字列を足す |
ValueError | 値が不正 | int("abc") |
KeyError | 辞書のキーが存在しない | d["missing"] |
IndexError | リストの範囲外 | lst[100] が存在しない |
FileNotFoundError | ファイルが見つからない | open("no.txt") |
AttributeError | 属性が存在しない | None.upper() |
④ クラス——データと処理を「設計図」にまとめる
関数は「処理をまとめる」仕組みだった。では「データと処理を一緒にまとめたい」場合はどうするか。
たとえば「ユーザー情報(名前・年齢)と、そのユーザーに関する操作(挨拶する・誕生日を迎える)」をセットで管理したいとき、関数だけでは難しくなってくる。
クラス(class) はデータと処理をひとまとめにした「設計図」だ。
その設計図から作った実体を インスタンス と呼ぶ。
「設計図」と「家」の関係に近い——設計図は1枚でも、家は何軒でも建てられる。
基本構造
class User:
# __init__ はインスタンスを作るときに自動で呼ばれる初期化メソッド
def __init__(self, name, age):
self.name = name # self.xxx でインスタンスの属性(データ)を持つ
self.age = age
def greet(self):
return f"こんにちは!私は{self.name}、{self.age}歳です。"
def birthday(self):
self.age += 1
return f"{self.name}さん、お誕生日おめでとう!{self.age}歳になりました。"
# インスタンスを作る(設計図から家を建てる)
user1 = User("田中", 28)
user2 = User("鈴木", 35)
print(user1.greet()) # → こんにちは!私は田中、28歳です。
print(user2.greet()) # → こんにちは!私は鈴木、35歳です。
print(user1.birthday()) # → 田中さん、お誕生日おめでとう!29歳になりました。
print(user1.age) # → 29(user2 には影響なし)
self は「自分自身のインスタンス」を指すキーワードだ。
メソッド(クラスの中の関数)の第1引数には必ず self を書く——これを忘れると怒られるので覚えておこう。
クラスの構造を図解する
継承——設計図を「拡張」する
クラスの強みのひとつが 継承(inheritance) だ。
既存のクラスを土台にして、機能を追加・上書きした新しいクラスを作れる。
class AdminUser(User): # User を継承
def __init__(self, name, age, role):
super().__init__(name, age) # 親クラスの __init__ を呼ぶ
self.role = role # 管理者固有の属性を追加
def greet(self): # 親クラスのメソッドを上書き(オーバーライド)
return f"[管理者] {self.name}({self.role})です。"
admin = AdminUser("山田", 40, "インフラ担当")
print(admin.greet()) # → [管理者] 山田(インフラ担当)です。
print(admin.birthday()) # → 親クラスのメソッドがそのまま使える
⑤ ファイル操作——データを読み書きして「残す」
プログラムは実行が終わると、変数に入っていたデータは消える。
「次回も使いたい」「別のプログラムに渡したい」データはファイルに書き出す必要がある。
冷蔵庫に食材を保存しておくようなイメージだ。
ファイルの読み書き基本
# ファイルに書き込む("w" モード:上書き)
with open("memo.txt", "w", encoding="utf-8") as f:
f.write("Pythonのファイル操作\n")
f.write("withブロックを使うと自動でファイルが閉じる\n")
# ファイルを読み込む("r" モード:読み取り)
with open("memo.txt", "r", encoding="utf-8") as f:
content = f.read() # ファイル全体を文字列として読む
print(content)
# 1行ずつ読む(大きなファイルに向いている)
with open("memo.txt", "r", encoding="utf-8") as f:
for line in f:
print(line.strip()) # strip() で行末の改行を除去
with open(...) as f: の形を使うと、ブロックを抜けたときに自動でファイルが閉じられる。
f.close() を手動で呼ぶ必要がなく、うっかりファイルを開きっぱなしにするバグを防げる。
モード一覧
| モード | 動作 | ファイルが存在しない場合 |
|---|---|---|
"r" | 読み取り(デフォルト) | エラー(FileNotFoundError) |
"w" | 書き込み(上書き) | 新規作成 |
"a" | 追記(末尾に追加) | 新規作成 |
"x" | 新規作成のみ | 新規作成(既存なら FileExistsError) |
CSVファイルの読み書き
実務でよく使うCSVファイルは、標準ライブラリの csv モジュールで手軽に扱える。
import csv
# CSVに書き込む
data = [
["名前", "年齢", "チーム"],
["田中", 28, "バックエンド"],
["鈴木", 35, "フロントエンド"],
["山田", 40, "インフラ"],
]
with open("users.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerows(data)
# CSVを読み込む
with open("users.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f) # ヘッダー行をキーとして辞書形式で読む
for row in reader:
print(f"{row['名前']}({row['チーム']}): {row['年齢']}歳")
# 出力:
# 田中(バックエンド): 28歳
# 鈴木(フロントエンド): 35歳
# 山田(インフラ): 40歳
✅ 要点まとめ
この記事で手に入れた5つの武器を振り返っておこう。
- データ構造: 「順番があって変えられる」リスト、「名前で引き出す」辞書、「変えられない」タプル、「重複を消す」セット——目的で選ぶ
- 内包表記:
[式 for 変数 in リスト if 条件]の形でforループを1行に圧縮する。読める範囲で使うのがコツ - 例外処理:
try/except/else/finallyでエラーを制御し、プログラムを「崩れにくく」する - クラス: データと処理を設計図(クラス)にまとめ、設計図から何個でもインスタンスを作れる。継承で機能を拡張できる
- ファイル操作:
with open(...) as f:でファイルを安全に開き、テキスト・CSVを読み書きできる
🚀 取り込み方——次の30分で試すこと
今日(5分)
前回作った greet() 関数を User クラスに移植してみよう。
「関数」から「クラスのメソッド」へのリファクタリングが最速の習得ルートだ。
# 前回の関数
def greet(name):
return f"こんにちは、{name}さん!"
# ↓ クラスに移植する
class User:
def __init__(self, name):
self.name = name
def greet(self):
return f"こんにちは、{self.name}さん!"
今週(30分の課題)
簡単な「家計簿スクリプト」を作ってみよう。
辞書のリスト・内包表記・ファイル保存をすべて組み合わせる練習になる。
import csv
# 支出データを辞書のリストで管理
expenses = [
{"date": "2026-04-01", "item": "コーヒー", "amount": 500},
{"date": "2026-04-02", "item": "ランチ", "amount": 1200},
{"date": "2026-04-03", "item": "書籍", "amount": 2500},
]
# 合計を内包表記で計算
total = sum(e["amount"] for e in expenses)
print(f"合計: {total}円") # → 合計: 4200円
# CSVに保存
with open("expenses.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["date", "item", "amount"])
writer.writeheader()
writer.writerows(expenses)
今月(ミニプロジェクト)
「コマンドラインで動く TODO リストツール」を作ってみよう。
Task クラス・ファイル保存・例外処理をすべて実戦で使うことになり、
この記事の内容がまるごと定着する。
🔥 ハマりポイント——次のステップでよくある3つの詰まりどころ
その1:クラスのメソッドで self を書き忘れる
「普通の関数と同じように書けばいいのでは?」と思いがちだが、
クラスのメソッドは必ず第1引数に self を書く。
def greet(): と書いてしまうと、呼び出したときに謎の TypeError が飛んでくる。
「メソッドの第1引数は絶対に self」を呪文のように覚えよう。
その2:辞書のキーが存在しないときに直接アクセスしてしまう
user["address"] のようにキーを直接指定すると、
キーが存在しないときに KeyError で止まる。
user.get("address", "未設定") を使えば、
存在しないときにデフォルト値が返り、プログラムが落ちない。
辞書から値を取り出す場面では get() を反射的に使う癖をつけよう。
その3:ファイルの文字コードを指定しない
Windows環境では open() のデフォルト文字コードが cp932(Shift-JIS系)になる場合があり、
日本語を含むファイルを書いたら別の環境で文字化けした——という事故が起きる。
open(..., encoding="utf-8") を必ず明示する習慣をつけておけばこの問題は起きない。
Rui Software