在 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 语句。