Pythonで一歩進んだコードを書こうとすると、必ず出てくるのが「クラス」の概念です。
「クラスって難しそう…」「関数だけじゃダメなの?」「いつ使うべきなの?」と感じている方も多いでしょう。
実は、クラスを使うことで、コードの再利用性が高まり、複雑な処理も整理して扱えるようになります。
大きなプログラムを作るときには欠かせない技術です。
この記事では、Pythonのクラスについて基礎から応用まで解説します。
クラスとは?オブジェクト指向の基礎知識

クラスの基本概念
クラスとは: 「オブジェクト(物体)を生み出す設計図」
例え話:
- クラス = たい焼きの型
- オブジェクト(インスタンス) = 実際に作られたたい焼き
- 属性(変数) = あんこの種類、大きさ
- メソッド(関数) = 食べる、温める
なぜクラスが必要なの?
従来の関数中心の考え方:
# 関数で犬の情報を管理
def bark_dog(name):
print(f"{name}がワンワン!")
def feed_dog(name, food):
print(f"{name}に{food}をあげた")
# 使用例
bark_dog("ポチ")
feed_dog("ポチ", "ドッグフード")
問題点:
- データ(犬の名前)と処理(吠える、食べる)がバラバラ
- 犬の情報が増えると管理が大変
- 複数の犬を扱うときにコードが複雑になる
クラスを使った改善:
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name}がワンワン!")
def eat(self, food):
print(f"{self.name}に{food}をあげた")
# 使用例
pochi = Dog("ポチ")
pochi.bark()
pochi.eat("ドッグフード")
メリット:
- データと処理がひとまとめになっている
- 複数の犬を簡単に管理できる
- コードの意味がわかりやすい
オブジェクト指向プログラミング(OOP)の基本
オブジェクト指向の3つの要素:
- カプセル化:データと処理をひとまとめにする
- 継承:既存のクラスを元に新しいクラスを作る
- ポリモーフィズム:同じ操作でも対象によって動作が変わる
身近な例:
- スマートフォン:データ(連絡先、写真)と機能(電話、カメラ)が一体
- 車:基本機能は共通だが、車種によって性能が異なる
基本的なクラスの作り方
最もシンプルなクラス
基本的な構文:
class クラス名:
def メソッド名(self):
処理内容
実例:
class Dog:
def bark(self):
print("ワン!")
# オブジェクトを作成(インスタンス化)
my_dog = Dog()
# メソッドを呼び出し
my_dog.bark() # 出力: ワン!
重要なポイント:
- クラス名は大文字で始める(PascalCase)
- selfは必ず第一引数に書く
- selfは「自分自身」を指す特別な変数
selfの意味を理解しよう
selfとは: 「このオブジェクト自身」を指す特別な変数
例で理解:
class Calculator:
def add(self, a, b):
result = a + b
print(f"結果: {result}")
return result
# 2つの電卓オブジェクトを作成
calc1 = Calculator()
calc2 = Calculator()
# それぞれ独立して動作
calc1.add(2, 3) # 結果: 5
calc2.add(10, 20) # 結果: 30
selfの働き:
calc1.add(2, 3)
を呼び出すとき- Pythonは自動的に
Calculator.add(calc1, 2, 3)
として実行 self
にはcalc1
が入る
コンストラクタとインスタンス変数

