カプセル化とアクセス修飾子 – データ保護の技術

2025-10-28

はじめに

オブジェクト指向プログラミングのカプセル化は、データとそれを操作するメソッドを1つの単位にまとめ、外部からの不正なアクセスから保護する重要な概念です。これにより、プログラムの信頼性、保守性、セキュリティが大幅に向上します。

現実世界で例えるなら、自動車のエンジンはカプセル化の良い例です。ドライバーはアクセルやブレーキといったインターフェースを通じて車を操作しますが、エンジンの内部構造や動作原理を知る必要はありません。この「内部の詳細を隠蔽する」という考え方がカプセル化の本質です。

カプセル化の基本概念

カプセル化には主に2つの目的があります。

  1. データの保護: オブジェクトの内部状態を外部から保護する
  2. 実装の隠蔽: 内部の実装詳細を隠し、インターフェースのみを公開する

カプセル化の利点として、まず内部実装を変更しても外部インターフェースが変わらなければ影響を最小限に抑えられるため保守性が向上し、データの整合性を保証できることで信頼性も高まります。また、重要なデータへの直接アクセスを防ぐことでセキュリティが強化され、複雑な内部実装を知らなくても利用可能な使いやすいインターフェースを提供できる点も大きなメリットです。

Pythonのアクセス修飾子

Pythonでは、他の言語のような厳格なアクセス修飾子(public, private, protected)はありませんが、命名規則を通じてアクセスレベルを示す慣習があります。

公開(Public)メンバー

アンダースコアなしで始まるメンバーは公開され、どこからでもアクセス可能です。

次のコードは、銀行口座を表す BankAccount クラスの定義です。

  • コンストラクタ __init__ で口座名義(account_holder)と初期残高(balance)を受け取り、公開属性として保持します。
  • 公開メソッド deposit は、引数で渡された金額が正の値であれば残高に加算し、成功した場合は True を返します。金額が不正(0以下)の場合は処理せず False を返します。
class BankAccount:
    def __init__(self, account_holder, initial_balance):
        self.account_holder = account_holder  # 公開属性
        self.balance = initial_balance        # 公開属性

    def deposit(self, amount):  # 公開メソッド
        if amount > 0:
            self.balance += amount
            return True
        return False

つまり、このクラスは口座残高の管理と入金処理を簡単に扱えるように設計されています。

保護(Protected)メンバー

シングルアンダースコア(_)は非公開にしたいことを示す慣習的な目印であり、技術的なアクセス制限はなく、外部からも利用可能ですが、「触らないでほしい」という開発者間の暗黙の約束を意味します。メンバーは保護を意図し、クラス内部とサブクラスからのアクセスを想定しています。

  • コンストラクタ __init__ では、口座名義(account_holder)を公開属性として保持し、残高(_balance)や取引回数(_transaction_count)を保護属性として内部的に管理します。
  • 保護メソッド _validate_amount は、入金や出金時に金額が正の値かどうかを検証する内部処理用メソッドです。
  • 保護メソッド _increment_transaction_count は、取引が行われた際に取引回数をインクリメントする内部処理用メソッドです。
class BankAccount:
    def __init__(self, account_holder, initial_balance):
        self.account_holder = account_holder
        self._balance = initial_balance  # 保護属性
        self._transaction_count = 0      # 保護属性

    def _validate_amount(self, amount):  # 保護メソッド
        """金額の検証(内部使用)"""
        return amount > 0

    def _increment_transaction_count(self):  # 保護メソッド
        """取引回数をインクリメント(内部使用)"""
        self._transaction_count += 1

つまり、これらの保護属性・保護メソッドを用いることで、外部からの直接操作を避けつつ、内部処理を安全に管理できる設計になっています。

非公開(Private)メンバー

アンダースコア2つで始まるメンバーは非公開を意図し、名前のマングリング(name mangling)が行われます。外部から直接アクセスできず、クラスの内部実装を隠すために使われます。

  • コンストラクタ __init__ では、口座名義(account_holder)を公開属性として保持し、残高(__balance)や口座番号(__account_number)を非公開属性として内部で管理します。
  • 非公開メソッド __generate_account_number は、口座番号をランダムに生成する内部処理用メソッドです。
  • 非公開メソッド __validate_transaction は、取引が有効か(正の金額で残高が十分か)を検証する内部処理用メソッドです。
  • 公開メソッド get_balance は、残高を取得するための公開インターフェースとして外部からアクセス可能です。
class BankAccount:
    def __init__(self, account_holder, initial_balance):
        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 __validate_transaction(self, amount):  # 非公開メソッド
        """取引の検証(内部実装)"""
        return amount > 0 and self.__balance >= amount

    def get_balance(self):  # 公開メソッド
        """残高を取得(公開インターフェース)"""
        return self.__balance

上記のコードでは、__balance__account_numberなどの変数、および__generate_account_number()__validate_transaction()といったメソッドが非公開属性として定義されています。これらは名前の前に__を付けることで、Pythonが内部的に_BankAccount__balanceのように**name mangling(名前改変)**を行い、外部から直接アクセスしにくくしています。これにより、内部実装を保護し、サブクラスなどで誤って上書きされるのを防ぐことで、クラスの安全性とカプセル化を高めています。

プロパティの使用

基本的なプロパティの使い方

Pythonの@propertyデコレータを使用すると、属性へのアクセスをメソッドで制御できます。次のコードは@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(temp.celsius)     # 25
print(temp.fahrenheit)  # 77.0

temp.celsius = 30      # セッターを呼び出し
# temp.fahrenheit = 100  # エラー: セッターが定義されていない

celsiusは取得と検証付きの設定が可能で、-273.15℃(絶対零度)未満を防ぎます。一方、fahrenheitは読み取り専用プロパティとして定義され、celsiusの値から自動的に華氏を計算します。これにより、内部変数を直接操作せずに、安全で分かりやすいインターフェースで温度を管理できます。

ビジネスロジックを含むプロパティ

次のコードは、@propertyデコレータを用いたカプセル化と自動計算の仕組みを示しています。

class Employee:
    def __init__(self, name, base_salary):
        self.name = name
        self._base_salary = base_salary
        self._bonus = 0
        self._performance_rating = 1.0

    @property
    def base_salary(self):
        return self._base_salary

    @base_salary.setter
    def base_salary(self, value):
        if value >= 0:
            self._base_salary = value
        else:
            raise ValueError("基本給は0以上である必要があります")

    @property
    def performance_rating(self):
        return self._performance_rating

    @performance_rating.setter
    def performance_rating(self, value):
        if 0.5 <= value <= 2.0:
            self._performance_rating = value
            self._calculate_bonus()
        else:
            raise ValueError("評価は0.5から2.0の間である必要があります")

    def _calculate_bonus(self):
        """ボーナスを計算(内部メソッド)"""
        self._bonus = self._base_salary * (self._performance_rating - 1.0)

    @property
    def total_salary(self):
        """総支給額(読み取り専用)"""
        return self._base_salary + self._bonus

    @property
    def bonus(self):
        """ボーナス額(読み取り専用)"""
        return self._bonus

