
1..引言封装≠复杂面向对象三大特性封装、继承、多态。封装的核心是隐藏内部数据对外提供受控的访问接口。在 Python 中实现封装最常见的武器就是property装饰器。它让你既能像访问普通属性一样自然obj.attr又能像方法一样实现校验、计算甚至只读控制。很多人初学时觉得property只是 “getter/setter 的语法糖”但其实它的设计哲学远比语法糖深刻 —— 它是Python对“属性访问一致性”的最佳实践。2. property基础语法标准模板完整调用示例pythonclass Person:def __init__(self, age):self._age age #习惯用单下划线表示“受保护”propertydef age(self): # getter我是获取年龄的接口return self._ageage.setterdef age(self, new_age): # setter我是设置年龄的接口带校验if new_age 0:raise ValueError(年龄不能为负数)self._age new_ageage.deleterdef age(self): # deleter可选删除年龄属性del self._age# 调用示例 if __name__ __main__:p Person(18)# 触发 getter 获取属性print(p.age) # 输出18# 触发 setter 修改属性p.age 22print(p.age) # 输出22# 触发 setter传入非法值抛出异常try:p.age -5except ValueError as e:print(e) # 输出年龄不能为负数# 触发 deleter 删除属性del p.age# print(p.age) # 删除后访问会报 AttributeError2.1三个装饰器的书写要求装饰器位置方法名一致性参数要求property必须写在 getter 上方固定写法后无内容仅selfage.setter写在 setter 上方必须与getter方法名完全一致这里是ageself 一个接收新值的参数age.deleter写在 deleter 上方必须与getter方法名完全一致这里是age仅self⚠️命名致命伤如果getter命名为def agesetter却写成Age.setter会报AttributeError。3.三种实现方式对比直击痛点方式外部调用能否校验代码量重构友好度Python 风格直接暴露公有属性obj.age 20❌极少极差改后外部全崩✅ 简洁但不安全传统get/set方法obj.set_age(20)✅极多一般外部需改动❌ 啰嗦且非主流propertyobj.age 20✅适中极佳外部无需改动✅ 简洁安全4.为什么要用property核心价值4.1向后兼容性接口不变性这是property最伟大的设计。想象一个项目初期pythonclass User:def __init__(self, name):self.name name #直接暴露到处都在用u.name Tom。后来需求变更要求name必须首字母大写。如果用传统方式你只能改成set_name()牵一发而动全身。但用property你只需悄然升级类内部pythonclass User:def __init__(self, name):self._name namepropertydef name(self):return self._namename.setterdef name(self, value):self._name value.capitalize() #添加逻辑# 调用示例if __name__ __main__:u User(tom)u.name lilyprint(u.name) # 输出Lily自动首字母大写外部代码无需修改外部代码u.name tom一行不用改却自动转成了Tom。这就是“在不破坏接口的前提下增强功能”是property的灵魂。4.2实现只读属性不写setterpythonclass Circle:def __init__(self, radius):self._radius radiuspropertydef area(self): #只有 getter没有 setterreturn 3.14 * self._radius ** 2# 调用示例if __name__ __main__:c Circle(10)print(c.area) # 输出314.0# c.area 100 # 取消注释报 AttributeError: cant set attribute只读生效外部执行c.area 100会报错AttributeError: cant set attribute天然实现了只读。4.3懒加载延迟计算pythonclass DataLoader:def _load_from_db(self):模拟耗时数据库加载print(正在从数据库加载大量数据...)return [1, 2, 3, 4, 5]propertydef huge_data(self):if not hasattr(self, _huge_data):self._huge_data self._load_from_db() # 首次访问才加载return self._huge_data# 调用示例if __name__ __main__:dl DataLoader()print(对象创建完成尚未加载数据)print(dl.huge_data) # 第一次访问执行加载打印加载日志print(dl.huge_data) # 第二次访问直接读取缓存不再加载5.调用方式与习惯直观必看5.1定义时的习惯命名私有变量用_age或__age双下划线会名称修饰更隐蔽但通常_age足够。getter 名就是对外属性名尽量用名词。setter 的参数名建议value或new_val。5.2外部调用规范加粗提醒pythonp Person(18)# ✅正确直接通过属性名访问无括号print(p.age) # 触发 getterp.age 25 # 触发 setterdel p.age # 触发 deleter# ❌ 错误当成方法调用加括号# p.age() # TypeError: int object is not callable黄金法则property让方法看起来像数据所以调用时一律不加括号。5.3内部使用的注意事项增加内部调用演示示例pythonclass Demo:def __init__(self, score):self._score scorepropertydef score(self):print(执行getter)return self._scoredef show(self):# ✅ 推荐直接访问底层私有变量不会触发getterprint(self._score)# ⚠️ 不推荐会再次触发 score 的 getter# print(self.score)if __name__ __main__:d Demo(90)d.show()在类内部方法中建议始终通过self._age访问真实数据而不要用self.age会再次触发 getter造成循环或性能损耗。只有外部才用self.age。6.常见错误案例分析错误示范代码pythonclass类名:def __init__(self, x):self.__age xpropertydef age(self):return self.__ageage.setterdef age(self, newVal):if newVal 0 and self.__age 0:self.__age 0 # ❌ 这里写死 0而不是 newValage.deleterdef age(self):del self.__age# 复现bug调用示例if __name__ __main__:obj 类名(10)obj.age 20print(obj.age) # 输出依旧是10setter没有生效6.1错误分析逻辑Bugsetter 里self.__age 0即便满足条件也只会赋 0而不会赋newVal。执行对象a.age 20后因为初始__age为 10self.__age 0为False条件不满足setter 什么也不做所以print(对象a.age)仍然是 10。✅ 修正后代码pythonage.setterdef age(self, newVal):if newVal 0: #只做合理性校验self.__age newVal # ✅ 赋新值else:raise ValueError(年龄必须大于0)6.2其他易错点忘记写setter导致属性只读赋值会报错。setter中忘记给私有属性赋值造成属性 “伪修改”。getter中返回了可变对象如列表外部直接修改列表元素会绕过 setter需要返回副本。可变对象坑点示例pythonclass Student:def __init__(self):self._scores [80, 90]propertydef scores(self):return self._scores # ❌返回原始列表外部可篡改if __name__ __main__:s Student()s.scores.append(100)print(s._scores) # 内部数据被悄悄修改setter完全没有触发# ✅ 修复返回副本# return self._scores.copy()7.进阶property与描述符的取舍当你发现多个类或多个属性需要相同的校验逻辑如 “非负整数”property会产生大量重复代码。此时应升级为自定义描述符Descriptorpythonclass NonNegative:def __set_name__(self, owner, name):self.name namedef __get__(self, obj, objtypeNone):return obj.__dict__.get(self.name, 0)def __set__(self, obj, value):if value 0:raise ValueError(f{self.name} must be 0)obj.__dict__[self.name] valueclass Order:quantity NonNegative()price NonNegative()#调用示例if __name__ __main__:o Order()o.quantity 10o.price 99print(o.quantity, o.price)# o.price -10 # 抛异常校验复用生效描述符可以多处复用比单独写多个property更干净。8.总结一张图记住propertyPlain Text---------------- -------------------|外部调用 | | 类内部实际执行 || obj.age 20 | | age.setter || print(obj.age)| | property getter || del obj.age | | age.deleter |---------------- -------------------使用场景属性需要校验、计算、只读、延迟加载。替代方案传统 get/set不推荐、描述符高级复用。核心原则外部像属性内部像方法兼顾安全与优雅。忠告不要为了用而用——如果属性真的不需要任何控制直接暴露self.age也是可以的但一旦后期需要增加逻辑一定要第一时间重构为property这是Python开发者成熟的标志。本文由“水哥澎湃”学习笔记整理而成欢迎转载请注明出处