__init__メソッド(コンストラクタ)
__init__
とは: オブジェクトが作られたときに自動的に実行される特別なメソッド
基本的な使い方:
class Person:
def __init__(self, name, age):
self.name = name # インスタンス変数
self.age = age # インスタンス変数
def introduce(self):
print(f"こんにちは、{self.name}です。{self.age}歳です。")
# オブジェクトを作成(__init__が自動実行される)
alice = Person("アリス", 25)
bob = Person("ボブ", 30)
# メソッドを呼び出し
alice.introduce() # こんにちは、アリスです。25歳です。
bob.introduce() # こんにちは、ボブです。30歳です。
インスタンス変数の詳細
インスタンス変数の特徴:
self.変数名
で定義- オブジェクトごとに独立した値を持つ
- オブジェクトが存在する限り値を保持
実例で確認:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # 口座名義
self.balance = balance # 残高
def deposit(self, amount):
self.balance += amount
print(f"{amount}円を入金しました。残高: {self.balance}円")
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
print(f"{amount}円を出金しました。残高: {self.balance}円")
else:
print("残高不足です")
def show_balance(self):
print(f"{self.owner}さんの残高: {self.balance}円")
# 2つの口座を作成
account1 = BankAccount("田中", 10000)
account2 = BankAccount("佐藤", 5000)
# それぞれ独立して動作
account1.deposit(2000) # 2000円を入金しました。残高: 12000円
account2.withdraw(1000) # 1000円を出金しました。残高: 4000円
account1.show_balance() # 田中さんの残高: 12000円
account2.show_balance() # 佐藤さんの残高: 4000円
デフォルト引数の活用
便利な初期設定:
class Student:
def __init__(self, name, grade=1, subjects=None):
self.name = name
self.grade = grade
self.subjects = subjects if subjects is not None else []
def add_subject(self, subject):
self.subjects.append(subject)
def show_info(self):
print(f"生徒名: {self.name}")
print(f"学年: {self.grade}年生")
print(f"履修科目: {self.subjects}")
# さまざまな作り方
student1 = Student("山田") # デフォルト値を使用
student2 = Student("鈴木", 3) # 学年を指定
student3 = Student("高橋", 2, ["数学", "英語"]) # すべて指定
student1.add_subject("国語")
student1.show_info()
クラス変数とインスタンス変数の違い

2つの変数の種類
インスタンス変数:
self.変数名
で定義- オブジェクトごとに異なる値を持つ
クラス変数:
- クラス直下で定義
- すべてのオブジェクトで共有される
実例で比較:
class Car:
# クラス変数(すべての車で共通)
wheels = 4
fuel_type = "ガソリン"
def __init__(self, maker, model, color):
# インスタンス変数(車ごとに異なる)
self.maker = maker
self.model = model
self.color = color
self.mileage = 0
def drive(self, distance):
self.mileage += distance
print(f"{distance}km走行しました。総走行距離: {self.mileage}km")
def show_info(self):
print(f"メーカー: {self.maker}")
print(f"車種: {self.model}")
print(f"色: {self.color}")
print(f"タイヤ数: {self.wheels}") # クラス変数
print(f"走行距離: {self.mileage}km")
# 2台の車を作成
car1 = Car("トヨタ", "プリウス", "白")
car2 = Car("ホンダ", "フィット", "青")
# それぞれ別々に走行
car1.drive(100)
car2.drive(50)
car1.show_info()
print("---")
car2.show_info()
# クラス変数は共通
print(f"すべての車のタイヤ数: {Car.wheels}")
クラス変数の変更とその影響
クラス変数を変更:
class Counter:
count = 0 # クラス変数
def __init__(self, name):
self.name = name
Counter.count += 1 # クラス変数を更新
def show_count(self):
print(f"{self.name}: 現在{Counter.count}個のオブジェクトが作られています")
# オブジェクトを作るたびにカウントが増える
obj1 = Counter("オブジェクト1")
obj1.show_count() # オブジェクト1: 現在1個のオブジェクトが作られています
obj2 = Counter("オブジェクト2")
obj2.show_count() # オブジェクト2: 現在2個のオブジェクトが作られています
obj3 = Counter("オブジェクト3")
obj1.show_count() # オブジェクト1: 現在3個のオブジェクトが作られています
継承とオーバーライド

