Pythonクラスとオブジェクトの概念

2025-10-25

はじめに

Pythonプログラミングを学ぶ上で、「クラスとオブジェクト」の理解は非常に重要です。これらはオブジェクト指向プログラミング(OOP)の核となる概念で、大規模なプログラムを整理し、保守しやすくするための強力なツールを提供します。

現実世界を考えてみましょう。私たちは「車」という概念を知っています。特定の車(例えば、あなたの車や友人の車)は「車」というカテゴリの具体的な実例です。プログラミングの世界でも同様に、「クラス」は設計図やテンプレートであり、「オブジェクト」はその設計図に基づいて作成された具体的な実体です。

クラスとは何か

クラスは、オブジェクトの設計図やテンプレートとして機能するコードの構造です。クラスは、オブジェクトが持つデータ(属性)と振る舞い(メソッド)を定義します。

例えば、自動車を表すクラスを考えてみましょう。

class Car:
    pass

これは最も単純なクラス定義です。classキーワードを使ってクラスを定義し、クラス名(ここではCar)を指定します。クラス名は慣習的に大文字で始まります。

オブジェクト(インスタンス)の作成

クラスからオブジェクト(インスタンス)を作成するには、クラス名の後に括弧を付けます。

my_car = Car()
your_car = Car()

ここで、my_caryour_carはどちらもCarクラスのインスタンスですが、メモリ上では別々のオブジェクトです。これらは同じ設計図から作られていますが、独立して存在しています。

属性:オブジェクトのデータ

属性はオブジェクトに結びついたデータです。オブジェクトの状態を表します。Pythonでは、簡単に属性を追加できます。

my_car.color = "red"
my_car.speed = 0
your_car.color = "blue"
your_car.speed = 0

ここでは、my_carオブジェクトにcolorspeed属性を追加し、値を設定しました。your_carにも同じ属性名ですが異なる値を持たせています。

メソッド:オブジェクトの振る舞い

メソッドはクラス内で定義された関数で、オブジェクトの振る舞いを定義します。すべてのメソッドの最初の引数はselfであり、これはメソッドが呼び出されたオブジェクト自身を参照します。

class Car:
    def start_engine(self):
        self.engine_running = True
        print("エンジンが始動しました")

    def accelerate(self, amount):
        if hasattr(self, 'engine_running') and self.engine_running:
            self.speed += amount
            print(f"速度が {self.speed} km/h になりました")
        else:
            print("まずエンジンを始動してください")

コンストラクタ:initメソッド

__init__メソッドは特殊なメソッドで、コンストラクタとして機能します。オブジェクトが作成されるときに自動的に呼び出され、オブジェクトの初期状態を設定します。

class Car:
    def __init__(self, color, model):
        self.color = color
        self.model = model
        self.speed = 0
        self.engine_running = False

    def start_engine(self):
        self.engine_running = True
        print("エンジンが始動しました")

    def accelerate(self, amount):
        if self.engine_running:
            self.speed += amount
            print(f"速度が {self.speed} km/h になりました")
        else:
            print("まずエンジンを始動してください")

Car クラスには、車の色やモデル、速度、エンジンの状態といった属性が定義されています。
__init__ メソッドで初期化し、start_engine() でエンジンを始動、accelerate() で加速処理を行います。ただし、エンジンが停止している状態で accelerate() を呼ぶと警告メッセージを出す仕組みになっています。

これで、Carオブジェクトを作成するときに色とモデルを指定できます。

my_car = Car("red", "Sedan")
your_car = Car("blue", "SUV")

なぜクラスとオブジェクトを使うのか

クラスとオブジェクトを使うのは、現実世界の概念をプログラムで整理・再利用しやすくするためです。クラスは設計図、オブジェクトはその実体として扱えるため、コードの再利用性・保守性・拡張性が高まり、複雑なプログラムを効率的に構築できます。

  1. 整理と構造化: 関連するデータと機能を1つの単位にまとめることができます
  2. 再利用性: 同じクラスから複数のオブジェクトを作成できます
  3. 保守性: コードの変更が容易になります。クラス内の変更がすべてのオブジェクトに反映されます
  4. モデリング: 現実世界の概念を自然に表現できます

クラスとオブジェクトを使用しない場合のデメリット

大規模開発での管理困難

関連するデータと関数がバラバラになり、コードの依存関係が複雑化します。プロジェクトが成長するにつれて、どの関数がどのデータを操作するのか追跡できなくなります。

状態管理の煩雑さ

