メソッドと属性 – オブジェクトの状態と振る舞い

2025-10-26

はじめに

今回は、オブジェクト指向プログラミングの中核をなす「メソッドと属性」について深く探っていきます。メソッドと属性は、オブジェクトが持つ「状態」と「振る舞い」を定義する重要な要素です。現実世界で例えるなら、人間の「身長」「体重」「名前」などが属性に相当し、「歩く」「話す」「食べる」などの行動がメソッドに相当します。

属性:オブジェクトの状態を表現する

属性はオブジェクトに結びついたデータで、オブジェクトの状態や特性を表します。Pythonでは、属性は非常に柔軟に扱うことができます。

インスタンス属性

インスタンス属性は、個々のオブジェクトに固有のデータを保持します。
各オブジェクトは同じ属性名を持っていても、異なる値を持つことができます。

class Student:
    def __init__(self, name, age):
        self.name = name      # インスタンス属性
        self.age = age        # インスタンス属性
        self.grades = []      # インスタンス属性

# 異なるオブジェクトは異なる属性値を持つ
student1 = Student("山田太郎", 20)
student2 = Student("佐藤花子", 22)

print(student1.name)  # 山田太郎
print(student2.name)  # 佐藤花子

nameagegrades はそれぞれのオブジェクト(インスタンス)専用のデータで、student1student2 は異なる値を持ちます。同じクラスから作られても、各インスタンスが独立した状態を持つことがオブジェクト指向の基本的な特徴です。

クラス属性

クラス属性は、クラス全体で共有される属性です。すべてのインスタンスで同じ値を持ちます。次のコードはその例になります。

class Student:
    school_name = "東京大学"  # クラス属性

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

student1 = Student("山田太郎", 20)
student2 = Student("佐藤花子", 22)

print(student1.school_name)  # 東京大学
print(student2.school_name)  # 東京大学
print(Student.school_name)   # 東京大学

school_name は全インスタンスで共有される値であり、student1student2 など個々のオブジェクトからも同じ値「東京大学」にアクセスできます。インスタンスごとに異なる nameage とは異なり、クラス全体で共通の情報(学校名など)を保持するのに適しています。

属性の動的追加

Pythonでは、実行時にオブジェクトに新しい属性を動的に追加することができます。

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

my_car = Car("Toyota", "Camry")
my_car.color = "red"  # 実行時に属性を追加
my_car.year = 2023    # 実行時に属性を追加

print(f"{my_car.color}色の{my_car.brand} {my_car.model}")

Car クラス自体には coloryear は定義されていませんが、インスタンス生成後に my_car.colormy_car.year を代入することで実行時に新しい属性を持たせています。Pythonでは柔軟にインスタンスへ属性を追加できるため、動的なデータ拡張が可能です。

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

メソッドはクラス内で定義された関数で、オブジェクトが実行できる操作を定義します。

インスタンスメソッド

インスタンスメソッドは、最も一般的なメソッドの種類です。
最初の引数としてselfを取り、このselfを通じてオブジェクトの属性や他のメソッドにアクセスします。

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

    # インスタンスメソッド
    def deposit(self, amount):
        """預金を行うメソッド"""
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました。残高: {self.balance}円")
        else:
            print("預入額は正の値である必要があります")

    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

BankAccount クラスで銀行口座を表現しています。account_holder(口座名義)と balance(残高)を保持し、deposit() で預金、withdraw() で引き出し、get_balance() で残高確認を行います。

インスタンスメソッドを使って各口座ごとの操作を実現しており、金額の妥当性や残高不足もチェックする安全な設計になっています。

プライベートメソッド

プライベートメソッドとは、クラス内部でのみ利用されることを意図したメソッドで、名前の先頭に「_(アンダースコア1つ)」を付けて定義します。

class DataProcessor:
    def __init__(self, data):
        self.data = data

    def process_data(self):
        """公開メソッド:データ処理の主要インターフェース"""
        self.__validate_data()
        self.__clean_data()
        result = self.__analyze_data()
        return result

    def _validate_data(self):
        """プライベートメソッド:データの検証"""
        # 検証ロジック
        pass

    def _clean_data(self):
        """プライベートメソッド:データのクリーニング"""
        # クリーニングロジック
        pass

    def _analyze_data(self):
        """プライベートメソッド:データの分析"""
        # 分析ロジック
        return "分析結果"