継承の基本概念
継承とは: 既存のクラスをベースに新しいクラスを作る仕組み
メリット:
- コードの再利用ができる
- 共通機能をまとめて管理
- 段階的な機能拡張が可能
基本的な書き方:
class 親クラス名:
# 親クラスの内容
class 子クラス名(親クラス名):
# 子クラス独自の内容
継承の実例
基本的な継承:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name}が何かの声を出した")
def move(self):
print(f"{self.name}が移動した")
class Dog(Animal): # Animalクラスを継承
def speak(self): # 親クラスのメソッドをオーバーライド
print(f"{self.name}がワンワン!")
def fetch(self): # 子クラス独自のメソッド
print(f"{self.name}がボールを取ってきた")
class Cat(Animal): # Animalクラスを継承
def speak(self): # 親クラスのメソッドをオーバーライド
print(f"{self.name}がニャーニャー!")
def climb(self): # 子クラス独自のメソッド
print(f"{self.name}が木に登った")
# 使用例
dog = Dog("ポチ")
cat = Cat("タマ")
# 共通のメソッド(継承されたもの)
dog.move() # ポチが移動した
cat.move() # タマが移動した
# オーバーライドされたメソッド
dog.speak() # ポチがワンワン!
cat.speak() # タマがニャーニャー!
# 子クラス独自のメソッド
dog.fetch() # ポチがボールを取ってきた
cat.climb() # タマが木に登った
super()を使った親クラスの活用
親クラスのメソッドを活用:
class Vehicle:
def __init__(self, maker, model):
self.maker = maker
self.model = model
self.fuel = 100
def start(self):
print(f"{self.maker} {self.model}のエンジンをかけた")
def stop(self):
print(f"{self.maker} {self.model}のエンジンを止めた")
class Car(Vehicle):
def __init__(self, maker, model, doors):
super().__init__(maker, model) # 親クラスの__init__を呼び出し
self.doors = doors
def start(self):
super().start() # 親クラスのstartメソッドを呼び出し
print("ドアをロックしました")
def show_info(self):
print(f"車種: {self.maker} {self.model}")
print(f"ドア数: {self.doors}")
print(f"燃料残量: {self.fuel}%")
class Motorcycle(Vehicle):
def __init__(self, maker, model, cc):
super().__init__(maker, model)
self.cc = cc
def start(self):
super().start()
print("ヘルメットを着用してください")
# 使用例
car = Car("トヨタ", "カローラ", 4)
bike = Motorcycle("ホンダ", "CB750", 750)
car.start()
# 出力:
# トヨタ カローラのエンジンをかけた
# ドアをロックしました
bike.start()
# 出力:
# ホンダ CB750のエンジンをかけた
# ヘルメットを着用してください
実用例:クラスを使ったアプリケーション開発

