(四)集中管理属性的读写
1. 对外操作与内部数据
书籍进度既要能读取,也要能更新,但更新不能把“已读 50 页”变成“已读 250 页、总共 200 页”。可以让一次赋值先经过检查,再更新实际保存的值。
Python 中单个下划线开头的 _read_pages 表示内部使用约定,它仍能通过对象直接访问,调用者按公开接口更新状态。双下划线开头、且末尾没有对应双下划线的 __code,会在类定义中按类名改写,例如 Book 中变为 _Book__code,主要帮助继承中的名称避让。__init__ 这样首尾都有双下划线的名称,则属于语言约定的方法名。它们承担的作用不同。
普通属性可以直接公开。需要在读写时计算、检查或转换的属性,再用 property 集中处理;这样调用处仍可以写 progress.read_pages = 50。
2. property 的读取与设置
下面演示一次成功设置与一次错误输入。raise ValueError(...) 发出带说明的数值错误;try 执行可能出错的语句,except ValueError as error 接收这类错误并显示说明。这里用它看清错误发生前后保存的状态,完整的异常传播会在下一章展开。
# property 把属性读取交给方法,setter 把属性赋值交给检查过程。
class ReadingProgress:
def __init__(self, total_pages):
if total_pages <= 0:
raise ValueError("总页数必须大于 0")
self._total_pages = total_pages
self._read_pages = 0
@property
def total_pages(self):
return self._total_pages
@property
def read_pages(self):
return self._read_pages
@read_pages.setter
def read_pages(self, value):
if not 0 <= value <= self.total_pages:
raise ValueError("已读页数应在 0 到总页数之间")
self._read_pages = value
@property
def ratio(self):
return self._read_pages / self.total_pages
progress = ReadingProgress(200)
progress.read_pages = 50
print(progress.read_pages, progress.ratio)
try:
progress.read_pages = 250
except ValueError as error:
print(error)
print(progress.read_pages)输出 50 0.25、“已读页数应在 0 到总页数之间”、50。第一次赋值进入 setter,检查通过后更新 _read_pages;第二次在更新前发出错误,所以原值保留。
progress.read_pages 不加圆括号,读取时由 property 调用 getter。total_pages 和 ratio 只有 getter,没有提供 setter;例如执行 progress.ratio = 0.5 会报告 AttributeError。比例每次根据当前页数计算,不需要同时维护另一份比例状态。@property 与 @read_pages.setter 也沿用上一章的装饰器写法,只是返回的对象负责管理属性访问。属性管理