グローバル変数や複数のデータ構造に状態が分散し、予期しない副作用が発生しやすくなります。データの整合性を保つことが難しく、バグの原因となります。

コードの再利用性低下

類似した処理を毎回一から実装する必要があり、生産性が低下します。共通ロジックのメンテナンスも困難で、変更時に複数箇所を修正する必要が生じます。

実世界の例:銀行口座

クラスとオブジェクトの概念をより理解するために、銀行口座をモデル化した例を見てみましょう。

__init__ メソッドで口座名義・初期残高・ランダムな口座番号を設定します。
deposit() で入金、withdraw() で出金、get_balance() で残高取得、display_account_info() で口座情報を表示できます。また、_generate_account_number() は内部的に呼び出されるメソッド(先頭に _)で、ランダムな番号を生成します。

class BankAccount:
    def __init__(self, account_holder, initial_balance=0):
        self.account_holder = account_holder
        self.balance = initial_balance
        self.account_number = self._generate_account_number()

    def _generate_account_number(self):
        # 実際のアプリケーションではより複雑なロジックを使用
        import random
        return f"ACC{random.randint(10000, 99999)}"

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました。新しい残高: {self.balance}円")
        else:
            print("預入額は0より大きい必要があります")

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}円引き出しました。新しい残高: {self.balance}円")
        else:
            print("残高不足または無効な金額です")

    def get_balance(self):
        return self.balance

    def display_account_info(self):
        print(f"口座名義: {self.account_holder}")
        print(f"口座番号: {self.account_number}")
        print(f"残高: {self.balance}円")

このクラスを使用する例です。BankAccount クラスをもとに、account1account2 という2つの口座オブジェクトを作成しています。それぞれのオブジェクトが独立したデータ(名前と残高)を持ち、deposit()withdraw()display_account_info() などのメソッドを通じて入金、出金、情報表示といった操作を行います。

# 口座オブジェクトの作成
account1 = BankAccount("山田太郎", 1000)
account2 = BankAccount("佐藤花子", 5000)

# メソッドの使用
account1.deposit(500)
account1.withdraw(200)
account1.display_account_info()

account2.deposit(1000)
account2.display_account_info()

まとめ

クラスとオブジェクトは、Pythonのオブジェクト指向プログラミングの基礎です。クラスは設計図として機能し、オブジェクトはその設計図に基づいて作成された具体的な実体です。属性はオブジェクトの状態を、メソッドはオブジェクトの振る舞いを表します。__init__メソッドはコンストラクタとして、オブジェクトの初期化を担当します。

この基礎的理解があれば、より高度な概念(継承、ポリモーフィズム、カプセル化など)を学ぶ準備が整いました。これらの概念は、より複雑で強力なプログラムを作成するための基盤を提供します。


演習問題

初級問題

問題1
次のコードの出力を予想してください。

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name}がワンワン吠えています!")

my_dog = Dog("ポチ")
my_dog.bark()

問題2
簡単なBookクラスを作成してください。このクラスはtitle(タイトル)とauthor(著者)を属性として持ち、display_infoメソッドで「[タイトル] by [著者]」という形式で情報を表示するようにしてください。

問題3
次のクラスからオブジェクトを作成し、introduceメソッドを呼び出してください。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"私は{self.name}です。{self.age}歳です。")

中級問題

問題4
Rectangleクラスを作成してください。このクラスはwidth(幅)とheight(高さ)を属性として持ち、面積を計算するareaメソッドと周囲の長さを計算するperimeterメソッドを含めてください。

問題5
BankAccountクラスを拡張して、interest_rate(利率)属性と利息を計算するcalculate_interestメソッドを追加してください。利息は「残高 × 利率」で計算されます。

問題6
Studentクラスを作成してください。このクラスは学生の名前と複数の科目の点数を管理し、平均点を計算するメソッドを含めてください。

問題7
次のコードの間違いを修正してください。

class Cat:
    def __init__(self, name, age)
        self.name = name
        self.age = age

    def meow()
        print(f"{name}がニャーと鳴いています")

my_cat = Cat("タマ", 3)
my_cat.meow()

問題8
Calculatorクラスを作成してください。このクラスは加算、減算、乗算、除算のメソッドを持ち、計算結果を保持するresult属性を持ってください。各計算メソッドは結果を更新し、新しい結果を返すようにしてください。

問題9
次のクラスを使用して、2人の従業員オブジェクトを作成し、給与を増加させた後、情報を表示してください。

