Menu
Coddy logo textTech

プライベート属性

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 15/64。

プライベート属性は、特定のデータがクラスの外部から直接アクセスされるべきではないことを示すためにアンダースコアを使用します。

なぜプライベート属性が必要なのでしょうか? 銀行口座を持っていると想像してください。誰かに残高を直接変更されたくはありません。銀行が取引を検証し、不正をチェックし、記録を保持できるように、適切なチャネル(deposit/withdraw メソッド)を経由する必要があります。プライベート属性も同じように機能します。これらは、データが誤って変更されるのを防ぎます。

以下は、単一のアンダースコア(「内部利用」のための慣習)を使用した例です:

class Person:
    def __init__(self, name, age):
        self._name = name    # "protected" - 内部利用
        self._age = age      # "protected" - 内部利用

単一のアンダースコアで始まる属性には引き続きアクセス可能ですが、アクセスしないようにという合図です:

person = Person("Alice", 30)
print(person._name)  # 動作しますが、推奨されません

より強力なプライバシー(名前修飾)のために、二重のアンダースコアを使用します:

class Person:
    def __init__(self, name, age):
        self.__name = name   # "private" - 名前修飾されます
        self.__age = age     # "private" - 名前修飾されます
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

なぜゲッター(getter)とセッター(setter)メソッドを使用するのでしょうか? これらは門番のような役割を果たします。__age を誰にでも直接変更させる(負の年齢やその他の無効なデータが発生する可能性があります)代わりに、まず入力を検証する set_age() を強制的に使用させます。これにより、バグを防ぎ、データの整合性を確保します。

「追加のコード」には目的があります:

  • get_name() - 名前を読み取るための制御されたアクセス
  • set_age(age) - 変更を許可する前に年齢を検証する(負の年齢は不可!)
  • これらのメソッドがないと、誰かが age = -100 と設定してプログラムを壊してしまう可能性があります

プライベート属性を操作するには、アクセサメソッドを使用します:

person = Person("Bob", 25)
print(person.get_name())  # Bob

person.set_age(30)        # 有効:年齢は30になります
person.set_age(-5)        # 無効:年齢は正の数である必要があります!(年齢は30のままです)

メリットがわかりますか? set_age() メソッドは無効なデータを防ぎます。これがないと、誤って年齢が-5の人物を作成してしまう可能性があり、それは意味をなしません!

二重アンダースコアの属性は「名前修飾(name mangling)」されますが、それでもアクセスすることは可能です:

person = Person("Charlie", 35)
# これは動作しません:
# print(person.__name)  # AttributeError

# しかし、これは動作します(非推奨):
print(person._Person__name)  # Charlie

実世界の例 - なぜこれらすべてのコードが必要なのか?

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # プライベート: 直接変更することはできません
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
    
    def get_balance(self):
        return self.__balance

# プライベート属性がない場合:
# account.__balance = -1000000  # 大惨事!負の残高が許可されてしまいます

# プライベート属性がある場合:
account = BankAccount(100)
account.deposit(50)           # 安全: バリデーション済み
print(account.get_balance())  # 150

この「追加のコード」(メソッド)は、不正な変更からデータを保護します。それは、貴重品を無防備なままにしておくのではなく、警備員を配置するようなものです。

出力:

Alice
Bob
Age must be positive!
Charlie
150

単一のアンダースコア _attribute は、慣習的に「内部利用のみ」を意味します。二重のアンダースコア __attribute は、より強力なプライバシーのために名前修飾(ネームマングリング)をトリガーします。プライベートデータと適切に対話し、バリデーションを追加するには、ゲッター/セッターメソッド(アクセサメソッド)を使用してください。この「追加のコード」は、データが常に有効であることを保証することで、バグを防ぎます。

challenge icon

チャレンジ

簡単

user.py 内の User クラスをプライベートなパスワード機能で完成させ、次に driver.py でそれを使用してください。TODO コメントに従って、パスワードの確認および変更メソッドを実装してください。

チートシート

プライベート属性は、クラスの外部からデータに直接アクセスすべきではないことを示すためにアンダースコアを使用します。

シングルアンダースコア (_attribute) - 「内部利用」のための慣習:

class Person:
    def __init__(self, name, age):
        self._name = name    # 「保護」 - 内部利用
        self._age = age      # 「保護」 - 内部利用