家計簿アプリの作成
段階的に機能を作っていきます:
基本的な支出クラス:
class Expense:
def __init__(self, date, category, amount, description=""):
self.date = date
self.category = category
self.amount = amount
self.description = description
def show(self):
desc = f" ({self.description})" if self.description else ""
print(f"{self.date}: {self.category} - {self.amount}円{desc}")
# 使用例
expense1 = Expense("2024-01-15", "食費", 1500, "昼食")
expense2 = Expense("2024-01-15", "交通費", 300)
expense1.show() # 2024-01-15: 食費 - 1500円 (昼食)
expense2.show() # 2024-01-15: 交通費 - 300円
家計簿管理クラス:
class HouseholdAccount:
def __init__(self):
self.expenses = []
def add_expense(self, date, category, amount, description=""):
expense = Expense(date, category, amount, description)
self.expenses.append(expense)
print(f"支出を追加しました: {category} {amount}円")
def show_all_expenses(self):
print("=== 支出一覧 ===")
for expense in self.expenses:
expense.show()
def get_total_by_category(self, category):
total = sum(expense.amount for expense in self.expenses
if expense.category == category)
return total
def get_monthly_total(self, year_month):
total = sum(expense.amount for expense in self.expenses
if expense.date.startswith(year_month))
return total
def show_category_summary(self):
categories = {}
for expense in self.expenses:
if expense.category in categories:
categories[expense.category] += expense.amount
else:
categories[expense.category] = expense.amount
print("=== カテゴリ別集計 ===")
for category, total in categories.items():
print(f"{category}: {total}円")
# 実際に使ってみる
account = HouseholdAccount()
# 支出を追加
account.add_expense("2024-01-15", "食費", 1500, "昼食")
account.add_expense("2024-01-15", "交通費", 300, "電車代")
account.add_expense("2024-01-16", "食費", 2000, "夕食")
account.add_expense("2024-01-16", "娯楽", 1200, "映画")
account.add_expense("2024-01-17", "食費", 800, "朝食")
# 一覧表示
account.show_all_expenses()
print()
# カテゴリ別集計
account.show_category_summary()
print()
# 特定カテゴリの合計
food_total = account.get_total_by_category("食費")
print(f"食費の合計: {food_total}円")
# 月別合計
january_total = account.get_monthly_total("2024-01")
print(f"1月の合計: {january_total}円")
ゲーム開発での応用
RPGゲームのキャラクター管理:
class Character:
def __init__(self, name, hp, attack, defense):
self.name = name
self.max_hp = hp
self.hp = hp
self.attack = attack
self.defense = defense
self.level = 1
self.exp = 0
def take_damage(self, damage):
actual_damage = max(1, damage - self.defense)
self.hp = max(0, self.hp - actual_damage)
print(f"{self.name}は{actual_damage}のダメージを受けた!")
if self.hp == 0:
print(f"{self.name}は倒れた...")
return True
else:
print(f"{self.name}の残りHP: {self.hp}/{self.max_hp}")
return False
def heal(self, amount):
old_hp = self.hp
self.hp = min(self.max_hp, self.hp + amount)
healed = self.hp - old_hp
print(f"{self.name}は{healed}回復した! HP: {self.hp}/{self.max_hp}")
def attack_target(self, target):
print(f"{self.name}は{target.name}を攻撃!")
return target.take_damage(self.attack)
class Player(Character):
def __init__(self, name):
super().__init__(name, hp=100, attack=20, defense=5)
self.items = []
def use_item(self, item_name):
if item_name in self.items:
self.items.remove(item_name)
if item_name == "回復薬":
self.heal(30)
print("回復薬を使用した!")
return True
else:
print(f"{item_name}を持っていません")
return False
def add_item(self, item_name):
self.items.append(item_name)
print(f"{item_name}を手に入れた!")
class Enemy(Character):
def __init__(self, name, hp, attack, defense, exp_reward):
super().__init__(name, hp, attack, defense)
self.exp_reward = exp_reward
# ゲームの実行例
player = Player("勇者")
enemy = Enemy("スライム", 50, 15, 2, 10)
player.add_item("回復薬")
print("=== バトル開始 ===")
while player.hp > 0 and enemy.hp > 0:
# プレイヤーの攻撃
enemy_defeated = player.attack_target(enemy)
if enemy_defeated:
print(f"{enemy.name}を倒した!")
break
# 敵の攻撃
player_defeated = enemy.attack_target(player)
if player_defeated:
print("ゲームオーバー...")
break
# 回復アイテム使用
if player.hp < 30 and "回復薬" in player.items:
player.use_item("回復薬")
特殊メソッド(マジックメソッド)

