Python封装一方面指把数据和方法收进类里,另一方面指控制外部能直接碰哪些内部细节。约定上,单下划线表示内部使用。双下划线会触发名字改写,从类外直接访问会更困难。真正要加校验时,常见做法是私有属性配合 getter / setter,或用 @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不是强制私有,类外仍可读写。- 双下划线改写挡不住刻意访问,也不承担保密职责。
- 不必把所有字段都做成私有再配一套存取方法。没有校验需求的简单数据,公开属性往往更清晰。