在 Python 中,封装指将数据与方法组织在类内,并控制外部对内部状态的访问方式。约定上,单下划线前缀表示内部使用;双下划线前缀会触发名字改写,从类外以原名称直接访问较困难。须在写入时校验数据时,可使用私有属性配合存取方法,或使用 @property 保持属性式调用外观。本节说明上述约定及常见实现方式。

私有属性与对外接口

若余额不应被外部任意改写,可命名为 __balance,仅通过 deposit、get_balance 等方法访问:

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

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def get_balance(self):
        return self.__balance

acc = BankAccount("Tom", 1000)
acc.deposit(500)
print(acc.get_balance())
# print(acc.__balance)   # AttributeError

公开属性带来的风险

若属性完全公开,外部可直接赋值,可能绕过业务规则:

class Bad:
    def __init__(self):
        self.balance = 100

b = Bad()
b.balance = -9999

双下划线与名字改写

以双下划线开头、且结尾少于两个下划线的属性,会被改写为 _类名__属性。外部使用 obj.__code 通常失败;若访问改写后的名称,技术上仍可能读到,故名字改写用于约定与阻碍,并非加密:

class Secret:
    def __init__(self):
        self.__code = 123

s = Secret()
# print(s.__code)         # AttributeError
print(s._Secret__code)    # 能读到,但不要依赖这种写法

单下划线 _name 不触发改写,仅表示不宜从类外直接使用。需要更强隔离时可使用双下划线。

getter 与 setter

读操作经 get 方法,写操作经 set 方法并在 set 中校验,是常见的封装读写方式:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            print("成绩必须在 0~100 之间")

s = Student("Tom", 85)
s.set_score(95)
s.set_score(200)
print(s.get_score())

@property

若希望外部仍使用 s.score / s.score = 95 的形式,同时执行校验逻辑,可使用 @property 与 @score.setter。外观像属性,实际执行方法:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            raise ValueError("成绩必须在 0~100 之间")

s = Student("Tom", 85)
print(s.score)
s.score = 95
print(s.score)

私有方法

仅供类内部调用的辅助步骤可定义为双下划线方法。外部不宜直接调用,类内其他方法可以:

class Calculator:
    def __init__(self, n):
        self.n = n

    def __validate(self):
        if self.n < 0:
            raise ValueError("不能是负数")

    def square(self):
        self.__validate()
        return self.n * self.n

c = Calculator(4)
print(c.square())
# c.__validate()   # AttributeError

常见错误

  • _x 并非语言强制私有,类外仍可访问。
  • 双下划线改写不能阻止刻意访问,不承担保密职责。
  • 无校验需求的简单字段不必一律私有化;过度封装可能降低可读性。

本节说明了 Python 中封装的约定与 @property 用法。下一节介绍文件读写:open、模式与 with 语句。