news 2026/8/25 15:50:59

Python 封装利器 —— @property 深度详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 封装利器 —— @property 深度详解

1..引言:封装复杂

面向对象三大特性:封装、继承、多态。封装的核心是隐藏内部数据,对外提供受控的访问接口

在 Python 中,实现封装最常见的武器就是@property装饰器。它让你既能像访问普通属性一样自然(obj.attr),又能像方法一样实现校验、计算甚至只读控制。

很多人初学时觉得@property只是 “getter/setter 的语法糖”,但其实它的设计哲学远比语法糖深刻 —— 它是Python属性访问一致性的最佳实践。

2. @property基础语法(标准模板+完整调用示例)

python
class Person:
def __init__(self, age):
self._age = age #
习惯用单下划线表示“受保护”

@property
def age(self): # getter
"""我是获取年龄的接口"""
return self._age

@age.setter
def age(self, new_age): # setter
"""我是设置年龄的接口,带校验"""
if new_age < 0:
raise ValueError("年龄不能为负数")
self._age = new_age

@age.deleter
def age(self): # deleter(可选)
"""删除年龄属性"""
del self._age


# ========== 调用示例 ==========
if __name__ == "__main__":
p = Person(18)

# 触发 getter 获取属性
print(p.age) # 输出:18

# 触发 setter 修改属性
p.age = 22
print(p.age) # 输出:22

# 触发 setter,传入非法值,抛出异常
try:
p.age = -5
except ValueError as e:
print(e) # 输出:年龄不能为负数

# 触发 deleter 删除属性
del p.age
# print(p.age) # 删除后访问会报 AttributeError

2.1三个装饰器的书写要求

装饰器

位置

方法名一致性

参数要求

@property

必须写在 getter 上方

固定写法,后无内容

self

@age.setter

写在 setter 上方

必须与getter方法名完全一致(这里是age

self+ 一个接收新值的参数

@age.deleter

写在 deleter 上方

必须与getter方法名完全一致(这里是age

self

⚠️命名致命伤:如果getter命名为def agesetter却写成@Age.setter,会报AttributeError

3.三种实现方式对比(直击痛点)

方式

外部调用

能否校验

代码量

重构友好度

Python 风格

直接暴露公有属性

obj.age = 20

极少

极差(改后外部全崩)

✅ 简洁但不安全

传统get/set方法

obj.set_age(20)

极多

一般(外部需改动)

❌ 啰嗦且非主流

@property

obj.age = 20

适中

极佳(外部无需改动)

✅ 简洁安全

4.为什么要用@property?(核心价值)

4.1向后兼容性(接口不变性)

这是@property最伟大的设计。想象一个项目初期:

python
class User:
def __init__(self, name):
self.name = name #
直接暴露

到处都在用u.name = "Tom"。后来需求变更,要求name必须首字母大写。
如果用传统方式,你只能改成set_name(),牵一发而动全身。
但用@property,你只需悄然升级类内部:

python
class User:
def __init__(self, name):
self._name = name

@property
def name(self):
return self._name

@name.setter
def name(self, value):
self._name = value.capitalize() #
添加逻辑


# 调用示例
if __name__ == "__main__":
u = User("tom")
u.name = "lily"
print(u.name) # 输出:Lily,自动首字母大写,外部代码无需修改

外部代码u.name = "tom"一行不用改,却自动转成了"Tom"
这就是在不破坏接口的前提下增强功能,是@property的灵魂。

4.2实现只读属性(不写setter

python
class Circle:
def __init__(self, radius):
self._radius = radius

@property
def area(self): #
只有 getter,没有 setter
return 3.14 * self._radius ** 2


# 调用示例
if __name__ == "__main__":
c = Circle(10)
print(c.area) # 输出:314.0
# c.area = 100 # 取消注释,报 AttributeError: can't set attribute,只读生效

外部执行c.area = 100会报错AttributeError: can't set attribute,天然实现了只读。

4.3懒加载(延迟计算)

python
class DataLoader:
def _load_from_db(self):
"""
模拟耗时数据库加载"""
print("正在从数据库加载大量数据...")
return [1, 2, 3, 4, 5]

@property
def 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 的参数名建议valuenew_val

5.2外部调用规范(加粗提醒)

python
p = Person(18)

# ✅
正确:直接通过属性名访问(无括号)
print(p.age) # 触发 getter
p.age = 25 # 触发 setter
del p.age # 触发 deleter

# ❌ 错误:当成方法调用(加括号)
# p.age() # TypeError: 'int' object is not callable

黄金法则@property让方法看起来像数据,所以调用时一律不加括号

5.3内部使用的注意事项

增加内部调用演示示例

python
class Demo:
def __init__(self, score):
self._score = score

@property
def score(self):
print("
执行getter")
return self._score

def show(self):
# ✅ 推荐:直接访问底层私有变量,不会触发getter
print(self._score)

# ⚠️ 不推荐:会再次触发 score 的 getter
# print(self.score)


if __name__ == "__main__":
d = Demo(90)
d.show()

在类内部方法中,建议始终通过self._age访问真实数据,而不要用self.age(会再次触发 getter,造成循环或性能损耗)。只有外部才用self.age

6.常见错误案例分析

错误示范代码:

python
class类名:
def __init__(self, x):
self.__age = x

@property
def age(self):
return self.__age

@age.setter
def age(self, newVal):
if newVal > 0 and self.__age == 0:
self.__age = 0 # ❌ 这里写死 0,而不是 newVal

@age.deleter
def age(self):
del self.__age


# 复现bug调用示例
if __name__ == "__main__":
obj = 类名(10)
obj.age = 20
print(obj.age) # 输出依旧是10,setter没有生效

6.1错误分析

  • 逻辑Bug:setter 里self.__age = 0,即便满足条件也只会赋 0,而不会赋newVal
    执行对象a.age = 20后,因为初始__age为 10,self.__age == 0False,条件不满足,setter 什么也不做,所以print(对象a.age)仍然是 10。

✅ 修正后代码:

python
@age.setter
def age(self, newVal):
if newVal > 0: #
只做合理性校验
self.__age = newVal # ✅ 赋新值
else:
raise ValueError("年龄必须大于0")

6.2其他易错点

  1. 忘记写setter:导致属性只读,赋值会报错。
  1. setter中忘记给私有属性赋值:造成属性 “伪修改”。
  1. getter中返回了可变对象(如列表),外部直接修改列表元素会绕过 setter,需要返回副本。

可变对象坑点示例

python
class Student:
def __init__(self):
self._scores = [80, 90]

@property
def 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会产生大量重复代码。此时应升级为自定义描述符(Descriptor):

python
class NonNegative:
def __set_name__(self, owner, name):
self.name = name

def __get__(self, obj, objtype=None):
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] = value

class Order:
quantity = NonNegative()
price = NonNegative()


#
调用示例
if __name__ == "__main__":
o = Order()
o.quantity = 10
o.price = 99
print(o.quantity, o.price)
# o.price = -10 # 抛异常,校验复用生效

描述符可以多处复用,比单独写多个@property更干净。

8.总结:一张图记住@property

Plain Text
+----------------+ +-------------------+
|
外部调用 | | 类内部实际执行 |
| obj.age = 20 | =====> | @age.setter |
| print(obj.age)| =====> | @property getter |
| del obj.age | =====> | @age.deleter |
+----------------+ +-------------------+

  • 使用场景:属性需要校验、计算、只读、延迟加载。
  • 替代方案:传统 get/set(不推荐)、描述符(高级复用)。
  • 核心原则外部像属性,内部像方法,兼顾安全与优雅。

💡忠告:不要为了用而用——如果属性真的不需要任何控制,直接暴露self.age也是可以的,但一旦后期需要增加逻辑,一定要第一时间重构为@property,这是Python开发者成熟的标志。

本文由水哥澎湃学习笔记整理而成,欢迎转载,请注明出处

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 15:49:03

CIMPro孪大师|智慧路桥数字孪生管理平台建设方案

摘要&#xff1a;本文系统介绍了智慧路桥数字孪生平台的构建与应用。针对路桥管养行业存在的资产台账与空间位置脱节、隐蔽病害难发现、数据分散等痛点&#xff0c;提出基于数字孪生技术的解决方案。文章详细阐述了平台的四层架构&#xff08;感知采集层、传输网关层、孪生底座…

作者头像 李华
网站建设 2026/8/25 15:48:04

把 TAPD / 飞书需求变成可审核的 AI 开发流程

我做了一个 Req2Code&#xff0c;让 Codex、Claude Code 和 Cursor 从多平台工作项选择一路走到规划分析、开发、测试与人工发布审批 “让 AI 开发不只会改代码&#xff0c;还能被选择、被追踪、被审核&#xff0c;并且只在明确批准后发布。” Req2Code 0.10.0 Alpha P…

作者头像 李华
网站建设 2026/8/25 15:28:45

基于springboot的云南中草药知识普及管理系统网站源码+文档

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/8/25 15:23:44

stl:reverse

时间复杂度o&#xff08;n&#xff09;空间复杂度o&#xff08;1&#xff09;vector<int>a/string a; reverse(a.begin(),a.end());vector<vector<int>>a上下翻转&#xff08;翻转行&#xff09;&#xff1a;cpp vector<vector<int>> a {{1…

作者头像 李华
网站建设 2026/8/25 15:19:21

Qt 接入DeepSeek官方API实现AI对话

一.准备工作:DeepSeek官方参考文档:链接,官方文档随时可变,因此代码也会发生相应变化,请以官方提供的实际API为准。 二.实验环境:Win11、Qt Creator、Qt 5.9.9 MinGW 32bit. 三.最终效果: 四.工程目录: 五.在.pro文件中添加网络库: 六.完整代码: 1.main.cpp

作者头像 李华
网站建设 2026/8/25 15:19:06

全网都在推 DeepSeek Harness 插件,skill 还有必要装吗?

前言 最近 dsh 非常火&#xff0c;开源不到 48 小时就破了 10万 Star&#xff0c;创下 GitHub 史上最快涨星纪录&#x1f4b9; DeepSeek Harness&#xff08;简称 dsh&#xff09;是 DeepSeek 于 2026年8月13日 开源的 Agent 框架&#xff0c;口号是「一切皆插件」 截止目前&am…

作者头像 李华