Employeeクラスでは、base_salaryperformance_ratingを安全に設定できるよう検証付きのセッターを定義しています。評価値performance_ratingが変更されると、自動的に内部メソッド_calculate_bonus()が呼ばれ、ボーナスが再計算されます。また、total_salarybonusは読み取り専用プロパティとして定義され、外部から直接変更できません。これにより、整合性の取れた給与管理が可能になります。

ゲッターとセッターメソッド

プロパティを使用しない場合、明示的なゲッター・セッターメソッドを定義することもできます。次のコードはゲッターとセッターによるカプセル化の基本を示しています。

class Student:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._grades = []
        self._is_graduated = False

    # ゲッターメソッド
    def get_name(self):
        return self._name

    def get_age(self):
        return self._age

    def get_grades(self):
        return self._grades.copy()  # コピーを返して内部リストを保護

    def is_graduated(self):
        return self._is_graduated

    # セッターメソッド
    def set_name(self, name):
        if isinstance(name, str) and len(name) >= 2:
            self._name = name
        else:
            raise ValueError("名前は2文字以上の文字列である必要があります")

    def set_age(self, age):
        if isinstance(age, int) and 0 <= age <= 120:
            self._age = age
        else:
            raise ValueError("年齢は0から120の整数である必要があります")

    def add_grade(self, grade):
        if 0 <= grade <= 100:
            self._grades.append(grade)
        else:
            raise ValueError("成績は0から100の間である必要があります")

    def graduate(self):
        if len(self._grades) >= 5:
            self._is_graduated = True
        else:
            raise ValueError("卒業には5つ以上の成績が必要です")

Studentクラスは名前・年齢・成績・卒業状態などの情報を持ち、それぞれの属性を安全に操作するためのメソッドを定義しています。直接属性を変更させず、set_name()set_age()などのセッターで値の検証を行うことで、不正なデータの代入を防ぎます。また、get_grades()はリストのコピーを返すことで外部からの改変を防ぎ、データの整合性を保ちます。これはオブジェクト指向における情報隠蔽の好例です。

カプセル化の実践例:銀行口座システム

実際の銀行口座システムを模した、しっかりとしたカプセル化の例を見てみましょう。

BankAccountクラスでは、__balance__account_numberなどの重要なデータを二重アンダースコアによるname manglingで保護し、外部からの直接アクセスを防いでいます。口座番号生成(__generate_account_number)や取引検証(__validate_transaction)、履歴記録(__record_transaction)といった内部処理も非公開メソッドとして定義されています。

また、deposit()withdraw()transfer()などのメソッドを通してのみ残高操作が可能で、@propertyを利用して残高や口座状態を安全に参照できます。さらに、get_transaction_history()では履歴を一部マスクして返すなど、セキュリティとデータ保護を重視した設計となっています。

class BankAccount:
    def __init__(self, account_holder, initial_balance=0):
        self._account_holder = account_holder
        self.__account_number = self.__generate_account_number()
        self.__balance = initial_balance
        self.__transaction_history = []
        self.__is_active = True
        self.__overdraft_limit = 0

    def __generate_account_number(self):
        """口座番号を生成(非公開メソッド)"""
        import random
        import hashlib
        base_number = f"{self._account_holder}{random.randint(1000, 9999)}"
        return hashlib.md5(base_number.encode()).hexdigest()[:10].upper()

    def __validate_transaction(self, amount):
        """取引の検証(非公開メソッド)"""
        if not self.__is_active:
            raise ValueError("口座が無効です")
        if amount <= 0:
            raise ValueError("取引額は正の値である必要があります")
        return True

    def __record_transaction(self, transaction_type, amount):
        """取引履歴を記録(非公開メソッド)"""
        import datetime
        transaction = {
            'type': transaction_type,
            'amount': amount,
            'balance': self.__balance,
            'timestamp': datetime.datetime.now()
        }
        self.__transaction_history.append(transaction)

    @property
    def account_holder(self):
        return self._account_holder

    @property
    def account_number(self):
        return f"****{self.__account_number[-4:]}"

    @property
    def balance(self):
        return self.__balance

    @property
    def is_active(self):
        return self.__is_active

    def deposit(self, amount):
        """預入処理"""
        if self.__validate_transaction(amount):
            self.__balance += amount
            self.__record_transaction('DEPOSIT', amount)
            return True
        return False

    def withdraw(self, amount):
        """引出処理"""
        if self.__validate_transaction(amount):
            if self.__balance + self.__overdraft_limit >= amount:
                self.__balance -= amount
                self.__record_transaction('WITHDRAWAL', -amount)
                return True
            else:
                raise ValueError("残高不足です")
        return False

    def transfer(self, amount, target_account):
        """振込処理"""
        if self.withdraw(amount):
            target_account.deposit(amount)
            self.__record_transaction('TRANSFER_OUT', -amount)
            target_account.__record_transaction('TRANSFER_IN', amount)
            return True
        return False

    def get_transaction_history(self, recent_count=10):
        """取引履歴を取得(限定公開)"""
        recent_transactions = self.__transaction_history[-recent_count:]
        # 機密情報をマスクして返す
        masked_history = []
        for transaction in recent_transactions:
            masked_transaction = transaction.copy()
            masked_transaction['balance'] = '***'
            masked_history.append(masked_transaction)
        return masked_history

    def close_account(self):
        """口座を閉鎖"""
        if self.__balance == 0:
            self.__is_active = False
            return True
        else:
            raise ValueError("残高が0でないと口座を閉鎖できません")

    def set_overdraft_limit(self, limit):
        """当座貸越限度額を設定(制限付き)"""
        # 実際のシステムではより複雑な検証が必要
        if 0 <= limit <= 100000:  # 10万円まで
            self.__overdraft_limit = limit
            return True
        return False

コンポジションによるカプセル化

継承ではなくコンポジション(包含)を使用することで、より柔軟なカプセル化を実現できます。次のコードは、コンポジション(合成)とカプセル化を組み合わせた設計の例です。

CarクラスはEngineクラスのインスタンスを内部に持ち、エンジン関連の操作(始動・停止・走行距離の加算など)をすべてEngineオブジェクトに委譲しています。これにより、エンジンの詳細(馬力・燃料・走行距離など)はCarから直接操作できず、start_engine()drive()などの限定的なインターフェースを通じてのみ利用可能です。

class Engine:
    def __init__(self, horsepower, fuel_type):
        self.__horsepower = horsepower
        self.__fuel_type = fuel_type
        self.__is_running = False
        self.__mileage = 0

    def start(self):
        if not self.__is_running:
            self.__is_running = True
            return True
        return False

    def stop(self):
        if self.__is_running:
            self.__is_running = False
            return True
        return False

    def add_mileage(self, distance):
        if distance > 0 and self.__is_running:
            self.__mileage += distance
            return True
        return False

    def get_maintenance_info(self):
        return {
            'horsepower': self.__horsepower,
            'fuel_type': self.__fuel_type,
            'mileage': self.__mileage,
            'needs_oil_change': self.__mileage % 5000 == 0
        }

class Car:
    def __init__(self, brand, model, engine_horsepower, fuel_type):
        self._brand = brand
        self._model = model
        self.__engine = Engine(engine_horsepower, fuel_type)  # コンポジション
        self.__speed = 0

    def start_engine(self):
        return self.__engine.start()

    def stop_engine(self):
        return self.__engine.stop()

    def drive(self, distance):
        if self.__engine.add_mileage(distance):
            self.__speed = min(120, distance / 10)  # 単純化した速度計算
            return True
        return False

    def get_car_info(self):
        engine_info = self.__engine.get_maintenance_info()
        return {
            'brand': self._brand,
            'model': self._model,
            'current_speed': self.__speed,
            'engine_info': engine_info
        }

    # エンジン内部への直接アクセスは許可しない
    # 必要な機能はCarクラスを通じて提供する

このような構造により、Engineの内部実装を隠蔽しつつ、Carクラスは必要な機能だけを外部に公開できます。結果として、部品同士が独立し保守性の高いオブジェクト設計が実現されています。

カプセル化の設計原則

最小権限の原則

オブジェクトは、そのタスクを実行するために必要な最小限の権限のみを持つべきです。

情報隠蔽

実装の詳細を隠し、安定した公開インターフェースのみを提供すべきです。

不変性の確保

可能な限り、オブジェクトの状態を不変(イミュータブル)に保つことで、予期しない変更を防ぎます。

デザインパターンとの関連

カプセル化は多くのデザインパターンの基礎となります。

ファサードパターン

ファサードパターンとは、複雑なシステムの内部構造を隠蔽し、外部には単純で統一されたインターフェースを提供するデザインパターンです。これにより、利用者は内部の詳細を意識せずにシステムを簡単に扱えるようになります。

プロキシパターン

プロキシパターンとは、実際のオブジェクトへのアクセスを隠蔽し、代わりに代理オブジェクト(プロキシ)が処理を仲介するデザインパターンです。これにより、アクセス制御や遅延処理、キャッシュなどを実オブジェクトの外側で柔軟に管理できます。

オブザーバーパターン

オブザーバーパターンとは、あるオブジェクトの状態変化を隠蔽しつつ、依存する複数のオブジェクト(オブザーバー)へ自動的に通知する仕組みを提供するデザインパターンです。これにより、状態変化と通知処理を分離し、柔軟で拡張性の高い設計が可能になります。

まとめ

カプセル化は、オブジェクト指向プログラミングにおいて、コードの品質と信頼性を高める重要な概念です。Pythonでは命名規則を通じてアクセスレベルを表現し、プロパティを使用して属性アクセスを制御します。

適切なカプセル化を行うことで、データの整合性を保ちつつ、内部実装の変更による影響を局所化し、利用者にとって使いやすいインターフェースを提供するとともに、セキュリティの向上も実現できます。


演習問題

初級問題

問題1

次の条件を満たす Person クラスを作成してください。

  • 名前を保存する
  • 年齢を保存する
  • show() メソッドで情報を表示する
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

実行結果:

名前: 山田
年齢: 20歳

問題2

次の条件を満たす BankAccount クラスを作成してください。

  • 口座名義を保存する
  • 残高を保存する
  • deposit() メソッドで入金する
  • show_balance() メソッドで残高を表示する

実行結果:

山田さんの口座
5000円入金しました
残高: 15000円

問題3

次の条件を満たす Temperature クラスを作成してください。

  • 摂氏温度を保存する
  • 華氏温度を計算する
  • 温度を表示する

華氏温度は次の式で求めます。

華氏 = 摂氏 × 9 ÷ 5 + 32

実行結果:

摂氏: 25℃
華氏: 77.0℉

中級問題

問題4

次の条件を満たす Student クラスを作成してください。

  • 学生の名前を保存する
  • 点数を保存する
  • 平均点を表示する

実行結果:

名前: 佐藤
点数: [80, 90, 70]
平均点: 80.0点

問題5

次の条件を満たす Employee クラスを作成してください。

  • 社員名を保存する
  • 基本給を保存する
  • ボーナスを追加できる
  • 合計金額を表示する

実行結果:

社員名: 山田
基本給: 300000円
ボーナス: 50000円
合計: 350000円

問題6

次の条件を満たす PasswordManager クラスを作成してください。

  • サービス名とパスワードを辞書で管理する
  • add_password() で登録する
  • check_password() でパスワード一致確認をする
  • パスワードはそのまま保存せず、SHA256でハッシュ化して保存する
  • 登録済みサービス一覧を表示できるようにする

次のコードを実行したとき、結果が一致するようにしてください。

pm = PasswordManager()

pm.add_password("gmail", "abc123")
pm.add_password("amazon", "pass999")

pm.check_password("gmail", "abc123")
pm.check_password("gmail", "wrong")

print(pm.get_services())

実行結果:

gmail のパスワードを登録しました
amazon のパスワードを登録しました
パスワード一致
パスワード不一致
['gmail', 'amazon']

問題7

次の条件を満たす InventoryItem クラスを作成してください。

  • 商品名を保存する
  • 価格を保存する
  • 在庫数を保存する
  • sell() メソッドで在庫を減らす
  • restock() メソッドで在庫を増やす
  • show_stock() メソッドで在庫状況を表示する
  • 在庫不足の場合は「在庫不足」と表示する

次のコードを実行したとき、結果が一致するようにしてください。

item = InventoryItem("ノートPC", 150000, 10)

item.show_stock()

item.sell(3)
item.show_stock()

item.sell(20)

item.restock(5)
item.show_stock()

実行結果:

商品名: ノートPC
在庫: 10個

3個販売しました
現在の在庫: 7個

在庫不足です

5個補充しました
現在の在庫: 12個

問題8

次の条件を満たす Car クラスを作成してください。

  • 車名を保存する
  • 燃料を保存する
  • drive() メソッドで走行する
  • 走行すると燃料が減る
  • 燃料不足の場合は走行できない
  • show_info() メソッドで車情報を表示する

次のコードを実行したとき、結果が一致するようにしてください。

car = Car("Toyota", 50)

car.show_info()

car.drive(20)

car.drive(40)

car.refuel()

car.show_info()

実行結果:

車名: Toyota
燃料: 50L

20km走行しました
残り燃料: 30L

燃料不足です