process_data() は外部から呼び出す公開メソッドで、内部的に _validate_data()(検証)、_clean_data()(整備)、_analyze_data()(分析)を順に実行します。
先頭にアンダースコアを付けたメソッドは外部から直接呼ばないことを意図した内部処理専用のメソッドです。

メソッドの先頭に「_(アンダースコア1つ)」は「非公開の目安(慣習)」を示します。開発者への合図であり慣例です、しかし外部からアクセスは可能ですので完全なプライベートではありません。では完全な「プライベートメソッド」は __(ダブルアンダースコア)で定義します。

class DataProcessor:
    def __init__(self, data):
        self.data = data

    def process_data(self):
        """公開メソッド:データ処理の主要インターフェース"""
        self.__validate_data()
        self.__clean_data()
        result = self.__analyze_data()
        return result

    def __validate_data(self):
        """プライベートメソッド:データの検証"""
        # 検証ロジック
        pass

    def __clean_data(self):
        """プライベートメソッド:データのクリーニング"""
        # クリーニングロジック
        pass

    def __analyze_data(self):
        """プライベートメソッド:データの分析"""
        # 分析ロジック
        return "分析結果"

クラス内部でのみ利用されることを意図したメソッドは、名前の先頭に「__(アンダースコア2つ)」を付けて定義します。外部から直接アクセスできず、クラスの内部実装を隠すために使われます。

プロパティ:属性アクセスを制御する

プロパティとは、クラスの属性に対してgetter・setter・deleterを定義し、メソッドのように処理を行いつつ属性のようにアクセスできる仕組みです。@propertyデコレーターを使って定義します。

class Temperature:
    def __init__(self, celsius):
        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

# 使用例
temp = Temperature(25)
print(f"摂氏: {temp.celsius}°C")    # 摂氏: 25°C
print(f"華氏: {temp.fahrenheit}°F") # 華氏: 77.0°F

temp.celsius = 30  # セッターを呼び出し
print(f"新しい温度: {temp.celsius}°C")

celsius はゲッターとセッターを持ち、値を取得・設定する際に検証(絶対零度以下はエラー)を行います。fahrenheit は読み取り専用プロパティで、摂氏から華氏を自動計算して返します。
属性を直接扱うように見えても、安全にアクセス制御を行える設計です。

クラスメソッドとスタティックメソッド

クラスメソッド

クラスメソッドは、インスタンスではなくクラス自体に結びついたメソッドです。
最初の引数はclsで、クラス自体を参照します。

class Employee:
    company = "ABC株式会社"
    employees_count = 0

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
        Employee.employees_count += 1

    @classmethod
    def get_company_info(cls):
        """クラスメソッド:会社情報を表示"""
        return f"会社名: {cls.company}, 従業員数: {cls.employees_count}"

    @classmethod
    def create_from_string(cls, employee_str):
        """クラスメソッド:文字列から従業員を作成"""
        name, salary_str = employee_str.split(",")
        salary = int(salary_str)
        return cls(name, salary)

# クラスメソッドの使用
print(Employee.get_company_info())

# 代替コンストラクタとして使用
emp_str = "山田太郎,500000"
employee = Employee.create_from_string(emp_str)

companyemployees_count は全インスタンスで共有され、社員が作成されるたびに従業員数が増えます。get_company_info() は会社情報を返し、create_from_string() は文字列(例: "山田太郎,500000")から新しい従業員オブジェクトを生成する代替コンストラクタとして機能します。

スタティックメソッド

スタティックメソッドは、クラスやインスタンスに依存しないユーティリティ関数です。
selfclsを受け取りません。

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def multiply(a, b):
        return a * b

    @staticmethod
    def is_even(number):
        return number % 2 == 0

# スタティックメソッドの使用
result = MathUtils.add(5, 3)
print(f"5 + 3 = {result}")

print(f"10は偶数ですか: {MathUtils.is_even(10)}")

MathUtils クラス内でスタティックメソッド(@staticmethod)を使って数学的な処理をまとめています。インスタンス化せずに MathUtils.add(5, 3) のように直接呼び出せます。add は加算、multiply は乗算、is_even は偶数判定を行います。汎用的な計算処理をまとめるのに便利な設計です。

属性とメソッドの命名規則