よく使われる特殊メソッド
特殊メソッドとは: __
で囲まれた、Pythonが特別に扱うメソッド
主要な特殊メソッド:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
# print()で呼ばれる
return f"『{self.title}』 著者: {self.author}"
def __repr__(self):
# デバッグ用の表現
return f"Book('{self.title}', '{self.author}', {self.pages})"
def __len__(self):
# len()関数で呼ばれる
return self.pages
def __eq__(self, other):
# == で比較されたときに呼ばれる
if isinstance(other, Book):
return (self.title == other.title and
self.author == other.author)
return False
def __lt__(self, other):
# < で比較されたときに呼ばれる
if isinstance(other, Book):
return self.pages < other.pages
return NotImplemented
# 使用例
book1 = Book("Python入門", "山田太郎", 300)
book2 = Book("Web開発", "佐藤花子", 450)
book3 = Book("Python入門", "山田太郎", 300)
print(book1) # 『Python入門』 著者: 山田太郎
print(repr(book1)) # Book('Python入門', '山田太郎', 300)
print(len(book1)) # 300
print(book1 == book3) # True
print(book1 == book2) # False
print(book1 < book2) # True (ページ数で比較)
# リストのソートも可能
books = [book2, book1]
books.sort() # ページ数でソート
for book in books:
print(book)
プロパティとアクセス制御
プロパティの使い方
@propertyデコレータ:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("絶対零度より低い温度は設定できません")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
@property
def kelvin(self):
return self._celsius + 273.15
# 使用例
temp = Temperature(25)
print(f"摂氏: {temp.celsius}°C") # 25.0°C
print(f"華氏: {temp.fahrenheit}°F") # 77.0°F
print(f"ケルビン: {temp.kelvin}K") # 298.15K
# 華氏で設定
temp.fahrenheit = 86
print(f"摂氏: {temp.celsius}°C") # 30.0°C
# エラーチェック
try:
temp.celsius = -300
except ValueError as e:
print(f"エラー: {e}")
プライベート属性
Pythonでの慣習:
class BankAccount:
def __init__(self, owner, initial_balance=0):
self.owner = owner
self._balance = initial_balance # プライベート(慣習)
self.__account_number = self._generate_account_number() # より強いプライベート
def _generate_account_number(self):
# プライベートメソッド
import random
return f"{random.randint(1000000, 9999999)}"
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
return True
return False
def get_account_info(self):
return f"口座名義: {self.owner}, 口座番号: {self.__account_number}"
# 使用例
account = BankAccount("田中太郎", 10000)
print(account.balance) # 10000 (プロパティ経由)
print(account.get_account_info())
# プライベート属性への直接アクセス(推奨されない)
print(account._balance) # 10000 (アクセス可能だが推奨されない)
# より強いプライベート属性(名前マングリング)
# print(account.__account_number) # エラー
print(account._BankAccount__account_number) # アクセス可能だが推奨されない
よくある間違いと対処法

