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

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では、実行時にオブジェクトに新しい属性を動的に追加することができます。これは多くのPythonオブジェクトは内部に __dict__ という属性管理用の辞書を持っています。self.brandself.model も、この辞書の中に保存されています。

__dict__ とは、Pythonオブジェクトが持っている「属性を保存するための辞書」です。この__dict__ というオブジェクトが「属性を自由に追加できる辞書のような仕組み」を内部に持っているため、クラス定義時に存在しなかった属性でも後から追加できます。

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を通じてオブジェクトの属性や他のメソッドにアクセスします。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つ)」は「非公開の目安(慣習)」を示します。開発者への合図であり慣例です、しかし外部からアクセスは可能ですので完全なプライベートではありません

よりアクセスをプライベートにする「プライベートメソッド」は __(ダブルアンダースコア)で定義します。しかし「本当にアクセス禁止」にする強力な private 機能はpythonには備わっていません。

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

実行結果:

名前: 山田太郎, 年齢: 25

問題2

Book クラスを作成してください。

このクラスは次の条件を満たすようにしてください。

  • show_info() を実行すると、次の形式で表示する
  • title(本のタイトル)を属性として持つ
  • pages(ページ数)を属性として持つ
  • show_info() メソッドを作成する

実行結果:

タイトル: Python入門
ページ数: 320ページ

問題3

次のコードを完成させ、車の情報 {"Toyota", "Camry", 2023} を表示するdisplay_infoメソッドを追加してください。

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

実行結果:

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

中級問題

問題4

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

実行結果:

利息: 2000.0円
利息: 2000.0円
利息を適用しました。新しい残高: 102000.0円

問題5

Studentクラスを作成し、複数の科目の点数を管理する機能を実装してください。add_gradeメソッドで科目の点数を追加し、calculate_averageメソッドで平均点を、get_highest_gradeメソッドで最高点を、最後にdisplay_gradesメソッドでStudentの成績を全て表示してください。

実行結果:

数学: 85点 を追加しました
英語: 92点 を追加しました
国語: 78点 を追加しました

佐藤花子さんの成績:
  数学: 85点
  英語: 92点
  国語: 78点
平均点: 85.0点
最高点: 英語 92点

問題6

問題

温度を管理する Temperature クラスを作成してください。

このクラスでは、摂氏温度を管理し、華氏温度とケルビン温度を自動で計算できるようにしてください。まず、コンストラクタで摂氏温度を受け取り、内部に保存してください。今回は初期値として 25 を設定してください。

celsius というプロパティを作成し、摂氏温度を取得できるようにしてください。また、セッターも作成し、摂氏温度を変更できるようにしてください。ただし、-273.15 未満の値が代入された場合は、ValueError を発生させて「絶対零度以下は設定できません」と表示するようにしてください。

さらに、fahrenheit という読み取り専用プロパティを作成し、摂氏温度から華氏温度を計算してください。計算式は次の通りです。

F=(C×95)+32F=(C\times\frac{9}{5})+32

また、kelvin という読み取り専用プロパティを作成し、ケルビン温度を計算してください。計算式は次の通りです。

K=C+273.15K=C+273.15

display_all() メソッドを作成し、摂氏・華氏・ケルビンを次の形式で表示してください。

摂氏: 25°C
華氏: 77.0°F
ケルビン: 298.15K

問題7

従業員情報を管理する Employee クラスでは従業員の「名前」「年齢」「役職」を管理できるようにしてください。コンストラクタ __init__() を作成し、nameageposition を受け取り、それぞれインスタンス変数へ保存してください。

次に、from_birth_year() というクラスメソッドを作成してください。このメソッドでは、「名前」「誕生年」「役職」を受け取り、現在の西暦から誕生年を引いて年齢を計算し、その年齢を使って Employee オブジェクトを作成してください。display_info() メソッドを作成し、従業員情報を次の形式で表示してください。

名前: 山田太郎
年齢: 30歳
役職: マネージャー
名前: 佐藤花子
年齢: 31歳
役職: エンジニア

問題8

図書館の本を管理するプログラムを作成してください。まず、Book クラスを作成してください。このクラスでは、本の「タイトル」「著者名」「ISBN番号」を管理できるようにしてください。

また、本が貸出中かどうかを表す is_borrowed 属性を作成し、初期値は False にしてください。さらに、__str__() メソッドを作成し、本の情報を次の形式で表示できるようにしてください。

『Python入門』 - 山田太郎 (利用可能)

本が貸出中の場合は、利用可能 の代わりに 貸出中 と表示してください。Library クラスを作成してください。図書館名は "中央図書館" とし、このクラスでは図書館名と蔵書リストを管理します。add_book() メソッドを作成し、本を蔵書リストへ次の3冊を登録してください。

  • タイトル: "Python入門"、著者: "山田太郎"、ISBN: "1234567890"
  • タイトル: "機械学習実践"、著者: "佐藤花子"、ISBN: "0987654321"
  • タイトル: "Pythonデータ分析"、著者: "鈴木一郎"、ISBN: "1122334455"

borrow_book() メソッドを作り、ISBN番号を指定して本を貸し出してください。対象の本が存在し、まだ貸出中でない場合は is_borrowedTrue に変更し、次の形式で表示してください。

『Python入門』を貸し出しました

すでに貸出中の場合や、対象の本が存在しない場合は、次のメッセージを表示してください。

本が見つからないか、既に貸出中です

return_book() メソッドでは、本を返却できるようにしてください。返却時には is_borrowedFalse に変更し、次の形式で表示してください。

『Python入門』を返却しました

貸し出しborrow_bookメソッドや、返却return_bookメソッドの前に、display_books() メソッドを作成し、蔵書一覧を表示してください。

問題9
Calculatorクラスを作成し、四則演算のメソッドと計算履歴を保持する機能を実装してください。Calculatorクラスで定義するメソッドは以下の名前で定義してください。計算結果はCalculatorクラスにhistoryのリスト属性を加えて管理してください。

  • add() メソッド(足し算)
  • subtract() メソッド(引き算)
  • multiply()メソッド(掛け算)
  • divide() メソッド(割り算)
  • power() メソッド(累乗)
  • clear() メソッド(計算結果の削除)
  • get_history() メソッド(計算結果履歴)

実行結果:

=== 計算履歴 ===
1. + 10 = 10
2. × 3 = 30
3. - 5 = 25
4. ÷ 2 = 12.5
5. ^ 2 = 156.25
現在の値: 156.25
================

上級問題

問題10

あなたは商品管理システムを開発しています。次の要件を満たすプログラムを作成してください。

まず、商品を表すクラスを作成し、各商品は商品ID、商品名、価格、在庫数を持つものとします。また、在庫数を更新する機能と価格を更新する機能を持ち、これらは負の値が指定された場合は更新しない仕様としてください。さらに、商品情報を表示したときに「ID: 1 | ノートパソコン | ¥150000 | 在庫: 10個」のように整形された文字列が表示されるようにしてください。

次に、複数の商品を管理する在庫管理クラスを作成してください。このクラスでは商品を追加する際に自動的に商品IDを採番し、内部で商品を辞書形式で管理します。商品IDを指定して商品を削除できる機能、商品名の一部を使って検索できる機能、そして在庫数を更新する機能を実装してください。

さらに、商品を販売する機能を作成し、指定した数量が在庫に存在する場合のみ販売を行い、在庫数を減らし売上金額を計算して表示するようにしてください。在庫が不足している場合はその旨を表示し、処理を行わないようにしてください。

最後に、すべての商品を一覧表示する機能を作成し、各商品の情報とともに在庫全体の金額(在庫数×価格の合計)も表示してください。

実行例として、複数の商品を追加した後に在庫表示を行い、いくつかの商品を販売し、その後に特定のキーワードで検索を行い、最終的な在庫状況を表示する一連の処理が正しく動作することを確認してください。

実行結果:

商品 'ノートパソコン' を追加しました (ID: 1)
商品 'ワイヤレスマウス' を追加しました (ID: 2)
商品 'USBメモリ 64GB' を追加しました (ID: 3)

=== 在庫一覧 ===
ID: 1 | ノートパソコン | ¥150000 | 在庫: 10個
ID: 2 | ワイヤレスマウス | ¥5000 | 在庫: 50個
ID: 3 | USBメモリ 64GB | ¥3000 | 在庫: 100個
在庫総額: ¥2050000
================
商品 'ノートパソコン' 2個を販売しました
売上: ¥300000
商品 'ワイヤレスマウス' 5個を販売しました
売上: ¥25000

'メモリ'を含む商品:
  - ID: 3 | USBメモリ 64GB | ¥3000 | 在庫: 100個

=== 在庫一覧 ===
ID: 1 | ノートパソコン | ¥150000 | 在庫: 8個
ID: 2 | ワイヤレスマウス | ¥5000 | 在庫: 45個
ID: 3 | USBメモリ 64GB | ¥3000 | 在庫: 100個
在庫総額: ¥1725000
================

問題11

あなたはユーザー認証機能を持つアプリケーションの開発を担当しています。セキュリティを考慮したユーザー管理システムを実装してください。

まず、ユーザーを表すクラスを作成し、ユーザー名とメールアドレスを保持できるようにしてください。また、パスワードは直接参照できないように設計し、外部からパスワードを取得しようとした場合にはエラーを発生させる仕様としてください。

パスワードの設定はプロパティを通じて行い、その際には必ず検証処理を行うものとします。具体的には、パスワードは「8文字以上であること」「アルファベットを1文字以上含むこと」「数字を1文字以上含むこと」という条件をすべて満たす必要があります。条件を満たさない場合はエラーとして拒否してください。

また、パスワードは安全のためハッシュ化して内部に保存するものとし、SHA-256アルゴリズムを用いて暗号化してください。パスワードの正当性を確認する機能も実装し、入力されたパスワードと内部のハッシュ値を比較して一致するかどうかを判定できるようにしてください。

さらに、現在のパスワードが正しい場合にのみ新しいパスワードへ変更できるリセット機能を実装してください。この際にも同じバリデーションルールを適用し、安全なパスワードのみを受け付けるようにしてください。

最後に、ユーザー情報を表示する機能を用意し、ユーザー名、メールアドレス、およびアクティブ状態が確認できるようにしてください。

実行例として、正しいパスワードと不正なパスワードの両方を設定し、それぞれの挙動が正しく処理されること、またパスワード検証およびリセット機能が適切に動作することを確認してください。

実行結果:

パスワードを安全に設定しました
パスワード設定成功
パスワード設定失敗: パスワードは8文字以上で、英数字を含む必要があります
パスワード検証: True
パスワード検証: False
パスワードを安全に設定しました
パスワードを変更しました

問題12

あなたは銀行システムの開発者として、顧客管理と口座管理を統合したアプリケーションを設計することになりました。以下の仕様を満たすプログラムを作成してください。

まず、顧客を表すクラスを作成し、顧客ID、氏名、住所を保持できるようにしてください。また、その顧客が複数の口座を持てるようにし、口座を追加できる機能と、顧客情報(ID・氏名・住所・口座数)を表示する機能を実装してください。

次に、銀行口座を表すクラスを作成し、口座番号、名義人となる顧客、残高、および取引履歴を管理できるようにしてください。口座は作成時に初期残高を設定できるものとし、その際に「口座開設」という取引が自動的に履歴へ記録されるようにしてください。

口座には預入・引出・振込の機能を実装してください。預入は0より大きい金額のみ可能とし、引出は残高以内でのみ実行できるようにしてください。振込は自分の口座から別の口座へ送金する機能とし、送金時には両口座の残高と取引履歴が正しく更新されるようにしてください。

また、すべての取引は日時・取引内容・金額・取引後残高を含む形式で履歴として保存し、履歴を一覧表示できる機能を実装してください。

さらに、銀行全体を管理するクラスを作成し、顧客の追加、口座の作成、顧客検索、口座検索ができるようにしてください。また、銀行全体の情報として顧客数・口座数・預金総額を表示できる機能も実装してください。

実行例として、複数の顧客を登録し、それぞれに口座を作成した後、預入・引出・振込などの取引を行い、銀行・顧客・口座それぞれの情報が正しく更新・表示されることを確認してください。

実行結果:

顧客 '山田太郎' を追加しました (ID: 1)
顧客 '佐藤花子' を追加しました (ID: 2)
山田太郎さんに口座を追加しました
佐藤花子さんに口座を追加しました
50000円預け入れました
20000円引き出しました
30000円引き出しました
30000円預け入れました
30000円を振り込みました

=== 東京銀行 情報 ===
顧客数: 2
口座数: 2
預金総額: 180000円
=====================

=== 顧客情報 ===
顧客ID: 1
氏名: 山田太郎
住所: 東京都渋谷区
口座数: 1
================

=== 取引履歴 (口座: 100000) ===
2026-05-14 17:48:07 | 口座開設            |    +100000円 | 残高: 100000円
2026-05-14 17:48:07 | 預入              |     +50000円 | 残高: 150000円
2026-05-14 17:48:07 | 引出              |     -20000円 | 残高: 130000円
2026-05-14 17:48:07 | 引出              |     -30000円 | 残高: 100000円
2026-05-14 17:48:07 | 振込(100001)      |     -30000円 | 残高: 100000円
============================================================

=== 取引履歴 (口座: 100001) ===
2026-05-14 17:48:07 | 口座開設            |     +50000円 | 残高: 50000円
2026-05-14 17:48:07 | 預入              |     +30000円 | 残高: 80000円
2026-05-14 17:48:07 | 振込(100000)      |     +30000円 | 残高: 80000円
============================================================

演習問題 解答例

初級問題

問題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 Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages

    def show_info(self):
        print(f"タイトル: {self.title}")
        print(f"ページ数: {self.pages}ページ")


book1 = Book("Python入門", 320)

book1.show_info()

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

問題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 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()

問題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 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()

# 貸出
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()