class Employee:
    def __init__(self, name, position, salary):
        self.name = name
        self.position = position
        self.salary = salary

    def increase_salary(self, percent):
        self.salary += self.salary * percent / 100

    def display_info(self):
        print(f"名前: {self.name}")
        print(f"役職: {self.position}")
        print(f"給与: {self.salary}円")

上級問題

問題10
Libraryクラスを作成してください。このクラスは複数のBookオブジェクトを管理し、本を追加、検索、一覧表示するメソッドを持ってください。Bookクラスも適切に定義してください。

問題11
Vehicleという基本クラスと、それを継承したCarクラスとMotorcycleクラスを作成してください。各クラスは適切な属性とメソッドを持ち、ポリモーフィズムを実演するコードを書いてください。

問題12
完全なProduct在庫管理システムを作成してください。ProductクラスとInventoryクラスを定義し、商品の追加、削除、検索、在庫数の更新、在庫一覧の表示などの機能を含めてください。

初級問題

問題1 解答

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name}がワンワン吠えています!")

my_dog = Dog("ポチ")
my_dog.bark()

出力結果:

ポチがワンワン吠えています!

解説: Dogクラスのインスタンスを作成し、barkメソッドを呼び出すことで、オブジェクトの属性であるnameを使用したメッセージが表示されます。

問題2 解答

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def display_info(self):
        print(f"『{self.title}』 by {self.author}")

# 使用例
book1 = Book("Python入門", "山田太郎")
book1.display_info()

出力結果:

『Python入門』 by 山田太郎

解説: コンストラクタでタイトルと著者を受け取り、display_infoメソッドで整形して表示します。

問題3 解答

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"私は{self.name}です。{self.age}歳です。")

# オブジェクト作成とメソッド呼び出し
person1 = Person("鈴木一郎", 25)
person1.introduce()

person2 = Person("田中美咲", 30)
person2.introduce()

出力結果:

私は鈴木一郎です。25歳です。 私は田中美咲です。30歳です。

解説: Personクラスから2つのオブジェクトを作成し、それぞれのintroduceメソッドを呼び出しています。

中級問題

問題4 解答

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

# 使用例
rect = Rectangle(5, 3)
print(f"面積: {rect.area()}")      # 出力: 面積: 15
print(f"周囲の長さ: {rect.perimeter()}")  # 出力: 周囲の長さ: 16

解説: 四角形の面積と周囲の長さを計算するメソッドを実装しました。

問題5 解答

class BankAccount:
    def __init__(self, account_holder, initial_balance=0, interest_rate=0.02):
        self.account_holder = account_holder
        self.balance = initial_balance
        self.interest_rate = interest_rate
        self.account_number = self._generate_account_number()

    def _generate_account_number(self):
        import random
        return f"ACC{random.randint(10000, 99999)}"

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました。新しい残高: {self.balance}円")
        else:
            print("預入額は0より大きい必要があります")

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}円引き出しました。新しい残高: {self.balance}円")
        else:
            print("残高不足または無効な金額です")

    def calculate_interest(self):
        interest = self.balance * self.interest_rate
        print(f"現在の利息: {interest}円")
        return interest

    def display_account_info(self):
        print(f"口座名義: {self.account_holder}")
        print(f"口座番号: {self.account_number}")
        print(f"残高: {self.balance}円")
        print(f"利率: {self.interest_rate * 100}%")

# 使用例
account = BankAccount("山田太郎", 100000, 0.03)
account.display_account_info()
account.calculate_interest()

解説: 利率属性と利息計算メソッドを追加しました。

問題6 解答

class Student:
    def __init__(self, name):
        self.name = name
        self.grades = {}  # 科目名をキー、点数を値とする辞書

    def add_grade(self, subject, score):
        self.grades[subject] = score
        print(f"{subject}の点数 {score}点 を追加しました")

    def calculate_average(self):
        if not self.grades:
            print("点数が登録されていません")
            return 0

        total = sum(self.grades.values())
        average = total / len(self.grades)
        return average

    def display_grades(self):
        print(f"{self.name}さんの成績:")
        for subject, score in self.grades.items():
            print(f"  {subject}: {score}点")
        if self.grades:
            print(f"平均点: {self.calculate_average():.1f}点")

# 使用例
student = Student("佐藤花子")
student.add_grade("数学", 85)
student.add_grade("英語", 92)
student.add_grade("国語", 78)
student.display_grades()

出力結果:

数学の点数 85点 を追加しました
英語の点数 92点 を追加しました
国語の点数 78点 を追加しました
佐藤花子さんの成績:
数学: 85点
英語: 92点
国語: 78点
平均点: 85.0点

問題7 解答(修正版)

class Cat:
    def __init__(self, name, age):  # コロンが不足していた
        self.name = name
        self.age = age

    def meow(self):  # コロンが不足していた
        print(f"{self.name}がニャーと鳴いています")  # self.nameに修正

my_cat = Cat("タマ", 3)
my_cat.meow()

修正点:

  1. __init__メソッドの定義行にコロンを追加
  2. meowメソッドの定義行にコロンを追加
  3. meowメソッド内でself.nameを使用(インスタンス変数にアクセスするため)

問題8 解答

class Calculator:
    def __init__(self, initial_value=0):
        self.result = initial_value

    def add(self, value):
        self.result += value
        return self.result

    def subtract(self, value):
        self.result -= value
        return self.result

    def multiply(self, value):
        self.result *= value
        return self.result

    def divide(self, value):
        if value != 0:
            self.result /= value
            return self.result
        else:
            print("エラー: 0で割ることはできません")
            return self.result

    def clear(self):
        self.result = 0
        return self.result

    def get_result(self):
        return self.result

# 使用例
calc = Calculator(10)
print(f"初期値: {calc.get_result()}")
print(f"加算: {calc.add(5)}")
print(f"乗算: {calc.multiply(2)}")
print(f"減算: {calc.subtract(3)}")
print(f"除算: {calc.divide(4)}")
print(f"最終結果: {calc.get_result()}")

出力結果:

初期値: 10
加算: 15
乗算: 30
減算: 27
除算: 6.75
最終結果: 6.75

問題9 解答

class Employee:
    def __init__(self, name, position, salary):
        self.name = name
        self.position = position
        self.salary = salary

    def increase_salary(self, percent):
        self.salary += self.salary * percent / 100

    def display_info(self):
        print(f"名前: {self.name}")
        print(f"役職: {self.position}")
        print(f"給与: {self.salary}円")
        print("-" * 20)

# 従業員オブジェクトの作成と操作
emp1 = Employee("山田太郎", "マネージャー", 500000)
emp2 = Employee("佐藤花子", "エンジニア", 400000)

print("給与増加前:")
emp1.display_info()
emp2.display_info()

# 給与を増加
emp1.increase_salary(10)  # 10%増加
emp2.increase_salary(15)  # 15%増加

print("給与増加後:")
emp1.display_info()
emp2.display_info()

出力結果:

給与増加前:
名前: 山田太郎
役職: マネージャー
給与: 500000円
--------------------
名前: 佐藤花子
役職: エンジニア
給与: 400000円
--------------------

給与増加後:
名前: 山田太郎
役職: マネージャー
給与: 550000.0円
--------------------
名前: 佐藤花子
役職: エンジニア
給与: 460000.0円
--------------------

上級問題

問題10 解答

class Book:
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn

    def __str__(self):
        return f"『{self.title}』 - {self.author} (ISBN: {self.isbn})"

class Library:
    def __init__(self):
        self.books = []

    def add_book(self, book):
        self.books.append(book)
        print(f"書籍 '{book.title}' を追加しました")

    def find_book_by_title(self, title):
        found_books = [book for book in self.books if title.lower() in book.title.lower()]
        return found_books

    def find_book_by_author(self, author):
        found_books = [book for book in self.books if author.lower() in book.author.lower()]
        return found_books

    def display_all_books(self):
        if not self.books:
            print("図書館に書籍はありません")
            return

        print("=== 図書館の蔵書一覧 ===")
        for i, book in enumerate(self.books, 1):
            print(f"{i}. {book}")
        print("========================")

# 使用例
library = Library()

# 書籍の追加
book1 = Book("Python入門", "山田太郎", "978-1234567890")
book2 = Book("機械学習実践", "佐藤花子", "978-0987654321")
book3 = Book("Pythonで学ぶデータ分析", "鈴木一郎", "978-1122334455")

library.add_book(book1)
library.add_book(book2)
library.add_book(book3)

# 全書籍表示
library.display_all_books()

# 書籍検索
print("Python関連書籍の検索結果:")
python_books = library.find_book_by_title("Python")
for book in python_books:
    print(f"  - {book}")

問題11 解答

