Python 现代开发学习笔记

(四)集中管理属性的读写

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 也沿用上一章的装饰器写法,只是返回的对象负责管理属性访问。属性管理

笔记正文与全部题解 · Ctrl / ⌘ K