第1部 Python基礎編
第8章 クラスとオブジェクト指向の基礎
「データ」と「そのデータを扱う処理」をひとまとめにする仕組みがクラスです。第1部の総仕上げとして、コードを整理する考え方を身につけます。
🎯 この章で学ぶこと
- クラスとインスタンスの関係
__init__とselfの意味- メソッドによる振る舞いの定義
- 継承の基本と、使いどころの判断
8.1 なぜクラスが必要か
ユーザーアカウントを辞書で表すと、こうなります。
account = {"username": "tanaka", "failed": 0, "locked": False}
しかし「失敗を記録する」「ロックすべきか判定する」という処理は、辞書とは別の場所にバラバラに書くことになります。データと処理が離れていると、正しい手順を踏まずにデータを直接いじるコード(=不整合やバグの温床)が生まれがちです。
クラスは、データ(属性)と処理(メソッド)をひとつの設計図にまとめる仕組みです。
8.2 クラスを定義する
class Account:
"""ログイン失敗の記録とロック判定を持つユーザーアカウント"""
MAX_FAILED = 5 # クラス共通の定数
def __init__(self, username):
self.username = username # 属性(インスタンスごとのデータ)
self.failed = 0
self.locked = False
def record_failure(self):
"""ログイン失敗を1回記録し、必要ならロックする"""
self.failed += 1
if self.failed >= Account.MAX_FAILED:
self.locked = True
def reset(self):
"""成功時に失敗カウントをリセットする"""
self.failed = 0
def status(self):
state = "ロック中" if self.locked else "有効"
return f"{self.username}: 失敗{self.failed}回 ({state})"
使ってみましょう。
alice = Account("alice") # インスタンス(実体)を作る
bob = Account("bob")
for _ in range(5):
alice.record_failure() # aliceにだけ失敗を記録
print(alice.status()) # alice: 失敗5回 (ロック中)
print(bob.status()) # bob: 失敗0回 (有効)
用語を整理します。
- クラス — 設計図(
Account) - インスタンス — 設計図から作った実体(
alice、bob)。互いに独立 __init__— インスタンス生成時に自動で呼ばれる初期化メソッドself— 「このインスタンス自身」。メソッドの第1引数に必ず書く- 属性 — インスタンスが持つデータ(
self.usernameなど) - メソッド — クラスの中に定義された関数
"abc".upper() も list.append() も、文字列オブジェクト・リストオブジェクトのメソッド呼び出しです。Pythonではすべての値がオブジェクトです。クラスを学ぶと「今まで使ってきたものの正体」がわかります。
8.3 カプセル化 — 正しい入口だけを使わせる
上の Account では、失敗の記録は必ず record_failure() を通ります。ロック判定のルールがこのメソッドの中に一元化されているので、「カウントは増えたのにロック判定を忘れた」という不整合が起こりません。
Pythonでは、外から直接触ってほしくない属性にアンダースコア始まりの名前を付ける慣習があります。
class ApiClient:
def __init__(self, api_key):
self._api_key = api_key # 「内部用」の目印
def masked_key(self):
return self._api_key[:4] + "****"
「データを変更する経路を1本に絞り、その経路で必ず検証する」──カプセル化のこの考え方は、セキュリティ設計そのものです。現実のシステムでも、DBを直接触らせずAPIを経由させる、ファイルを直接置かせずアップロード検証を通す、といった形で同じ原則が使われています。正しい入口を1つ作り、それ以外を塞ぐ。
8.4 継承 — 共通部分を親にまとめる
既存のクラスを土台に、差分だけ書いて新しいクラスを作れます。
class AdminAccount(Account):
"""管理者アカウント。より厳しいロック条件を持つ"""
MAX_FAILED = 3 # 管理者は3回でロック(上書き)
def record_failure(self):
self.failed += 1
if self.failed >= AdminAccount.MAX_FAILED:
self.locked = True
print(f"[重要] 管理者 {self.username} をロックしました。監査ログを確認してください")
admin = AdminAccount("admin")
for _ in range(3):
admin.record_failure()
print(admin.status()) # admin: 失敗3回 (ロック中)
AdminAccount は Account の status() や reset() をそのまま受け継ぎつつ、必要な部分だけ上書き(オーバーライド)しています。
継承は強力ですが、乱用すると「どの動きが親でどれが子か」追いにくいコードになります。初学者の目安:まず関数と通常のクラスで書く。明らかな「AはBの一種」の関係があるときだけ継承。迷ったら使わない、で大丈夫です。
8.5 データを運ぶだけなら dataclass
「処理はほぼ無く、データをまとめて運びたいだけ」なら、標準の dataclasses が便利です。__init__ を自動生成してくれます。
from dataclasses import dataclass
@dataclass
class LogEntry:
time: str
result: str
user: str
ip: str
entry = LogEntry("09:00:15", "FAILED", "admin", "203.0.113.9")
print(entry.user) # admin
print(entry) # LogEntry(time='09:00:15', result='FAILED', ...)
第13章のログ解析ツールでは、この LogEntry のような形でログの1行を構造化して扱います。
まとめ
- クラスはデータ(属性)と処理(メソッド)をまとめる設計図。インスタンスはその実体
__init__で初期化、selfは「自分自身」- データ変更の入口をメソッドに一本化すれば、不整合もバグも入りにくい(カプセル化)
- 継承は「AはBの一種」のときだけ。迷ったら使わない
- データの入れ物だけ欲しいときは
@dataclass
練習問題
Server クラスを作ってください。属性は name と is_up(初期値 True)。メソッド down() で停止状態に、up() で稼働状態にし、status() で「web-01 は稼働中です / 停止中です」と返すようにしてください。
解答を見る
class Server:
def __init__(self, name):
self.name = name
self.is_up = True
def down(self):
self.is_up = False
def up(self):
self.is_up = True
def status(self):
state = "稼働中" if self.is_up else "停止中"
return f"{self.name} は{state}です"
web = Server("web-01")
web.down()
print(web.status()) # web-01 は停止中です
8.2の Account クラスに、メソッド login(password, correct_password) を追加してください。仕様:①ロック中なら「ロック中です」と表示して False を返す ②パスワードが一致したら reset() して True を返す ③不一致なら record_failure() して False を返す。
解答を見る
class Account:
MAX_FAILED = 5
def __init__(self, username):
self.username = username
self.failed = 0
self.locked = False
def record_failure(self):
self.failed += 1
if self.failed >= Account.MAX_FAILED:
self.locked = True
def reset(self):
self.failed = 0
def login(self, password, correct_password):
if self.locked:
print("ロック中です")
return False
if password == correct_password:
self.reset()
return True
self.record_failure()
return False
「ロック確認 → 照合 → 成功/失敗処理」という認証の基本フローがクラスの中に一元化されました。なお、パスワードを平文で比較・保存するのは教材用の簡略化です。正しい扱い方は第12章で学びます。
次の設計のうち、カプセル化の考え方に反しているのはどれでしょう。①残高の変更は deposit() / withdraw() メソッド経由のみ ②どこからでも account.balance = 9999999 と直接書き換える ③withdraw() の中で残高不足をチェックする
解答を見る
②が反しています。検証を通らない変更経路が存在すると、残高不足チェック(③)をいくら丁寧に書いても意味がなくなります。「検証をバイパスできる経路を残さない」ことは、Webアプリの認可チェック漏れなど現実の脆弱性にも直結する考え方です。