給油しました

車名: Toyota
燃料: 100L

問題9

次の条件を満たす User クラスを作成してください。

  • ユーザー名を保存する
  • メールアドレスを保存する
  • show_info() メソッドで情報を表示する
  • change_email() メソッドでメールアドレスを変更する
  • メールアドレスに @ が含まれていない場合は「無効なメールアドレス」と表示する

次のコードを実行したとき、結果が一致するようにしてください。

user = User("taro", "taro@example.com")

user.show_info()

user.change_email("newtaro@example.com")

user.change_email("invalid-email")

実行結果:

ユーザー名: taro
メール: taro@example.com

メールアドレスを変更しました

無効なメールアドレスです

上級問題

問題10

図書館の本の貸出を管理するシステムを作成してください。Book クラスと Library クラスを作成し、本の登録、利用者登録、本の貸出ができるようにしてください。まず Library("中央図書館") を作成してください。次に、以下の2冊の本を作成してください。

Book("B001", "Python入門", "山田太郎", "978-1234567890", 3)

Book("B002", "機械学習実践", "佐藤花子", "978-0987654321", 2)

その後、2冊の本を図書館へ追加してください。続いて、利用者 "U001""U002" を登録してください。次に、以下の貸出処理を行ってください。

library.borrow_book("B001", "U001")

library.borrow_book("B001", "U002")

貸出後、get_library_report() を利用して図書館レポートを取得し、次の内容を表示してください。

総蔵書数
登録ユーザー数
利用可能な本

最後に、find_available_books() を利用して、現在貸出可能な本の一覧を表示してください。実行結果は次のようになるようにしてください。

'Python入門'を図書館に追加しました
'機械学習実践'を図書館に追加しました

ユーザー U001 を登録しました
ユーザー U002 を登録しました

'Python入門'をU001さんに貸し出しました
'Python入門'をU002さんに貸し出しました

総蔵書数: 2
登録ユーザー数: 2
利用可能な本: 2

利用可能な本:
- Python入門 (残り1冊)
- 機械学習実践 (残り2冊)

なお、Book クラスでは本の情報や貸出状況を管理し、Library クラスでは本の追加、利用者登録、貸出処理を管理してください。

問題11

株式の購入と売却を管理する StockPortfolio クラスを作成してください。このプログラムでは、株の購入、売却、現在の評価額計算、保有銘柄一覧の表示を行えるようにしてください。

#まず、次のコードでポートフォリオを作成してください。

portfolio = StockPortfolio("山田太郎")

# 次に、以下の株を購入してください。

portfolio.buy_stock("AAPL", 10, 15000)

portfolio.buy_stock("GOOGL", 5, 250000)

portfolio.buy_stock("AAPL", 5, 16000)

AAPL は追加購入になるため、数量が合算されるようにしてください。その後、get_portfolio_summary() を利用して、次の内容を表示してください。

保有者
保有銘柄数

次に、現在価格を次のように設定してください。

current_prices = {
    "AAPL": 17000,
    "GOOGL": 260000
}

この価格を利用して、get_current_value() を呼び出し、次の内容を表示してください。

評価額
損益
リターン率

続いて、get_holdings_detail() を利用し、保有銘柄ごとの損益を表示してください。最後に、get_performance_report() を利用して、リスクレベルを表示してください。実行結果は次のようになるようにしてください。

AAPLを10株、15000円で購入しました
GOOGLを5株、250000円で購入しました
AAPLを5株、16000円で購入しました

保有者: 山田太郎
保有銘柄数: 2

評価額: 1565000円
損益: 85000円
リターン率: 5.74%

保有銘柄詳細:
AAPL: 15株, 損益: 20000円
GOOGL: 5株, 損益: 50000円

リスクレベル: 中

なお、株の情報は辞書で管理し、購入時には保有数量を更新してください。また、現在価格を使って評価額や損益を計算できるようにしてください。

問題12

健康状態を管理する HealthMonitoringSystem クラスを作成してください。このプログラムでは、心拍数や体温などのバイタルサインを記録し、健康状態のサマリーを確認できるようにしてください。また、医療イベントやアクセスログも管理できるようにしてください。

# まず、次のコードで健康管理システムを作成してください。

health_system = HealthMonitoringSystem("U12345")

# 次に、以下のバイタルサインを記録してください。

health_system.record_vital_sign("heart_rate", 72)

health_system.record_vital_sign("body_temperature", 36.8)

health_system.record_vital_sign("blood_oxygen", 98)

health_system.record_vital_sign("heart_rate", 85)

health_system.record_vital_sign("heart_rate", 45)

health_system.record_vital_sign("body_temperature", 38.5)

heart_rate45 の場合は低すぎる値として扱い、body_temperature38.5 の場合は発熱として異常値扱いになるようにしてください。その後、次の医療イベントを追加してください。

health_system.add_medical_event(
    "checkup",
    "定期健康診断",
    "low"
)

続いて、get_health_summary(7) を利用して7日間の健康サマリーを取得し、次の内容を表示してください。

ユーザーID
健康状態
検出された異常件数

さらに、推奨事項を一覧表示してください。その後、data_encryption_status を利用してデータ暗号化状態を表示してください。最後に、get_access_log("doctor") を利用してアクセスログを取得し、直近のアクセス履歴を表示してください。実行結果は次のようになるようにしてください。

heart_rateを記録しました: 72
body_temperatureを記録しました: 36.8
blood_oxygenを記録しました: 98
heart_rateを記録しました: 85

警告: 異常値: 45 (heart_rate)
heart_rateを記録しました: 45

警告: 異常値: 38.5 (body_temperature)
body_temperatureを記録しました: 38.5

医療イベントを記録しました: checkup

ユーザーID: U12345
健康状態: 要医療相談
検出された異常: 2件

推奨事項:
- 異常なバイタルサインが検出されました。医療専門家に相談してください。

データ暗号化: 有効

最近のアクセス:
2026-05-15 10:00:00 - record_vital_sign:heart_rate by system
2026-05-15 10:00:01 - record_vital_sign:body_temperature by system

なお、バイタルサインは辞書で管理し、異常値の場合は警告メッセージを表示してください。また、アクセスログには実行した操作内容と実行者を保存してください。

初級問題

問題1 解答

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

    def show(self):
        print(f"名前: {self.name}")
        print(f"年齢: {self.age}歳")


# オブジェクト作成
person = Person("山田", 20)

# 情報表示
person.show()

解説: プロパティを使用して年齢の検証を行い、無効な値が設定されないように保護しています。

問題2 解答

class BankAccount:
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance

    # 入金メソッド
    def deposit(self, amount):
        self.balance += amount
        print(f"{amount}円入金しました")

    # 残高表示メソッド
    def show_balance(self):
        print(f"残高: {self.balance}円")


# オブジェクト作成
account = BankAccount("山田", 10000)

# 名前表示
print(f"{account.name}さんの口座")

# 入金
account.deposit(5000)

# 残高表示
account.show_balance()

問題3 解答

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    # 華氏温度を計算
    def to_fahrenheit(self):
        return (self.celsius * 9 / 5) + 32

    # 温度表示
    def show(self):
        print(f"摂氏: {self.celsius}℃")
        print(f"華氏: {self.to_fahrenheit()}℉")


# オブジェクト作成
temp = Temperature(25)

# 表示
temp.show()

中級問題

問題4 解答

class Student:
    def __init__(self, name):
        self.name = name
        self.grades = []

    # 点数追加
    def add_grade(self, grade):
        self.grades.append(grade)

    # 平均点計算
    def calculate_average(self):
        return sum(self.grades) / len(self.grades)

    # 情報表示
    def show(self):
        print(f"名前: {self.name}")
        print(f"点数: {self.grades}")
        print(f"平均点: {self.calculate_average()}点")


# オブジェクト作成
student = Student("佐藤")

# 点数追加
student.add_grade(80)
student.add_grade(90)
student.add_grade(70)

# 表示
student.show()

問題5 解答

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

    # ボーナス設定
    def add_bonus(self, bonus):
        self.bonus = bonus

    # 合計給与計算
    def total_salary(self):
        return self.salary + self.bonus

    # 情報表示
    def show(self):
        print(f"社員名: {self.name}")
        print(f"基本給: {self.salary}円")
        print(f"ボーナス: {self.bonus}円")
        print(f"合計: {self.total_salary()}円")


# オブジェクト作成
emp = Employee("山田", 300000)

# ボーナス追加
emp.add_bonus(50000)

# 表示
emp.show()

問題6 解答

import hashlib


class PasswordManager:
    def __init__(self):
        # サービス名とハッシュ化パスワードを保存
        self.passwords = {}

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

    # パスワード登録
    def add_password(self, service, password):
        hashed = self.hash_password(password)
        self.passwords[service] = hashed
        print(f"{service} のパスワードを登録しました")

    # パスワード確認
    def check_password(self, service, password):
        hashed = self.hash_password(password)

        if self.passwords[service] == hashed:
            print("パスワード一致")
        else:
            print("パスワード不一致")

    # サービス一覧取得
    def get_services(self):
        return list(self.passwords.keys())


# 使用例
pm = PasswordManager()

pm.add_password("gmail", "abc123")
pm.add_password("amazon", "pass999")

pm.check_password("gmail", "abc123")
pm.check_password("gmail", "wrong")

print(pm.get_services())

問題7 解答

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

    # 商品販売
    def sell(self, quantity):
        if quantity <= self.stock:
            self.stock -= quantity
            print(f"{quantity}個販売しました")
            print(f"現在の在庫: {self.stock}個")
        else:
            print("在庫不足です")

    # 在庫補充
    def restock(self, quantity):
        self.stock += quantity
        print(f"{quantity}個補充しました")

    # 在庫表示
    def show_stock(self):
        print(f"商品名: {self.name}")
        print(f"在庫: {self.stock}個")


# 使用例
item = InventoryItem("ノートPC", 150000, 10)

item.show_stock()

item.sell(3)
item.show_stock()

item.sell(20)

item.restock(5)
item.show_stock()

問題8 解答

class Car:
    def __init__(self, name, fuel):
        self.name = name
        self.fuel = fuel

    # 走行
    def drive(self, distance):
        if distance <= self.fuel:
            self.fuel -= distance
            print(f"{distance}km走行しました")
            print(f"残り燃料: {self.fuel}L")
        else:
            print("燃料不足です")

    # 給油
    def refuel(self):
        self.fuel = 100
        print("給油しました")

    # 車情報表示
    def show_info(self):
        print(f"車名: {self.name}")
        print(f"燃料: {self.fuel}L")


# 使用例
car = Car("Toyota", 50)

car.show_info()

car.drive(20)

car.drive(40)

car.refuel()

car.show_info()

問題9 解答

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    # ユーザー情報表示
    def show_info(self):
        print(f"ユーザー名: {self.username}")
        print(f"メール: {self.email}")

    # メール変更
    def change_email(self, new_email):
        if "@" in new_email:
            self.email = new_email
            print("メールアドレスを変更しました")
        else:
            print("無効なメールアドレスです")


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

user.show_info()

user.change_email("newtaro@example.com")

user.change_email("invalid-email")

上級問題

問題10 解答

from datetime import datetime, timedelta

class Book:
    def __init__(self, book_id, title, author, isbn, total_copies=1):
        self._book_id = book_id
        self._title = title
        self._author = author
        self._isbn = isbn
        self.__total_copies = total_copies  # 非公開属性
        self.__available_copies = total_copies
        self.__borrow_records = []  # 非公開属性

    def borrow_book(self, user_id, borrow_days=14):
        """本を貸し出す"""
        if self.__available_copies > 0:
            borrow_date = datetime.now()
            due_date = borrow_date + timedelta(days=borrow_days)

            record = {
                'user_id': user_id,
                'borrow_date': borrow_date,
                'due_date': due_date,
                'return_date': None,
                'is_overdue': False
            }

            self.__borrow_records.append(record)
            self.__available_copies -= 1

            print(f"'{self._title}'を{user_id}さんに貸し出しました")
            print(f"返却期限: {due_date.strftime('%Y-%m-%d')}")
            return True
        else:
            print("すべての貸出可能なコピーが貸し出されています")
            return False

    def return_book(self, user_id):
        """本を返却する"""
        for record in self.__borrow_records:
            if record['user_id'] == user_id and record['return_date'] is None:
                record['return_date'] = datetime.now()

                # 延滞チェック
                if record['return_date'] > record['due_date']:
                    record['is_overdue'] = True
                    overdue_days = (record['return_date'] - record['due_date']).days
                    print(f"延滞しています。延滞日数: {overdue_days}日")

                self.__available_copies += 1
                print(f"'{self._title}'を{user_id}さんから返却を受けました")
                return True

        print(f"{user_id}さんはこの本を借りていません")
        return False

    def __calculate_overdue_rate(self):
        """延滞率を計算(非公開メソッド)"""
        total_borrows = len(self.__borrow_records)
        if total_borrows == 0:
            return 0

        overdue_count = sum(1 for record in self.__borrow_records if record['is_overdue'])
        return (overdue_count / total_borrows) * 100

    def get_book_status(self):
        """本の状態を取得"""
        return {
            'book_id': self._book_id,
            'title': self._title,
            'author': self._author,
            'total_copies': self.__total_copies,
            'available_copies': self.__available_copies,
            'borrowed_copies': self.__total_copies - self.__available_copies,
            'availability': '利用可能' if self.__available_copies > 0 else '貸出中'
        }

    def get_borrowing_statistics(self):
        """貸出統計を取得"""
        total_borrows = len(self.__borrow_records)
        current_borrows = sum(1 for record in self.__borrow_records if record['return_date'] is None)

        return {
            'total_borrows': total_borrows,
            'current_borrows': current_borrows,
            'overdue_rate': f"{self.__calculate_overdue_rate():.1f}%",
            'popularity': '高' if total_borrows > 10 else '低'
        }

    def get_borrow_history(self, limit=5):
        """貸出履歴を取得(限定公開)"""
        recent_records = self.__borrow_records[-limit:]

        formatted_records = []
        for record in recent_records:
            status = "返却済み" if record['return_date'] else "貸出中"
            if record['is_overdue']:
                status += " (延滞)"

            formatted_records.append({
                'user_id': record['user_id'],
                'borrow_date': record['borrow_date'].strftime('%Y-%m-%d'),
                'due_date': record['due_date'].strftime('%Y-%m-%d'),
                'status': status
            })

        return formatted_records

    @property
    def book_id(self):
        return self._book_id

    @property
    def title(self):
        return self._title

    @property
    def author(self):
        return self._author