Pythonでは、属性とメソッドの命名について以下の慣習があります。

  • スネークケース: メソッド名と属性名は小文字で、単語の区切りにアンダースコアを使用(例: calculate_total
  • 公開メソッド: 通常の命名(例: process_data
  • 非公開メソッド: 先頭にアンダースコア1つ(例: _internal_method
  • マングリング: 先頭にアンダースコア2つ(例: __private_method
class Example:
    def __init__(self):
        self.public_attr = "公開"     # 公開属性
        self._protected_attr = "保護" # 保護属性(慣習的)
        self.__private_attr = "非公開" # 非公開属性(マングリング)

    def public_method(self):
        """公開メソッド"""
        pass

    def _protected_method(self):
        """保護メソッド(慣習的)"""
        pass

    def __private_method(self):
        """非公開メソッド(マングリング)"""
        pass

public_attrpublic_method() はどこからでもアクセスできる公開メンバー
_protected_attr_protected_method() は慣習的に内部用とされる保護メンバー
__private_attr__private_method() は名前が自動変換(マングリング)される非公開メンバーです。

Pythonでは厳密なアクセス制限はありませんが、このような命名規則でクラス内部の情報を意図的に隠すことができます。

実践的な例:Eコマースシステム

メソッドと属性の概念を実践的に理解するために、簡単なEコマースシステムを実装してみましょう。

Product クラスは商品を表し、ID・名前・価格・在庫を属性として持ちます。
update_stock() で在庫数を変更し、apply_discount() で割引を適用できます。
__str__() により、商品情報を文字列としてわかりやすく表示できます。

ShoppingCart クラスはカートを管理し、add_item() で商品を追加、remove_item() で削除します。
追加・削除時には自動的に商品の在庫を更新し、calculate_total() で合計金額を算出します。
display_cart() でカートの中身を確認でき、最終的に在庫の変動も反映されます。

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

    def update_stock(self, quantity):
        """在庫数を更新"""
        if self.stock + quantity >= 0:
            self.stock += quantity
            return True
        return False

    def apply_discount(self, percentage):
        """割引を適用"""
        if 0 <= percentage <= 100:
            self.price *= (1 - percentage / 100)
            return True
        return False

    def __str__(self):
        return f"{self.name} - ¥{self.price} (在庫: {self.stock})"

class ShoppingCart:
    def __init__(self):
        self.items = {}  # {product_id: quantity}
        self.total_price = 0

    def add_item(self, product, quantity=1):
        """商品をカートに追加"""
        if product.product_id in self.items:
            self.items[product.product_id] += quantity
        else:
            self.items[product.product_id] = quantity

        self.total_price += product.price * quantity
        product.update_stock(-quantity)

    def remove_item(self, product, quantity=1):
        """商品をカートから削除"""
        if product.product_id in self.items:
            if self.items[product.product_id] <= quantity:
                removed_quantity = self.items[product.product_id]
                del self.items[product.product_id]
            else:
                self.items[product.product_id] -= quantity
                removed_quantity = quantity

            self.total_price -= product.price * removed_quantity
            product.update_stock(removed_quantity)

    def calculate_total(self):
        """合計金額を計算"""
        return self.total_price

    def display_cart(self):
        """カートの内容を表示"""
        print("=== ショッピングカート ===")
        for product_id, quantity in self.items.items():
            print(f"商品ID: {product_id}, 数量: {quantity}")
        print(f"合計金額: ¥{self.calculate_total()}")
        print("=========================")

# 使用例
# 商品の作成
laptop = Product(1, "ノートパソコン", 150000, 10)
mouse = Product(2, "ワイヤレスマウス", 5000, 50)

# ショッピングカートの操作
cart = ShoppingCart()
cart.add_item(laptop, 1)
cart.add_item(mouse, 2)
cart.display_cart()

# 在庫確認
print(f"ノートパソコンの在庫: {laptop.stock}")

まとめ

メソッドと属性は、オブジェクト指向プログラミングの基礎をなす重要な概念です。属性はオブジェクトの状態を、メソッドはオブジェクトの振る舞いを定義します。Pythonでは、インスタンス属性、クラス属性、インスタンスメソッド、クラスメソッド、スタティックメソッドなど、様々な種類の属性とメソッドを柔軟に使用できます。

プロパティを使用することで属性アクセスを制御し、命名規則を通じて意図を明確に伝えることができます。これらの概念を適切に使い分けることで、より整理され、保守性の高いコードを書くことができます。


演習問題

初級問題

問題1
次のクラスにget_infoメソッドを追加してください。このメソッドは「名前: [name], 年齢: [age]」という形式の文字列を返すようにしてください。

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

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

問題3
次のコードを完成させ、車の情報を表示するdisplay_infoメソッドを追加してください。

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

中級問題

問題4
BankAccountクラスにinterest_rateクラス属性(デフォルト値0.02)を追加し、利息を計算するcalculate_interestメソッドを作成してください。

問題5
Studentクラスを作成し、複数の科目の点数を管理する機能を実装してください。add_gradeメソッドで点数を追加し、calculate_averageメソッドで平均点を計算できるようにしてください。

問題6
温度を管理するTemperatureクラスを作成し、プロパティを使用して摂氏と華氏の変換を実装してください。

問題7
Employeeクラスにクラスメソッドfrom_birth_yearを追加してください。このメソッドは誕生年から年齢を計算して従業員オブジェクトを作成します。

問題8
BookクラスとLibraryクラスを作成してください。Libraryクラスは本を追加、検索、一覧表示するメソッドを持ちます。

問題9
Calculatorクラスを作成し、四則演算のメソッドと計算履歴を保持する機能を実装してください。

上級問題

問題10
在庫管理システムを作成してください。ProductクラスとInventoryクラスを定義し、商品の追加、削除、在庫更新、検索機能を実装してください。

問題11
Userクラスを作成し、プロパティを使用してパスワードのセキュリティ検証を実装してください。パスワードは8文字以上である必要があります。

問題12
シンプルな銀行システムを構築してください。Customerクラス、Accountクラス、Bankクラスを作成し、顧客管理、口座操作、取引履歴の機能を実装してください。

演習問題 解答例

初級問題

問題1 解答

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

    def get_info(self):
        return f"名前: {self.name}, 年齢: {self.age}"

# 使用例
person = Person("山田太郎", 25)
print(person.get_info())  # 名前: 山田太郎, 年齢: 25

解説: インスタンスメソッドを追加し、オブジェクトの属性を使用して文字列を返します。

問題2 解答

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

解説: 四角形の基本的な属性と計算メソッドを実装しました。

問題3 解答

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

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

# 使用例
car = Car("Toyota", "Camry", 2023)
car.display_info()

出力結果:

ブランド: Toyota
モデル: Camry
年式: 2023

中級問題

問題4 解答

class BankAccount:
    interest_rate = 0.02  # クラス属性

    def __init__(self, account_holder, balance=0):
        self.account_holder = account_holder
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました。残高: {self.balance}円")

    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 apply_interest(self):
        interest = self.calculate_interest()
        self.balance += interest
        print(f"利息を適用しました。新しい残高: {self.balance}円")

# 使用例
account = BankAccount("山田太郎", 100000)
account.calculate_interest()  # 利息: 2000.0円
account.apply_interest()      # 利息を適用しました。新しい残高: 102000.0円

問題5 解答

class Student:
    def __init__(self, name):
        self.name = name
        self.grades = {}  # 科目名: 点数

    def add_grade(self, subject, score):
        """科目と点数を追加"""
        if 0 <= score <= 100:
            self.grades[subject] = score
            print(f"{subject}: {score}点 を追加しました")
        else:
            print("点数は0〜100の間で指定してください")

    def remove_grade(self, subject):
        """科目を削除"""
        if subject in self.grades:
            del self.grades[subject]
            print(f"{subject}を削除しました")
        else:
            print(f"{subject}は登録されていません")

    def calculate_average(self):
        """平均点を計算"""
        if not self.grades:
            return 0
        return sum(self.grades.values()) / len(self.grades)

    def get_highest_grade(self):
        """最高点の科目を取得"""
        if not self.grades:
            return None, 0
        subject = max(self.grades, key=self.grades.get)
        return subject, self.grades[subject]

    def display_grades(self):
        """成績を表示"""
        print(f"\n{self.name}さんの成績:")
        for subject, score in self.grades.items():
            print(f"  {subject}: {score}点")
        if self.grades:
            print(f"平均点: {self.calculate_average():.1f}点")
            highest_subject, highest_score = self.get_highest_grade()
            print(f"最高点: {highest_subject} {highest_score}点")

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

問題6 解答

class Temperature:
    def __init__(self, celsius):
        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

    @property
    def kelvin(self):
        """ケルビン温度を計算(読み取り専用)"""
        return self._celsius + 273.15

    def display_all(self):
        """すべての温度単位で表示"""
        print(f"摂氏: {self.celsius}°C")
        print(f"華氏: {self.fahrenheit}°F")
        print(f"ケルビン: {self.kelvin}K")

# 使用例
temp = Temperature(25)
temp.display_all()

# 温度変更
temp.celsius = 30
print(f"\n変更後の華氏: {temp.fahrenheit}°F")

問題7 解答

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

    @classmethod
    def from_birth_year(cls, name, birth_year, position):
        """誕生年から年齢を計算して従業員を作成"""
        from datetime import datetime
        current_year = datetime.now().year
        age = current_year - birth_year
        return cls(name, age, position)

    def display_info(self):
        print(f"名前: {self.name}")
        print(f"年齢: {self.age}歳")
        print(f"役職: {self.position}")

# 使用例
# 通常のコンストラクタ
emp1 = Employee("山田太郎", 30, "マネージャー")
emp1.display_info()

# クラスメソッドを使用
emp2 = Employee.from_birth_year("佐藤花子", 1995, "エンジニア")
emp2.display_info()

問題8 解答

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

    def __str__(self):
        status = "貸出中" if self.is_borrowed else "利用可能"
        return f"『{self.title}』 - {self.author} ({status})"

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

    def add_book(self, book):
        """本を追加"""
        self.books.append(book)
        print(f"『{book.title}』を追加しました")

    def find_books_by_title(self, title):
        """タイトルで本を検索"""
        return [book for book in self.books if title.lower() in book.title.lower()]

    def find_books_by_author(self, author):
        """著者で本を検索"""
        return [book for book in self.books if author.lower() in book.author.lower()]

    def borrow_book(self, isbn):
        """本を貸し出す"""
        for book in self.books:
            if book.isbn == isbn and not book.is_borrowed:
                book.is_borrowed = True
                print(f"『{book.title}』を貸し出しました")
                return True
        print("本が見つからないか、既に貸出中です")
        return False

    def return_book(self, isbn):
        """本を返却"""
        for book in self.books:
            if book.isbn == isbn and book.is_borrowed:
                book.is_borrowed = False
                print(f"『{book.title}』を返却しました")
                return True
        print("本が見つからないか、貸出中ではありません")
        return False

    def display_books(self):
        """すべての本を表示"""
        print(f"\n=== {self.name} 蔵書一覧 ===")
        if not self.books:
            print("蔵書はありません")
            return

        for i, book in enumerate(self.books, 1):
            print(f"{i}. {book}")
        print("=" * 30)

# 使用例
library = Library("中央図書館")

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

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

# 本の表示
library.display_books()

# 本の検索
print("\nPython関連書籍:")
python_books = library.find_books_by_title("Python")
for book in python_books:
    print(f"  - {book}")

# 貸出・返却
library.borrow_book("1234567890")
library.display_books()
library.return_book("1234567890")

問題9 解答

class Calculator:
    def __init__(self):
        self.result = 0
        self.history = []

    def add(self, value):
        """加算"""
        self.result += value
        self.history.append(f"+ {value} = {self.result}")
        return self.result

    def subtract(self, value):
        """減算"""
        self.result -= value
        self.history.append(f"- {value} = {self.result}")
        return self.result

    def multiply(self, value):
        """乗算"""
        self.result *= value
        self.history.append(f"× {value} = {self.result}")
        return self.result

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

        self.result /= value
        self.history.append(f"÷ {value} = {self.result}")
        return self.result

    def power(self, value):
        """累乗"""
        self.result **= value
        self.history.append(f"^ {value} = {self.result}")
        return self.result

    def clear(self):
        """クリア"""
        self.history.append(f"クリア: {self.result} → 0")
        self.result = 0
        return self.result

    def get_history(self):
        """計算履歴を取得"""
        return self.history

    def display_history(self):
        """計算履歴を表示"""
        print("\n=== 計算履歴 ===")
        for i, calculation in enumerate(self.history, 1):
            print(f"{i}. {calculation}")
        print(f"現在の値: {self.result}")
        print("================")

# 使用例
calc = Calculator()
calc.add(10)
calc.multiply(3)
calc.subtract(5)
calc.divide(2)
calc.power(2)
calc.display_history()

上級問題

問題10 解答

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):
        """在庫数を更新"""
        if new_quantity >= 0:
            self.quantity = new_quantity
            return True
        return False

    def update_price(self, new_price):
        """価格を更新"""
        if new_price >= 0:
            self.price = new_price
            return True
        return False

    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}' を削除しました")
            return True
        print(f"ID {product_id} の商品は見つかりません")
        return False

    def find_product_by_id(self, product_id):
        """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_stock(self, product_id, new_quantity):
        """在庫数を更新"""
        product = self.find_product_by_id(product_id)
        if product and product.update_quantity(new_quantity):
            print(f"商品 '{product.name}' の在庫数を {new_quantity} に更新しました")
            return True
        return False

    def sell_product(self, product_id, quantity):
        """商品を販売"""
        product = self.find_product_by_id(product_id)
        if not product:
            print(f"ID {product_id} の商品は見つかりません")
            return False

        if product.quantity >= quantity:
            product.quantity -= quantity
            total_price = product.price * quantity
            print(f"商品 '{product.name}' {quantity}個を販売しました")
            print(f"売上: ¥{total_price}")
            return True
        else:
            print(f"在庫不足です。現在の在庫: {product.quantity}個")
            return False

    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("================")

# 使用例
inventory = Inventory()

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

# 在庫表示
inventory.display_inventory()

# 商品販売
inventory.sell_product(p1, 2)
inventory.sell_product(p2, 5)

# 検索
print("\n'メモリ'を含む商品:")
memory_products = inventory.find_products_by_name("メモリ")
for product in memory_products:
    print(f"  - {product}")

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

問題11 解答

import hashlib
import re

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email
        self._password_hash = None
        self.is_active = True

    @property
    def password(self):
        raise AttributeError("パスワードの読み取りはできません")

    @password.setter
    def password(self, new_password):
        """パスワードを設定(検証付き)"""
        if not self._validate_password(new_password):
            raise ValueError("パスワードは8文字以上で、英数字を含む必要があります")

        self._password_hash = self._hash_password(new_password)
        print("パスワードを安全に設定しました")

    def _validate_password(self, password):
        """パスワードの検証"""
        if len(password) < 8:
            return False
        if not re.search(r'[a-zA-Z]', password):
            return False
        if not re.search(r'\d', password):
            return False
        return True

    def _hash_password(self, password):
        """パスワードをハッシュ化"""
        return hashlib.sha256(password.encode()).hexdigest()

    def verify_password(self, password):
        """パスワードの検証"""
        if not self._password_hash:
            return False
        return self._password_hash == self._hash_password(password)

    def reset_password(self, old_password, new_password):
        """パスワードをリセット"""
        if self.verify_password(old_password):
            self.password = new_password
            print("パスワードを変更しました")
            return True
        else:
            print("現在のパスワードが正しくありません")
            return False

    def display_info(self):
        """ユーザー情報を表示"""
        print(f"ユーザー名: {self.username}")
        print(f"メール: {self.email}")
        print(f"アクティブ: {'はい' if self.is_active else 'いいえ'}")

# 使用例
user = User("yamada", "yamada@example.com")

# パスワード設定(成功例)
try:
    user.password = "secure123"
    print("パスワード設定成功")
except ValueError as e:
    print(f"パスワード設定失敗: {e}")

# パスワード設定(失敗例)
try:
    user.password = "weak"
    print("パスワード設定成功")
except ValueError as e:
    print(f"パスワード設定失敗: {e}")

# パスワード検証
print(f"パスワード検証: {user.verify_password('secure123')}")  # True
print(f"パスワード検証: {user.verify_password('wrong')}")     # False

# パスワードリセット
user.reset_password("secure123", "newSecure456")

問題12 解答

from datetime import datetime

class Customer:
    def __init__(self, customer_id, name, address):
        self.customer_id = customer_id
        self.name = name
        self.address = address
        self.accounts = []

    def add_account(self, account):
        """口座を追加"""
        self.accounts.append(account)
        print(f"{self.name}さんに口座を追加しました")

    def display_info(self):
        """顧客情報を表示"""
        print(f"\n=== 顧客情報 ===")
        print(f"顧客ID: {self.customer_id}")
        print(f"氏名: {self.name}")
        print(f"住所: {self.address}")
        print(f"口座数: {len(self.accounts)}")
        print("================")

class Account:
    def __init__(self, account_number, customer, initial_balance=0):
        self.account_number = account_number
        self.customer = customer
        self.balance = initial_balance
        self.transactions = []
        self._add_transaction("口座開設", initial_balance)

    def deposit(self, amount):
        """預入"""
        if amount > 0:
            self.balance += amount
            self._add_transaction("預入", amount)
            print(f"{amount}円預け入れました")
            return True
        return False

    def withdraw(self, amount):
        """引出"""
        if 0 < amount <= self.balance:
            self.balance -= amount
            self._add_transaction("引出", -amount)
            print(f"{amount}円引き出しました")
            return True
        print("残高不足です")
        return False

    def transfer(self, amount, target_account):
        """振込"""
        if self.withdraw(amount):
            target_account.deposit(amount)
            self._add_transaction(f"振込({target_account.account_number})", -amount)
            target_account._add_transaction(f"振込({self.account_number})", amount)
            print(f"{amount}円を振り込みました")
            return True
        return False

    def _add_transaction(self, description, amount):
        """取引履歴を追加"""
        transaction = {
            'timestamp': datetime.now(),
            'description': description,
            'amount': amount,
            'balance': self.balance
        }
        self.transactions.append(transaction)

    def get_transaction_history(self):
        """取引履歴を取得"""
        return self.transactions

    def display_transactions(self):
        """取引履歴を表示"""
        print(f"\n=== 取引履歴 (口座: {self.account_number}) ===")
        for transaction in self.transactions:
            time_str = transaction['timestamp'].strftime("%Y-%m-%d %H:%M:%S")
            amount_str = f"+{transaction['amount']}" if transaction['amount'] >= 0 else f"{transaction['amount']}"
            print(f"{time_str} | {transaction['description']:15} | {amount_str:>10}円 | 残高: {transaction['balance']}円")
        print("=" * 60)

    def display_info(self):
        """口座情報を表示"""
        print(f"口座番号: {self.account_number}")
        print(f"名義人: {self.customer.name}")
        print(f"残高: {self.balance}円")
        print(f"取引件数: {len(self.transactions)}")

class Bank:
    def __init__(self, name):
        self.name = name
        self.customers = {}
        self.accounts = {}
        self.next_customer_id = 1
        self.next_account_number = 100000

    def add_customer(self, name, address):
        """顧客を追加"""
        customer_id = self.next_customer_id
        customer = Customer(customer_id, name, address)
        self.customers[customer_id] = customer
        self.next_customer_id += 1
        print(f"顧客 '{name}' を追加しました (ID: {customer_id})")
        return customer

    def create_account(self, customer_id, initial_balance=0):
        """口座を作成"""
        if customer_id not in self.customers:
            print("顧客が見つかりません")
            return None

        customer = self.customers[customer_id]
        account_number = self.next_account_number
        account = Account(account_number, customer, initial_balance)
        self.accounts[account_number] = account
        customer.add_account(account)
        self.next_account_number += 1
        return account

    def find_customer_by_id(self, customer_id):
        """顧客を検索"""
        return self.customers.get(customer_id)

    def find_account_by_number(self, account_number):
        """口座を検索"""
        return self.accounts.get(account_number)

    def display_bank_info(self):
        """銀行情報を表示"""
        print(f"\n=== {self.name} 情報 ===")
        print(f"顧客数: {len(self.customers)}")
        print(f"口座数: {len(self.accounts)}")
        total_deposits = sum(account.balance for account in self.accounts.values())
        print(f"預金総額: {total_deposits}円")
        print("=====================")

# 使用例
bank = Bank("東京銀行")

# 顧客と口座の作成
customer1 = bank.add_customer("山田太郎", "東京都渋谷区")
customer2 = bank.add_customer("佐藤花子", "東京都新宿区")

account1 = bank.create_account(1, 100000)
account2 = bank.create_account(2, 50000)

# 取引の実行
account1.deposit(50000)
account1.withdraw(20000)
account1.transfer(30000, account2)

# 情報表示
bank.display_bank_info()
customer1.display_info()
account1.display_transactions()
account2.display_transactions()