よくある初心者の間違い
間違い1:selfを忘れる
# ❌ 間違い
class Calculator:
def add(a, b): # selfがない
return a + b
# ✅ 正解
class Calculator:
def add(self, a, b):
return a + b
間違い2:インスタンス変数とクラス変数の混同
# ❌ 間違い(共有されてしまう)
class Student:
subjects = [] # クラス変数(すべてのインスタンスで共有)
def __init__(self, name):
self.name = name
def add_subject(self, subject):
self.subjects.append(subject)
# ✅ 正解
class Student:
def __init__(self, name):
self.name = name
self.subjects = [] # インスタンス変数
def add_subject(self, subject):
self.subjects.append(subject)
間違い3:ミュータブルなデフォルト引数
# ❌ 間違い
class Classroom:
def __init__(self, students=[]): # 危険!
self.students = students
# ✅ 正解
class Classroom:
def __init__(self, students=None):
self.students = students if students is not None else []
クラス設計のベストプラクティス
1. 単一責任の原則
# ❌ 悪い例:1つのクラスで複数の責任
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self):
# データベース操作
pass
def send_email(self):
# メール送信
pass
def generate_report(self):
# レポート生成
pass
# ✅ 良い例:責任を分離
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user):
# データベース操作
pass
class EmailService:
def send_email(self, user, message):
# メール送信
pass
class ReportGenerator:
def generate_user_report(self, user):
# レポート生成
pass
高度なクラス機能
クラスメソッドとスタティックメソッド
@classmethod と @staticmethod の使い分け:
import datetime
class Person:
population = 0 # クラス変数
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
Person.population += 1
def get_age(self):
# インスタンスメソッド
current_year = datetime.datetime.now().year
return current_year - self.birth_year
@classmethod
def from_birth_date(cls, name, birth_date):
# クラスメソッド:別のコンストラクタとして使用
birth_year = birth_date.year
return cls(name, birth_year)
@classmethod
def get_population(cls):
# クラスメソッド:クラス変数へのアクセス
return cls.population
@staticmethod
def is_adult(age):
# スタティックメソッド:ユーティリティ関数
return age >= 18
@staticmethod
def calculate_age_from_year(birth_year):
# スタティックメソッド:年からの年齢計算
current_year = datetime.datetime.now().year
return current_year - birth_year
# 使用例
# 通常のインスタンス作成
person1 = Person("田中", 1990)
# クラスメソッドを使ったインスタンス作成
birth_date = datetime.date(1985, 5, 15)
person2 = Person.from_birth_date("佐藤", birth_date)
# インスタンスメソッド
print(f"{person1.name}の年齢: {person1.get_age()}歳")
# クラスメソッド
print(f"現在の人口: {Person.get_population()}人")
# スタティックメソッド
age = Person.calculate_age_from_year(2000)
print(f"2000年生まれの人の年齢: {age}歳")
print(f"成人かどうか: {Person.is_adult(age)}")
抽象クラスと抽象メソッド
ABCモジュールの使用:
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def calculate_area(self):
"""面積を計算する(サブクラスで実装必須)"""
pass
@abstractmethod
def calculate_perimeter(self):
"""周囲の長さを計算する(サブクラスで実装必須)"""
pass
def display_info(self):
"""共通メソッド"""
print(f"図形: {self.name}")
print(f"面積: {self.calculate_area()}")
print(f"周囲の長さ: {self.calculate_perimeter()}")
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("長方形")
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
def calculate_perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
super().__init__("円")
self.radius = radius
def calculate_area(self):
import math
return math.pi * self.radius ** 2
def calculate_perimeter(self):
import math
return 2 * math.pi * self.radius
# 使用例
rectangle = Rectangle(5, 3)
circle = Circle(4)
rectangle.display_info()
print()
circle.display_info()
# 抽象クラスは直接インスタンス化できない
# shape = Shape("図形") # TypeError が発生
実際のプロジェクトでの活用例
Webアプリケーションでのモデル設計
簡単なブログシステム:
from datetime import datetime
from typing import List, Optional
class User:
def __init__(self, username: str, email: str, password_hash: str):
self.id: Optional[int] = None
self.username = username
self.email = email
self.password_hash = password_hash
self.created_at = datetime.now()
self.posts: List['BlogPost'] = []
def create_post(self, title: str, content: str) -> 'BlogPost':
post = BlogPost(title, content, self)
self.posts.append(post)
return post
def get_post_count(self) -> int:
return len(self.posts)
def __str__(self):
return f"User({self.username})"
class BlogPost:
def __init__(self, title: str, content: str, author: User):
self.id: Optional[int] = None
self.title = title
self.content = content
self.author = author
self.created_at = datetime.now()
self.updated_at = datetime.now()
self.comments: List['Comment'] = []
self.tags: List[str] = []
def update_content(self, new_title: str = None, new_content: str = None):
if new_title:
self.title = new_title
if new_content:
self.content = new_content
self.updated_at = datetime.now()
def add_comment(self, content: str, author: User) -> 'Comment':
comment = Comment(content, author, self)
self.comments.append(comment)
return comment
def add_tag(self, tag: str):
if tag not in self.tags:
self.tags.append(tag)
def get_summary(self, length: int = 100) -> str:
if len(self.content) <= length:
return self.content
return self.content[:length] + "..."
def __str__(self):
return f"BlogPost('{self.title}' by {self.author.username})"
class Comment:
def __init__(self, content: str, author: User, post: BlogPost):
self.id: Optional[int] = None
self.content = content
self.author = author
self.post = post
self.created_at = datetime.now()
def __str__(self):
return f"Comment by {self.author.username} on {self.post.title}"
class Blog:
def __init__(self, title: str):
self.title = title
self.users: List[User] = []
self.posts: List[BlogPost] = []
def register_user(self, username: str, email: str, password_hash: str) -> User:
user = User(username, email, password_hash)
self.users.append(user)
return user
def publish_post(self, post: BlogPost):
if post not in self.posts:
self.posts.append(post)
def get_recent_posts(self, limit: int = 10) -> List[BlogPost]:
sorted_posts = sorted(self.posts, key=lambda p: p.created_at, reverse=True)
return sorted_posts[:limit]
def search_posts(self, keyword: str) -> List[BlogPost]:
results = []
for post in self.posts:
if (keyword.lower() in post.title.lower() or
keyword.lower() in post.content.lower() or
keyword.lower() in post.tags):
results.append(post)
return results
# 使用例
blog = Blog("私のブログ")
# ユーザー登録
user1 = blog.register_user("alice", "alice@example.com", "hashed_password_123")
user2 = blog.register_user("bob", "bob@example.com", "hashed_password_456")
# 記事投稿
post1 = user1.create_post("Pythonクラスの基本", "Pythonのクラスについて学びました...")
post1.add_tag("Python")
post1.add_tag("プログラミング")
blog.publish_post(post1)
post2 = user2.create_post("Web開発入門", "HTMLとCSSを使ってWebページを作りました...")
post2.add_tag("Web開発")
post2.add_tag("HTML")
blog.publish_post(post2)
# コメント投稿
comment1 = post1.add_comment("とても参考になりました!", user2)
# ブログの情報表示
print(f"ブログタイトル: {blog.title}")
print(f"登録ユーザー数: {len(blog.users)}")
print(f"投稿数: {len(blog.posts)}")
print("\n最新の投稿:")
for post in blog.get_recent_posts(5):
print(f"- {post.title} by {post.author.username}")
print(f" {post.get_summary(50)}")
print(f" タグ: {', '.join(post.tags)}")
print(f" コメント数: {len(post.comments)}")
print()
# 検索機能
search_results = blog.search_posts("Python")
print(f"'Python'で検索した結果: {len(search_results)}件")
for post in search_results:
print(f"- {post.title}")
デバッグとテストのためのクラス設計
テストしやすいクラスの作り方
依存関係の注入:
class EmailSender:
def send(self, to: str, subject: str, body: str):
# 実際のメール送信処理
print(f"メール送信: {to} - {subject}")
class MockEmailSender:
def __init__(self):
self.sent_emails = []
def send(self, to: str, subject: str, body: str):
# テスト用のモック
self.sent_emails.append({
'to': to,
'subject': subject,
'body': body
})
class UserService:
def __init__(self, email_sender):
self.email_sender = email_sender
def register_user(self, username: str, email: str):
# ユーザー登録処理
user = User(username, email, "dummy_hash")
# ウェルカムメール送信
self.email_sender.send(
email,
"登録完了",
f"{username}さん、登録ありがとうございます!"
)
return user
# 本番環境
production_service = UserService(EmailSender())
production_service.register_user("alice", "alice@example.com")
# テスト環境
mock_sender = MockEmailSender()
test_service = UserService(mock_sender)
test_service.register_user("test_user", "test@example.com")
# テスト結果確認
print(f"送信されたメール数: {len(mock_sender.sent_emails)}")
print(f"最初のメール: {mock_sender.sent_emails[0]}")
まとめ
Pythonのクラスは、プログラムを「現実の物事のように」モデル化する強力な手段です。
今日学んだ重要ポイント:
クラスの基本:
- classでクラス定義、initで初期化
- selfは「自分自身」を指す特別な変数
- メソッドでオブジェクトの動作を定義
重要な概念:
- インスタンス変数:オブジェクトごとに独立した値
- クラス変数:すべてのオブジェクトで共有される値
- 継承:既存クラスを元に新しいクラスを作成
- オーバーライド:親クラスのメソッドを子クラスで再定義
実用的なテクニック:
- 特殊メソッド:
__str__
、__len__
など - プロパティ:
@property
デコレータ - クラスメソッド:
@classmethod
- スタティックメソッド:
@staticmethod
設計のポイント:
- 単一責任の原則:1つのクラスは1つの責任
- カプセル化:データと処理をまとめる
- 継承の適切な使用:is-a関係でのみ使用
- テストしやすい設計:依存関係の注入
よくある間違い:
- selfを忘れる
- クラス変数とインスタンス変数の混同
- ミュータブルなデフォルト引数
コメント