class Library:
    def __init__(self, name):
        self._name = name
        self.__books = {}  # 非公開属性 {book_id: Book}
        self.__users = set()  # 非公開属性

    def add_book(self, book):
        """本を図書館に追加"""
        self.__books[book.book_id] = book
        print(f"'{book.title}'を図書館に追加しました")

    def register_user(self, user_id):
        """ユーザーを登録"""
        if user_id not in self.__users:
            self.__users.add(user_id)
            print(f"ユーザー {user_id} を登録しました")
            return True
        else:
            print(f"ユーザー {user_id} は既に登録されています")
            return False

    def borrow_book(self, book_id, user_id):
        """本を貸し出す"""
        if user_id not in self.__users:
            print(f"ユーザー {user_id} は登録されていません")
            return False

        if book_id in self.__books:
            return self.__books[book_id].borrow_book(user_id)
        else:
            print(f"本ID {book_id} は見つかりません")
            return False

    def return_book(self, book_id, user_id):
        """本を返却する"""
        if book_id in self.__books:
            return self.__books[book_id].return_book(user_id)
        else:
            print(f"本ID {book_id} は見つかりません")
            return False

    def get_library_report(self):
        """図書館レポートを生成"""
        total_books = len(self.__books)
        total_borrows = sum(len(book._Book__borrow_records) for book in self.__books.values())
        available_books = sum(1 for book in self.__books.values() 
                            if book.get_book_status()['available_copies'] > 0)

        return {
            'library_name': self._name,
            'total_books': total_books,
            'total_users': len(self.__users),
            'available_books': available_books,
            'total_borrows': total_borrows,
            'utilization_rate': f"{(total_borrows / (total_books * 10)) * 100:.1f}%"  # 簡易的な利用率
        }

    def find_available_books(self):
        """利用可能な本のリストを取得"""
        available_books = []
        for book in self.__books.values():
            status = book.get_book_status()
            if status['available_copies'] > 0:
                available_books.append({
                    'title': status['title'],
                    'author': status['author'],
                    'available_copies': status['available_copies']
                })
        return available_books

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

# 本の追加
book1 = Book("B001", "Python入門", "山田太郎", "978-1234567890", 3)
book2 = Book("B002", "機械学習実践", "佐藤花子", "978-0987654321", 2)

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

# ユーザー登録
library.register_user("U001")
library.register_user("U002")

# 本の貸出
library.borrow_book("B001", "U001")
library.borrow_book("B001", "U002")

# 図書館レポート
report = library.get_library_report()
print(f"総蔵書数: {report['total_books']}")
print(f"登録ユーザー数: {report['total_users']}")
print(f"利用可能な本: {report['available_books']}")

# 利用可能な本のリスト
available = library.find_available_books()
print("\n利用可能な本:")
for book in available:
    print(f"- {book['title']} (残り{book['available_copies']}冊)")

問題11 解答

