ここまでのシリーズで、
- 辞書(dict)で「1件分のログ」を表現し
- リストで「複数件のログ」をまとめて扱い
- if / for / while でログを絞り込んだり、対話的に入力したりし
- CSVファイルに保存して、あとから読み返せるようにし
- 関数で「よく使う処理」に名前をつけて整理し
- 例外処理(try / except)で、入力ミスやファイルの有無にやさしく対応する
ところまで来ました。
だいぶ“ちゃんとしたツール”になってきましたが、
コードを眺めていると、こんな感覚も出てきませんか?
- 「コーヒーログって、毎回同じキーを持った辞書だよな」
- 「スコアからコメントを返す処理、どこかにくっつけておけたら楽なのに」
- 「釣りログも、ブラウンかどうかとか、50アップかどうかとか、
そのログ自身に聞けたら気持ちいいな」
そのタイミングで登場するのが クラス(class) です。
今回のテーマは、
CoffeeLog / FishingLog という「型」を作り、
それぞれを“ひとつの存在”として扱えるようになること。
難しいオブジェクト指向の理論は脇に置いておいて、
湖のログ・コーヒーのログを、Pythonの中で「ひとりひとりのキャラ」として
見ていく感覚を育てていきましょう
この回のゴールと全体像
第10回で目指すゴールは、次の4つです。
- クラス / オブジェクトのざっくりしたイメージ(設計図と実体)が持てる
classと__init__、self、メソッド(インスタンスメソッド)の基本がわかる- CoffeeLog / FishingLog クラスを定義し、属性・メソッドを使える
to_dict()でCSVとの橋渡しができる(過去のコードとつながる)
ここまで来れば、
- 「データ(状態)」
- 「そこにくっつく処理(メソッド)」
を ひとまとめの“型”として扱う 入口に立てます。
クラスとオブジェクトとは?— 設計図と、その実体たち
まずはイメージから。
- クラス(class)
- 設計図・型・テンプレート のようなもの
- CoffeeLog というクラスは「コーヒーログってこういう項目を持つし、
こういう振る舞い(メソッド)もできるよ」という設計図
- オブジェクト/インスタンス(instance)
- クラスという設計図から 実際に作られた“モノ”
- 2025/05/01 の「エチオピア浅煎り 4.5点」のログ1件が、CoffeeLogのインスタンス
たとえば、
「コーヒーログという“種族”の概念がクラスで、
実際に飲んだ一杯一杯がインスタンス」
くらいのイメージでOKです。
いちばん小さいクラスの例を見てみる
抽象的な話が続くと眠くなるので、
まずは 最小クラス の例から。
sample_class.py
class Hello:
def say_hello(self):
print("こんにちは、クラスの世界!")
# クラスからインスタンス(実体)を作る
greeter = Hello()
# メソッドを呼び出す
greeter.say_hello()
ターミナルから実行します:
python3 sample_class.py
実行結果:
こんにちは、クラスの世界!
ポイントは3つ:
class Hello:でクラスの定義を始めるgreeter = Hello()で インスタンスを1つ作るgreeter.say_hello()で インスタンスのメソッドを呼ぶ
self は「そのインスタンス自身」を表すキーワードで、
クラスの メソッドの1番目の引数 に必ず書くのがルールです(呼ぶ側で書く必要はない)。
init で「生まれたときの初期状態」を決める
さっきの Hello クラスは「何の情報も持たない」クラスでした。
コーヒーログ / 釣りログを扱うなら、
- コーヒー名
- 焙煎度
- メモ
- スコア
のような情報を、そのインスタンスが 最初から持っている 状態にしたくなります。
その「生まれた瞬間の初期化」を行うのが、__init__ メソッドです。
CoffeeLog の最初の形
class CoffeeLog:
def __init__(self, name, roast, memo, score):
# self.〇〇 が「インスタンスが持つ属性」
self.name = name
self.roast = roast
self.memo = memo
self.score = score
使う側はこうなります。
log1 = CoffeeLog(
name="エチオピア浅煎り",
roast="浅煎り",
memo="フルーティーで朝の湖に合う",
score=4.5,
)
print(log1.name) # → "エチオピア浅煎り"
print(log1.score) # → 4.5
__init__の引数(name, roast, ...)は、インスタンスを作るときに渡す材料self.〇〇は、その材料をインスタンスの「属性(プロパティ)」として保存している
self は「このインスタンス自身」を表すので、self.name は「このログの名前」、self.score は「このログのスコア」です。
メソッドで「そのログに聞いてほしいこと」をまとめる
クラスの本領発揮は、
「そのデータに関する処理を、そのクラスのメソッドとして生やす」
ところにあります。
CoffeeLog にとって自然な質問といえば、例えば:
- 「このコーヒーはお気に入りレベルか?」
- 「どんなコメントをつけるべき?」
などです。
CoffeeLog にメソッドを足してみる
class CoffeeLog:
def __init__(self, name, roast, memo, score):
self.name = name
self.roast = roast
self.memo = memo
self.score = score
def is_favorite(self, threshold=4.0):
"""お気に入り判定(デフォルト4.0以上)"""
return self.score >= threshold
def comment(self):
"""スコアに応じたコメントを返す"""
if self.score >= 4.5:
return "かなり特別な一杯。記念すべきレベル。"
elif self.score >= 4.0:
return "安定して美味しい。リピートしたい。"
elif self.score >= 3.0:
return "普通に美味しい。気分次第でまた飲むかも。"
else:
return "好みとは少し違うかもしれない。"
使う側:
log1 = CoffeeLog(
name="エチオピア浅煎り",
roast="浅煎り",
memo="フルーティーで朝の湖に合う",
score=4.5,
)
print(log1.name, "はお気に入り?:", log1.is_favorite()) # True
print("コメント:", log1.comment())
以前は「スコアに応じたコメント」を関数として外に置いていましたが、
こうして CoffeeLog のメソッド にすると、
log1.comment()log2.comment()
のように「そのログ自身に聞く」形になります。
FishingLog クラスも作ってみる
同じように、釣りログもクラスにしてみましょう。
釣りログの場合、自然な質問は:
- 50アップか?
- ブラウンか?レイクか?
- リリースしたか?
などです。
FishingLog の基本形
class FishingLog:
def __init__(self, date, lake, fish_type, length_cm, is_released):
self.date = date
self.lake = lake
self.fish_type = fish_type
self.length_cm = length_cm
self.is_released = is_released
def is_50up(self):
return self.length_cm >= 50
def is_brown(self):
return self.fish_type == "ブラウントラウト"
def summary(self):
base = f"{self.date} {self.lake} {self.fish_type} {self.length_cm}cm"
if self.is_released:
base += "(リリース)"
return base
使う側:
log = FishingLog(
date="2025/05/01",
lake="中禅寺湖",
fish_type="ブラウントラウト",
length_cm=52,
is_released=True,
)
print(log.summary()) # 1行サマリー
print("50アップ?:", log.is_50up()) # True
print("ブラウン?:", log.is_brown()) # True
だんだん、「1件のログ」が ただの辞書ではなく“キャラ” に見えてきたらOKです。
CSVとの橋渡し用に to_dict() / from_dict() を用意する
これまでのシリーズでは、CSVとのやり取りに
- 辞書のリスト(
{"name": ..., "score": ...}) csv.DictWriter/csv.DictReader
を使ってきました。
クラスと仲良くさせるには、
- クラス → 辞書 → CSV
- CSV → 辞書 → クラス
という橋渡しをするメソッドを用意すると便利です。
CoffeeLog に to_dict() / from_dict() を足す
class CoffeeLog:
def __init__(self, name, roast, memo, score):
self.name = name
self.roast = roast
self.memo = memo
self.score = float(score) # ここで型をそろえておく
def is_favorite(self, threshold=4.0):
return self.score >= threshold
def comment(self):
if self.score >= 4.5:
return "かなり特別な一杯。記念すべきレベル。"
elif self.score >= 4.0:
return "安定して美味しい。リピートしたい。"
elif self.score >= 3.0:
return "普通に美味しい。気分次第でまた飲むかも。"
else:
return "好みとは少し違うかもしれない。"
def to_dict(self):
"""CSVに書き出す用の辞書に変換"""
return {
"name": self.name,
"roast": self.roast,
"memo": self.memo,
"score": self.score,
}
@classmethod
def from_dict(cls, row):
"""CSVから読み込んだ辞書からCoffeeLogを作る"""
return cls(
name=row["name"],
roast=row["roast"],
memo=row["memo"],
score=float(row["score"]),
)
ここで初めて @classmethod と cls が登場しましたが、
@classmethod:クラスに紐づいたメソッドcls:そのクラス自身(CoffeeLogそのもの)
と思っておけばOKです。
CoffeeLog.from_dict(row) と呼ぶと、CoffeeLog(...) でインスタンスを作って返してくれます。
書き込み側:クラスのリスト → 辞書のリスト → CSV
import csv
logs = [
CoffeeLog("エチオピア浅煎り", "浅煎り", "朝の湖に合う", 4.5),
CoffeeLog("グアテマラ中煎り", "中煎り", "安定した味", 4.0),
]
with open("coffee_logs.csv", "w", newline="", encoding="utf-8") as f:
fieldnames = ["name", "roast", "memo", "score"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
dict_logs = [log.to_dict() for log in logs]
writer.writerows(dict_logs)
読み込み側:CSV → 辞書 → クラスのリスト
import csv
coffee_logs = []
with open("coffee_logs.csv", "r", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
log = CoffeeLog.from_dict(row)
coffee_logs.append(log)
# 50アップ…ではなく「スコア4.0以上のお気に入り」だけ表示
for log in coffee_logs:
if log.is_favorite():
print(log.name, log.score, log.comment())
これで、
- CSVとの橋渡しは
to_dict()/from_dict() - ログとしての振る舞いは
is_favorite()/comment()
と役割を分けられました。
FishingLog も同じパターンで CSV とつなげる
FishingLog でも同じように to_dict() / from_dict() を用意できます。
class FishingLog:
FIELDNAMES = ["date", "lake", "fish_type", "fish_length_cm", "is_released"]
def __init__(self, date, lake, fish_type, length_cm, is_released):
self.date = date
self.lake = lake
self.fish_type = fish_type
self.length_cm = int(length_cm)
self.is_released = bool(is_released)
def is_50up(self):
return self.length_cm >= 50
def is_brown(self):
return self.fish_type == "ブラウントラウト"
def summary(self):
base = f"{self.date} {self.lake} {self.fish_type} {self.length_cm}cm"
if self.is_released:
base += "(リリース)"
return base
def to_dict(self):
return {
"date": self.date,
"lake": self.lake,
"fish_type": self.fish_type,
"fish_length_cm": self.length_cm,
"is_released": self.is_released,
}
@classmethod
def from_dict(cls, row):
return cls(
date=row["date"],
lake=row["lake"],
fish_type=row["fish_type"],
length_cm=int(row["fish_length_cm"]),
is_released=row["is_released"] in ("True", "true", "1", "y", "Y"),
)
こうしておけば、
- 「今シーズンの釣りログCSV」を読み込んで
FishingLogのリストにして- 50アップだけを
.is_50up()で絞り込んで、.summary()で表示する
という流れが素直に書けるようになります。
コーヒーログクラス版の「全部入り」サンプル
ここまで出てきた要素をひとつにまとめた
クラス版コーヒーログツール のサンプルを載せておきます。
ファイル名:coffee_logger_class.py
# coffee_logger_class.py
import csv
import os
class CoffeeLog:
def __init__(self, name, roast, memo, score):
self.name = name
self.roast = roast
self.memo = memo
self.score = float(score)
def is_favorite(self, threshold=4.0):
return self.score >= threshold
def comment(self):
if self.score >= 4.5:
return "かなり特別な一杯。記念すべきレベル。"
elif self.score >= 4.0:
return "安定して美味しい。リピートしたい。"
elif self.score >= 3.0:
return "普通に美味しい。気分次第でまた飲むかも。"
else:
return "好みとは少し違うかもしれない。"
def to_dict(self):
return {
"name": self.name,
"roast": self.roast,
"memo": self.memo,
"score": self.score,
}
@classmethod
def from_dict(cls, row):
return cls(
name=row["name"],
roast=row["roast"],
memo=row["memo"],
score=float(row["score"]),
)
def input_one_coffee_log():
name = input("コーヒー名を入力してください(終了するには q):")
if name == "q":
return None
roast = input("焙煎度(浅煎り・中煎り・深煎りなど):")
memo = input("ひとことメモ:")
score_str = input("スコア(0〜5の数字):")
try:
score = float(score_str)
except ValueError:
print("数値に変換できなかったので、スコアを 3.0 にします。")
score = 3.0
return CoffeeLog(name, roast, memo, score)
def append_logs_to_csv(filename, fieldnames, logs):
if not logs:
print("書き込むログがありません。")
return
file_exists = os.path.exists(filename)
with open(filename, "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
if not file_exists:
writer.writeheader()
dict_logs = [log.to_dict() for log in logs]
writer.writerows(dict_logs)
print(f"{filename} に {len(logs)} 件のログを書き込みました。")
def main():
filename = "coffee_logs.csv"
fieldnames = ["name", "roast", "memo", "score"]
logs = []
while True:
log = input_one_coffee_log()
if log is None:
print("入力を終了します。")
break
logs.append(log)
print("1件ログを追加しました。")
print("----------------------")
append_logs_to_csv(filename, fieldnames, logs)
if __name__ == "__main__":
main()
実行方法
1. coffee_logger_class.py を作って上のコードを貼る
2. ターミナルでファイルのあるフォルダへ移動
3. 実行:
python3 coffee_logger_class.py
4. コーヒー名などを入力し、やめたくなったらコーヒー名に q
5. 同じフォルダに coffee_logs.csv ができていればOK
中身をあとから読み込んで、CoffeeLog.from_dict(row) でインスタンスに戻せば、
「昔の一杯」に .is_favorite() や .comment() で再び問いかけることができます。
今日のまとめ – ログが「ただのデータ」から「小さな存在」になる
今回は、
- クラスとオブジェクト(インスタンス)のイメージ(設計図と実体)
class/__init__/self/ メソッドの基本- CoffeeLog / FishingLog に属性とメソッドを持たせる
to_dict()/from_dict()でCSVとの橋渡しをする- クラス版のコーヒーログツール(coffee_logger_class.py)の例
を見てきました。
これで、
コーヒーや釣りのログは 「ただの辞書の集まり」 から、
「名前やスコアやコメントを持った、小さな存在(オブジェクト)」
に一歩近づいたはずです。



コメント