Menu
CoddyTech

私有属性

本课属于 Coddy Python 学习旅程的“面向对象编程”单元。第 15 课,共 64 课。

私有属性使用下划线表示某些数据不应从类外部直接访问。

为什么需要私有属性?想象一下,你有一个银行账户。你不希望任何人直接更改你的余额,他们应该通过适当的渠道(存款/取款方法)进行操作,这样银行才能验证交易、检查欺诈行为、保存记录等。私有属性的作用也是如此,它们可以保护你的数据,避免数据被错误地更改。

下面是一个使用单下划线的示例(表示“内部使用”的约定):

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(),先验证输入。这可以防止 bug,并确保数据完整性。

这些“额外代码”是有用的:

  • get_name() - 以受控的方式读取姓名
  • set_age(age) - 在允许修改前验证年龄(不能是负数!)
  • 没有这些方法,有人可能会设置 age = -100,导致程序出错

使用访问器方法与私有属性交互:

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

person.set_age(30)        # 有效:age 变为 30
person.set_age(-5)        # 无效:Age 必须为正数!(age 保持为 30)

看到好处了吗?set_age() 方法可以防止无效数据。如果没有它,你可能会意外创建一个年龄为 -5 的人,这毫无意义!

双下划线属性会被“名称改写”,但仍然可以访问:

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 会触发名称改写,以增强隐私保护。使用 getter/setter 方法(访问器方法)可以正确地操作私有数据并添加验证。“额外代码”通过确保数据始终有效来避免错误。

challenge icon

挑战

简单

在 user.py 中完善 User 类,实现私有密码功能,然后在 driver.py 中使用它。按照 TODO 注释,实现密码检查和修改方法。

亲手试试

# 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自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Python 在线编译器