class StockPortfolio:
    def __init__(self, owner_name):
        self._owner_name = owner_name
        self.__holdings = {}  # 非公開属性 {symbol: {'quantity': int, 'purchase_price': float}}
        self.__transaction_history = []  # 非公開属性

    def buy_stock(self, symbol, quantity, purchase_price):
        """株を購入"""
        if quantity <= 0 or purchase_price <= 0:
            print("数量と価格は正の値である必要があります")
            return False

        if symbol in self.__holdings:
            # 既存の保有銘柄を更新
            total_quantity = self.__holdings[symbol]['quantity'] + quantity
            # 平均購入価格を計算
            total_cost = (self.__holdings[symbol]['quantity'] * 
                         self.__holdings[symbol]['purchase_price'] + 
                         quantity * purchase_price)
            average_price = total_cost / total_quantity

            self.__holdings[symbol]['quantity'] = total_quantity
            self.__holdings[symbol]['purchase_price'] = average_price
        else:
            # 新規銘柄を追加
            self.__holdings[symbol] = {
                'quantity': quantity,
                'purchase_price': purchase_price
            }

        # 取引履歴を記録
        transaction = {
            'type': 'BUY',
            'symbol': symbol,
            'quantity': quantity,
            'price': purchase_price,
            'total_amount': quantity * purchase_price,
            'timestamp': self._get_current_timestamp()
        }
        self.__transaction_history.append(transaction)

        print(f"{symbol}を{quantity}株、{purchase_price}円で購入しました")
        return True

    def sell_stock(self, symbol, quantity, sell_price):
        """株を売却"""
        if symbol not in self.__holdings:
            print(f"{symbol}は保有していません")
            return False

        if quantity <= 0 or sell_price <= 0:
            print("数量と価格は正の値である必要があります")
            return False

        if quantity > self.__holdings[symbol]['quantity']:
            print(f"保有数量を超える売却はできません。保有: {self.__holdings[symbol]['quantity']}株")
            return False

        # 売却処理
        purchase_price = self.__holdings[symbol]['purchase_price']
        profit_loss = (sell_price - purchase_price) * quantity

        self.__holdings[symbol]['quantity'] -= quantity

        # 保有数量が0になったら削除
        if self.__holdings[symbol]['quantity'] == 0:
            del self.__holdings[symbol]

        # 取引履歴を記録
        transaction = {
            'type': 'SELL',
            'symbol': symbol,
            'quantity': quantity,
            'price': sell_price,
            'total_amount': quantity * sell_price,
            'profit_loss': profit_loss,
            'timestamp': self._get_current_timestamp()
        }
        self.__transaction_history.append(transaction)

        action = "利益" if profit_loss > 0 else "損失"
        print(f"{symbol}を{quantity}株、{sell_price}円で売却しました")
        print(f"{action}: {abs(profit_loss):.0f}円")
        return True

    def _get_current_timestamp(self):
        """現在のタイムスタンプを取得(保護メソッド)"""
        from datetime import datetime
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def get_current_value(self, current_prices):
        """現在の評価額を計算"""
        total_value = 0
        total_cost = 0

        for symbol, holding in self.__holdings.items():
            if symbol in current_prices:
                current_price = current_prices[symbol]
                current_value = holding['quantity'] * current_price
                cost_basis = holding['quantity'] * holding['purchase_price']

                total_value += current_value
                total_cost += cost_basis
            else:
                print(f"警告: {symbol}の現在価格が不明です")

        total_profit_loss = total_value - total_cost
        return {
            'total_cost': total_cost,
            'total_value': total_value,
            'total_profit_loss': total_profit_loss,
            'return_rate': (total_profit_loss / total_cost * 100) if total_cost > 0 else 0
        }

    def get_holdings_detail(self, current_prices=None):
        """保有銘柄の詳細を取得"""
        holdings_detail = []

        for symbol, holding in self.__holdings.items():
            detail = {
                'symbol': symbol,
                'quantity': holding['quantity'],
                'purchase_price': holding['purchase_price'],
                'cost_basis': holding['quantity'] * holding['purchase_price']
            }

            if current_prices and symbol in current_prices:
                current_price = current_prices[symbol]
                current_value = holding['quantity'] * current_price
                profit_loss = current_value - detail['cost_basis']

                detail.update({
                    'current_price': current_price,
                    'current_value': current_value,
                    'profit_loss': profit_loss,
                    'return_rate': (profit_loss / detail['cost_basis'] * 100) if detail['cost_basis'] > 0 else 0
                })

            holdings_detail.append(detail)

        return holdings_detail

    def get_portfolio_summary(self):
        """ポートフォリオの概要を取得"""
        total_holdings = len(self.__holdings)
        total_transactions = len(self.__transaction_history)

        # 最近の取引
        recent_transactions = self.__transaction_history[-5:] if self.__transaction_history else []

        return {
            'owner_name': self._owner_name,
            'total_holdings': total_holdings,
            'total_transactions': total_transactions,
            'recent_activity': len(recent_transactions)
        }

    def get_performance_report(self, current_prices):
        """パフォーマンスレポートを生成"""
        current_value = self.get_current_value(current_prices)
        holdings_detail = self.get_holdings_detail(current_prices)

        # トップパフォーマーとワーストパフォーマー
        if holdings_detail and all('profit_loss' in holding for holding in holdings_detail):
            top_performer = max(holdings_detail, key=lambda x: x['profit_loss'])
            worst_performer = min(holdings_detail, key=lambda x: x['profit_loss'])
        else:
            top_performer = worst_performer = None

        return {
            'portfolio_value': current_value['total_value'],
            'total_profit_loss': current_value['total_profit_loss'],
            'total_return_rate': f"{current_value['return_rate']:.2f}%",
            'top_performer': top_performer,
            'worst_performer': worst_performer,
            'risk_level': self._assess_risk_level(holdings_detail)
        }

    def _assess_risk_level(self, holdings_detail):
        """リスクレベルを評価(保護メソッド)"""
        if not holdings_detail:
            return "低"

        # 簡易的なリスク評価(銘柄数に基づく)
        num_holdings = len(holdings_detail)
        if num_holdings == 1:
            return "高"
        elif num_holdings <= 3:
            return "中"
        else:
            return "低"

# 使用例
portfolio = StockPortfolio("山田太郎")

# 株の購入
portfolio.buy_stock("AAPL", 10, 15000)
portfolio.buy_stock("GOOGL", 5, 250000)
portfolio.buy_stock("AAPL", 5, 16000)  # 追加購入

# ポートフォリオ概要
summary = portfolio.get_portfolio_summary()
print(f"保有者: {summary['owner_name']}")
print(f"保有銘柄数: {summary['total_holdings']}")

# 現在価格(仮定)
current_prices = {
    "AAPL": 17000,
    "GOOGL": 260000
}

# 現在の評価額
current_value = portfolio.get_current_value(current_prices)
print(f"評価額: {current_value['total_value']:.0f}円")
print(f"損益: {current_value['total_profit_loss']:.0f}円")
print(f"リターン率: {current_value['return_rate']:.2f}%")

# 保有銘柄詳細
holdings = portfolio.get_holdings_detail(current_prices)
print("\n保有銘柄詳細:")
for holding in holdings:
    print(f"{holding['symbol']}: {holding['quantity']}株, "
          f"損益: {holding['profit_loss']:.0f}円")

# パフォーマンスレポート
performance = portfolio.get_performance_report(current_prices)
print(f"\nリスクレベル: {performance['risk_level']}")

問題12 解答

import json
from datetime import datetime, timedelta