ダブルアンダースコア (__attribute) - より強力なプライバシーのために名前修飾(name mangling)をトリガーします:

class Person:
    def __init__(self, name, age):
        self.__name = name   # 「プライベート」 - 名前修飾される
        self.__age = age     # 「プライベート」 - 名前修飾される
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

プライベート属性を操作するには、アクセサメソッドを使用します:

person = Person("Bob", 25)
print(person.get_name())  # Bob
person.set_age(30)
person.set_age(-5)        # Age must be positive!

ダブルアンダースコア属性は名前修飾されますが、引き続きアクセス可能です(非推奨):

# これは動作しません:
# print(person.__name)  # AttributeError

# しかし、これは動作します(非推奨):
print(person._Person__name)  # Charlie

自分で試してみよう

# TODO: userモジュールからUserクラスをインポートする

# TODO: 入力からテストケースを取得する
test_case = input()

if test_case == "constructor_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    print("User created successfully")
    print("Initial password set")

elif test_case == "correct_password_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    # TODO: 正しいパスワードでcheck_passwordメソッドをテストする
    #       Trueが返された場合は "Password check successful!" と出力する
    #       Falseが返された場合は "Password check failed!" と出力する
    result = user.check_password("secure123")
    if result:
        print("Password check successful!")
    else:
        print("Password check failed!")

elif test_case == "incorrect_password_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    # TODO: 誤ったパスワード "wrong" でcheck_passwordメソッドをテストする
    #       Falseが返された場合は "Incorrect password rejected!" と出力する
    #       Trueが返された場合は "Security issue: incorrect password accepted!" と出力する
    result = user.check_password("wrong")
    if not result:
        print("Incorrect password rejected!")
    else:
        print("Security issue: incorrect password accepted!")

elif test_case == "change_password_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    # TODO: パスワードを "secure123" から "newpass456" に変更するテストを行う
    #       Trueが返された場合は "Password changed successfully!" と出力する
    #       Falseが返された場合は "Password change failed!" と出力する
    result = user.change_password("secure123", "newpass456")
    if result:
        print("Password changed successfully!")
    else:
        print("Password change failed!")
    
    # TODO: "newpass456" をチェックして、新しいパスワードが機能することを確認する
    #       Trueが返された場合は "New password works!" と出力する
    #       Falseが返された場合は "New password doesn't work!" と出力する
    if user.check_password("newpass456"):
        print("New password works!")
    else:
        print("New password doesn't work!")

elif test_case == "change_password_wrong_old_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    # TODO: 誤った旧パスワードでパスワード変更を試みる
    #       Falseが返された場合は "Security working: incorrect old password rejected!" と出力する
    #       Trueが返された場合は "Security issue: password changed with wrong old password!" と出力する
    result = user.change_password("wrong", "hackerpw")
    if not result:
        print("Security working: incorrect old password rejected!")
    else:
        print("Security issue: password changed with wrong old password!")

elif test_case == "comprehensive_test":
    # TODO: 初期パスワード "secure123" でUserオブジェクトを作成する
    user = User("secure123")
    print("User created with initial password")
    
    # TODO: 正しいパスワードでcheck_passwordメソッドをテストする
    if user.check_password("secure123"):
        print("Initial password verification successful")
    
    # TODO: 誤ったパスワードでcheck_passwordメソッドをテストする
    if not user.check_password("wrongpass"):
        print("Incorrect password properly rejected")
    
    # TODO: パスワードを "secure123" から "newpass456" に変更するテストを行う
    if user.change_password("secure123", "newpass456"):
        print("Password successfully changed")
    
    # TODO: 旧パスワードが機能しなくなったことを確認する
    if not user.check_password("secure123"):
        print("Old password no longer works")
    
    # TODO: 新しいパスワードが機能することを確認する
    if user.check_password("newpass456"):
        print("New password works correctly")
    
    # TODO: 誤った旧パスワードでパスワード変更を試みる
    if not user.change_password("wrongold", "hackerpw"):
        print("Security maintained: wrong old password rejected")
    
    # TODO: 変更試行に失敗した後もパスワードが安全であることを確認する
    if user.check_password("newpass456"):
        print("Password remains secure after failed change attempt")

else:
    print("Unknown test case")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン