⌨️ 第五章 从设计到测试
📄 本篇摘要

类的解剖图:字段(field)存数据(状态),方法(method)做操作(行为),属性(property)是【看起来像字段的方法】——读取时可以做校验、计算、缓存。三者分工清楚,类的接口才干净。

📝 关系

上一节:继承与多重继承 | 下一节:封装与访问修饰符

一、字段:对象的状态

字段=挂在对象身上的变量。分两种(5-15 见过):

字段是对象【现在是什么样】的记录——对象的状态。状态越少越好管:能算出来的别存(存【出生日期】而不是【年龄】——年龄会过期,日期不会)。

二、方法:对象的行为

方法=定义在类里的函数,第一个参数固定接住实例自己(Python 叫 self)。特殊成员:

三、属性:伪装成字段的方法

直接暴露字段有个大问题:acct.balance = -999 谁也拦不住。属性(property)的解法:语法上照字段用(acct.balance),实际走方法——读时校验、写时把关、值可现算。

class Account:
    def __init__(self):
        self._balance = 0            # 带下划线:内部字段,请勿直接碰
    @property
    def balance(self):               # 读 balance 实际调它
        return self._balance
    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("余额不能为负")
        self._balance = value

好处:调用方永远写 acct.balance——今天直接返回字段,明天改成实时查数据库,调用代码一行不用改。属性是 Python 对【统一访问原则】的实现。

四、动手 Demo:三种成员同台

class Circle:
    count = 0                                  # 类字段:统计造了多少个圆

    def __init__(self, r):
        if r <= 0:
            raise ValueError("半径必须为正")
        self._r = r                            # 实例字段(内部)
        Circle.count += 1

    @property
    def area(self):                            # 属性:现算,不存
        return 3.14159 * self._r ** 2

    @property
    def r(self):
        return self._r
    @r.setter
    def r(self, value):                        # 写时把关
        if value <= 0:
            raise ValueError("半径必须为正")
        self._r = value

    @classmethod
    def total(cls):                            # 类方法:不依赖实例
        return cls.count

    def __str__(self):                         # 魔术方法:打印长相
        return f"圆(r={self._r}, 面积={self.area:.2f})"

c = Circle(2)
print(c)                    # 触发 __str__ -> 属性 area 现算
c.r = 5                     # 走 setter 校验后合法修改
print(c)
print("已造圆数:", Circle.total())
try:
    c.r = -1
except ValueError as e:
    print("拦下非法赋值:", e)

沙箱实跑输出:

圆(r=2, 面积=12.57)
圆(r=5, 面积=78.54)
已造圆数: 1
拦下非法赋值: 半径必须为正

看到什么算成功:打印自动走 __str__;面积随半径现算更新;负半径被 setter 拦下。

读结果:面积没有存进任何字段——它由半径现算(状态最少化);外部代码看起来只是在读 c.area,背后是方法在跑。字段、方法、属性三者的边界,就是【数据怎么存】与【数据怎么给】的分界。

五、30 秒实验

给 Circle 加一个 perimeter 属性(2 * 3.14159 * self._r)重跑——c.perimeter 立刻可用。属性加零成本:调用方语法完全不变。

六、常见误区

误区 真相
字段全公开最方便 公开字段=放弃把关权;后期想加校验就得改所有调用方
能存的都存起来 状态越少 bug 越少;能从现有字段算出的就现算(属性正合适)
property 只是语法糖 它是【统一访问原则】的实现——内部实现可以随便换,接口永远稳定
自测三问(点击展开) 1. **实例字段和类字段的区别?** 答:实例字段每对象一份存独立状态;类字段全体共享(常量、计数器)。 2. **property 解决什么问题?** 答:读写字段时能插入校验/计算/缓存,而调用方语法保持像字段一样简单。 3. **为什么【存出生日期不存年龄】?** 答:年龄可由日期现算且会过期——存了就会忘记更新(状态最少化)。

相关笔记