class Vehicle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def start_engine(self):
        print(f"{self.brand} {self.model}のエンジンを始動します")

    def stop_engine(self):
        print(f"{self.brand} {self.model}のエンジンを停止します")

    def display_info(self):
        print(f"ブランド: {self.brand}")
        print(f"モデル: {self.model}")
        print(f"年式: {self.year}")

class Car(Vehicle):
    def __init__(self, brand, model, year, doors):
        super().__init__(brand, model, year)
        self.doors = doors

    def start_engine(self):  # オーバーライド
        print(f"{self.brand} {self.model}({self.doors}ドア)のエンジンを始動します")

    def display_info(self):  # オーバーライド
        super().display_info()
        print(f"ドア数: {self.doors}")

class Motorcycle(Vehicle):
    def __init__(self, brand, model, year, engine_size):
        super().__init__(brand, model, year)
        self.engine_size = engine_size

    def start_engine(self):  # オーバーライド
        print(f"{self.brand} {self.model}({self.engine_size}cc)のエンジンを始動します")

    def display_info(self):  # オーバーライド
        super().display_info()
        print(f"排気量: {self.engine_size}cc")

# ポリモーフィズムの実演
def operate_vehicle(vehicle):
    print("\n=== 車両操作 ===")
    vehicle.display_info()
    vehicle.start_engine()
    vehicle.stop_engine()

# 使用例
car = Car("Toyota", "Camry", 2023, 4)
motorcycle = Motorcycle("Honda", "CBR", 2023, 600)

# 同じインターフェースで異なる動作(ポリモーフィズム)
operate_vehicle(car)
operate_vehicle(motorcycle)

# リストに入れて一括操作
vehicles = [car, motorcycle]
print("\n=== 一括操作 ===")
for vehicle in vehicles:
    vehicle.start_engine()

問題12 解答

class Product:
    def __init__(self, product_id, name, price, quantity):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.quantity = quantity

    def update_quantity(self, new_quantity):
        self.quantity = new_quantity

    def update_price(self, new_price):
        self.price = new_price

    def __str__(self):
        return f"ID: {self.product_id} | {self.name} | 価格: {self.price}円 | 在庫: {self.quantity}個"

class Inventory:
    def __init__(self):
        self.products = {}
        self.next_id = 1

    def add_product(self, name, price, quantity):
        product_id = self.next_id
        product = Product(product_id, name, price, quantity)
        self.products[product_id] = product
        self.next_id += 1
        print(f"商品 '{name}' を追加しました (ID: {product_id})")
        return product_id

    def remove_product(self, product_id):
        if product_id in self.products:
            product_name = self.products[product_id].name
            del self.products[product_id]
            print(f"商品 '{product_name}' を削除しました")
        else:
            print(f"ID {product_id} の商品は見つかりません")

    def find_product_by_id(self, product_id):
        return self.products.get(product_id)

    def find_products_by_name(self, name):
        return [product for product in self.products.values() if name.lower() in product.name.lower()]

    def update_product_quantity(self, product_id, new_quantity):
        product = self.find_product_by_id(product_id)
        if product:
            product.update_quantity(new_quantity)
            print(f"商品 '{product.name}' の在庫数を {new_quantity} に更新しました")
        else:
            print(f"ID {product_id} の商品は見つかりません")

    def update_product_price(self, product_id, new_price):
        product = self.find_product_by_id(product_id)
        if product:
            product.update_price(new_price)
            print(f"商品 '{product.name}' の価格を {new_price}円 に更新しました")
        else:
            print(f"ID {product_id} の商品は見つかりません")

    def display_inventory(self):
        if not self.products:
            print("在庫は空です")
            return

        print("\n=== 在庫一覧 ===")
        total_value = 0
        for product in self.products.values():
            print(product)
            total_value += product.price * product.quantity
        print(f"在庫総額: {total_value}円")
        print("================\n")

# 使用例
inventory = Inventory()

# 商品の追加
p1_id = inventory.add_product("ノートパソコン", 150000, 10)
p2_id = inventory.add_product("ワイヤレスマウス", 5000, 50)
p3_id = inventory.add_product("USBメモリ 64GB", 3000, 100)

# 在庫表示
inventory.display_inventory()

# 在庫数の更新
inventory.update_product_quantity(p1_id, 8)
inventory.update_product_price(p2_id, 4500)

# 商品検索
print("マウス関連商品の検索結果:")
mouse_products = inventory.find_products_by_name("マウス")
for product in mouse_products:
    print(f"  - {product}")

# 最終在庫表示
inventory.display_inventory()

# 商品削除
inventory.remove_product(p3_id)
inventory.display_inventory()