class HealthMonitoringSystem:
    def __init__(self, user_id):
        self._user_id = user_id
        self.__vital_signs = {}  # 非公開属性 {date: {vital_type: value}}
        self.__medical_history = []  # 非公開属性
        self.__access_log = []  # 非公開属性
        self.__is_data_encrypted = True

    def __log_access(self, action, accessed_by="system"):
        """アクセスを記録(非公開メソッド)"""
        log_entry = {
            'timestamp': datetime.now(),
            'action': action,
            'accessed_by': accessed_by,
            'user_id': self._user_id
        }
        self.__access_log.append(log_entry)

    def __validate_vital_sign(self, vital_type, value):
        """バイタルサインの値を検証(非公開メソッド)"""
        ranges = {
            'heart_rate': (40, 200),
            'blood_pressure_systolic': (80, 200),
            'blood_pressure_diastolic': (50, 120),
            'body_temperature': (35.0, 42.0),
            'blood_oxygen': (70, 100)
        }

        if vital_type not in ranges:
            return False, f"未知のバイタルサインタイプ: {vital_type}"

        min_val, max_val = ranges[vital_type]
        if min_val <= value <= max_val:
            return True, "正常範囲内"
        else:
            return False, f"異常値: {value} ({vital_type})"

    def record_vital_sign(self, vital_type, value, timestamp=None):
        """バイタルサインを記録"""
        if timestamp is None:
            timestamp = datetime.now()

        date_key = timestamp.strftime("%Y-%m-%d")

        # 値の検証
        is_valid, message = self.__validate_vital_sign(vital_type, value)
        if not is_valid:
            print(f"警告: {message}")
            # 実際のシステムでは例外を投げるか、記録しない

        if date_key not in self.__vital_signs:
            self.__vital_signs[date_key] = {}

        if vital_type not in self.__vital_signs[date_key]:
            self.__vital_signs[date_key][vital_type] = []

        record = {
            'value': value,
            'timestamp': timestamp,
            'is_abnormal': not is_valid
        }

        self.__vital_signs[date_key][vital_type].append(record)
        self.__log_access(f"record_vital_sign:{vital_type}")

        print(f"{vital_type}を記録しました: {value}")
        return True

    def add_medical_event(self, event_type, description, severity="medium", timestamp=None):
        """医療イベントを追加"""
        if timestamp is None:
            timestamp = datetime.now()

        event = {
            'event_type': event_type,
            'description': description,
            'severity': severity,
            'timestamp': timestamp,
            'resolved': False
        }

        self.__medical_history.append(event)
        self.__log_access("add_medical_event")

        print(f"医療イベントを記録しました: {event_type}")
        return True

    def __analyze_trends(self, vital_type, days=7):
        """トレンドを分析(非公開メソッド)"""
        end_date = datetime.now()
        start_date = end_date - timedelta(days=days)

        values = []
        dates = []

        current_date = start_date
        while current_date <= end_date:
            date_key = current_date.strftime("%Y-%m-%d")
            if date_key in self.__vital_signs and vital_type in self.__vital_signs[date_key]:
                daily_values = [record['value'] for record in self.__vital_signs[date_key][vital_type]]
                if daily_values:
                    values.append(sum(daily_values) / len(daily_values))
                    dates.append(current_date)
            current_date += timedelta(days=1)

        if len(values) < 2:
            return None

        # 簡易的なトレンド分析
        if values[-1] > values[0] * 1.1:
            trend = "上昇"
        elif values[-1] < values[0] * 0.9:
            trend = "下降"
        else:
            trend = "安定"

        return {
            'vital_type': vital_type,
            'trend': trend,
            'current_value': values[-1],
            'change_percentage': ((values[-1] - values[0]) / values[0]) * 100,
            'data_points': len(values)
        }

    def __detect_anomalies(self, vital_type, value):
        """異常値を検出(非公開メソッド)"""
        thresholds = {
            'heart_rate': {'low': 60, 'high': 100},
            'body_temperature': {'low': 36.0, 'high': 37.5},
            'blood_oxygen': {'low': 95, 'high': 100}
        }

        if vital_type not in thresholds:
            return False

        threshold = thresholds[vital_type]
        return value < threshold['low'] or value > threshold['high']

    def get_health_summary(self, days=30):
        """健康サマリーを取得"""
        end_date = datetime.now()
        start_date = end_date - timedelta(days=days)

        vital_stats = {}
        anomalies = []
        trends = []

        # 主要なバイタルサインの分析
        for vital_type in ['heart_rate', 'body_temperature', 'blood_oxygen']:
            trend = self.__analyze_trends(vital_type, days)
            if trend:
                trends.append(trend)

            # 最新の値を取得
            latest_value = self.get_latest_vital_sign(vital_type)
            if latest_value:
                if self.__detect_anomalies(vital_type, latest_value):
                    anomalies.append({
                        'vital_type': vital_type,
                        'value': latest_value,
                        'timestamp': datetime.now()
                    })

        # 医療イベントの統計
        recent_events = [event for event in self.__medical_history 
                        if event['timestamp'] >= start_date]

        return {
            'user_id': self._user_id,
            'period': f"{days}日間",
            'vital_signs_monitored': len([vt for vt in self.__vital_signs.keys()]),
            'recent_anomalies': len(anomalies),
            'medical_events': len(recent_events),
            'trends': trends,
            'health_status': self.__assess_overall_health(anomalies, recent_events),
            'recommendations': self.__generate_recommendations(anomalies, trends)
        }

    def get_latest_vital_sign(self, vital_type):
        """最新のバイタルサインを取得"""
        if not self.__vital_signs:
            return None

        latest_date = max(self.__vital_signs.keys())
        if vital_type in self.__vital_signs[latest_date]:
            records = self.__vital_signs[latest_date][vital_type]
            if records:
                return records[-1]['value']
        return None

    def __assess_overall_health(self, anomalies, medical_events):
        """全体的な健康状態を評価(非公開メソッド)"""
        if not anomalies and not medical_events:
            return "良好"
        elif len(anomalies) <= 2 and len(medical_events) == 0:
            return "注意必要"
        else:
            return "要医療相談"

    def __generate_recommendations(self, anomalies, trends):
        """推奨事項を生成(非公開メソッド)"""
        recommendations = []

        if anomalies:
            recommendations.append("異常なバイタルサインが検出されました。医療専門家に相談してください。")

        for trend in trends:
            if trend['trend'] == "上昇" and trend['vital_type'] == 'heart_rate':
                recommendations.append("心拍数が上昇傾向にあります。ストレス管理を見直してください。")
            elif trend['trend'] == "下降" and trend['vital_type'] == 'blood_oxygen':
                recommendations.append("血中酸素濃度が低下傾向にあります。呼吸練習を検討してください。")

        if not recommendations:
            recommendations.append("現在の健康状態は良好です。現状維持を心がけてください。")

        return recommendations

    def get_access_log(self, requester="doctor"):
        """アクセスログを取得(限定公開)"""
        if requester not in ["doctor", "system_admin"]:
            return "アクセス権限がありません"

        self.__log_access("view_access_log", requester)

        recent_logs = self.__access_log[-10:]  # 直近10件のみ
        formatted_logs = []

        for log in recent_logs:
            formatted_logs.append({
                'timestamp': log['timestamp'].strftime("%Y-%m-%d %H:%M:%S"),
                'action': log['action'],
                'accessed_by': log['accessed_by']
            })

        return formatted_logs

    @property
    def user_id(self):
        return self._user_id

    @property
    def data_encryption_status(self):
        return "有効" if self.__is_data_encrypted else "無効"

# 使用例
health_system = HealthMonitoringSystem("U12345")

# バイタルサインの記録
health_system.record_vital_sign("heart_rate", 72)
health_system.record_vital_sign("body_temperature", 36.8)
health_system.record_vital_sign("blood_oxygen", 98)
health_system.record_vital_sign("heart_rate", 85)  # 少し高い

# 異常値の記録(テスト)
health_system.record_vital_sign("heart_rate", 45)  # 低すぎる
health_system.record_vital_sign("body_temperature", 38.5)  # 発熱

# 医療イベントの記録
health_system.add_medical_event("checkup", "定期健康診断", "low")

# 健康サマリーの取得
summary = health_system.get_health_summary(7)
print(f"ユーザーID: {summary['user_id']}")
print(f"健康状態: {summary['health_status']}")
print(f"検出された異常: {summary['recent_anomalies']}件")

print("\n推奨事項:")
for recommendation in summary['recommendations']:
    print(f"- {recommendation}")

print(f"\nデータ暗号化: {health_system.data_encryption_status}")

# アクセスログ(医師権限)
access_log = health_system.get_access_log("doctor")
print(f"\n最近のアクセス:")
for log in access_log:
    print(f"{log['timestamp']} - {log['action']} by {log['accessed_by']}")