第二十九篇:slots 与 @property
前言
Python 类默认允许动态添加属性,这带来了灵活性但也消耗了大量内存。__slots__ 限制了实例属性,节省空间;而 @property 将方法变成属性调用。本文将深入讲解这两大特性及其实际应用。
slots —— 限制实例属性的利器
问题背景
class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.password = password
u1 = User("小明", "xm@example.com")
u2 = User("小红", "xh@example.com")
print(u1.__dict__)
# {'name': '小明', 'email': 'xiaoming@example.com', 'password': 'secret'}
每个实例都有一个 __dict__ 字典存储属性,开销较大。
使用 slots 节省内存
class UserSlots:
__slots__ = ["name", "email", "password"]
def __init__(self, name, email, password):
self.name = name
self.email = email
self.password = password
u1 = UserSlots("小明", "xm@example.com", "secret")
u2 = UserSlots("小红", "xh@example.com", "secret")
print(u1.__dict__) # AttributeError! 没有 __dict__
print(u1.name) # 小明 — 仍然可以访问
# ⚠️ 不能动态添加新属性
# u1.age = 25 # AttributeError!
slots 的性能对比
import sys
class UserWithDict:
pass
class UserWithSlots:
__slots__ = ["name", "age"]
with_dict = [UserWithDict() for _ in range(1_000_000)]
with_slots = [UserWithSlots() for _ in range(1_000_000)]
total_with_dict = sum(sys.getsizeof(vars(u)) for u in with_dict)
total_with_slots = 0 # 无 __dict__ 开销
print(f"有 __dict__: {total_with_dict / 1024**2:.2f} MB")
# 有 __dict__: 38.15 MB
print(f"无 __dict__: {sys.getsizeof(with_slots) / 1024**2:.2f} MB")
# 无 __dict__: ~7.63 MB — 节省约 80% 内存!
slots 的注意事项
class Person:
__slots__ = ["name", "_age"]
def __init__(self, name, age):
self.name = name
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0:
raise ValueError("年龄必须是正整数")
self._age = value
p = Person("小明", 25)
print(p.age) # 25
p.age = 26 # 合法
p.age = -5 # ValueError!
@property —— 将方法变成属性
基本用法
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
"""面积(只读属性)"""
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(10, 5)
print(rect.area) # 50 — 像属性一样访问
print(rect.perimeter) # 30
getter / setter / deleter
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
@property
def balance(self):
"""查询余额"""
return self._balance
@balance.setter
def balance(self, amount):
"""设置余额(受保护)"""
if amount < 0:
raise ValueError("余额不能为负数")
self._balance = amount
@balance.deleter
def balance(self):
"""删除余额"""
del self._balance
account = BankAccount("小明", 1000)
print(account.balance) # 1000
account.balance = 2000 # 更新余额
print(account.balance) # 2000
del account.balance # 删除余额
综合应用示例
from datetime import datetime
class Task:
def __init__(self, title, priority="normal"):
self.title = title
self._priority = priority
self._created_at = datetime.now()
self.completed = False
@property
def priority(self):
return self._priority
@priority.setter
def priority(self, value):
valid_values = ["low", "normal", "high"]
if value not in valid_values:
raise ValueError(f"优先级必须是: {', '.join(valid_values)}")
self._priority = value
@property
def created_at(self):
return self._created_at.strftime("%Y-%m-%d %H:%M:%S")
def complete(self):
self.completed = True
task = Task("完成报告", "high")
print(task.priority) # high
print(task.created_at) # 2026-09-09 12:00:00
print(task.completed) # False
task.complete()
print(task.completed) # True
task.priority = "invalid" # ValueError!
实战:数据验证模型
class DataValidator:
"""数据验证基类"""
def __init__(self, **kwargs):
for key, value in kwargs.items():
if hasattr(type(self), key):
setattr(self, key, value) # 触发 property setter
else:
raise AttributeError(f"未知字段:{key}")
@classmethod
def from_dict(cls, data: dict):
instance = cls()
for key, value in data.items():
setattr(instance, key, value)
return instance
class UserModel(DataValidator):
__slots__ = ["name", "age", "email", "phone"]
def __init__(self):
self.name = None
self.age = None
self.email = None
self.phone = None
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str) or len(value.strip()) == 0:
raise ValueError("姓名不能为空")
self._name = value.strip()
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or not (0 <= value <= 150):
raise ValueError("年龄必须在 0~150 之间")
self._age = value
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" not in value or "." not in value:
raise ValueError("邮箱格式错误")
self._email = value
@property
def phone(self):
return self._phone
@phone.setter
def phone(self, value):
digits = "".join(c for c in value if c.isdigit())
if len(digits) != 11 or not digits.startswith("1"):
raise ValueError("手机号格式错误")
self._phone = digits
# 使用示例
try:
user = UserModel(name="小明", age=25, email="xm@test.com", phone="13800138000")
print(user.name)
print(user.age)
print(user.email)
print(user.phone)
except ValueError as e:
print(f"错误:{e}")
# 批量处理
data_list = [
{"name": "张三", "age": 20, "email": "zhangsan@test.com", "phone": "13800138000"},
{"name": "", "age": -5, "email": "invalid", "phone": "123"}, # 错误数据
]
valid_users = []
for data in data_list:
try:
user = UserModel.from_dict(data)
valid_users.append(user)
except ValueError as e:
print(f"跳过:{data['name']} — {e}")
总结
本文讲解了 slots 和 @property 的核心知识:
- slots — 限制实例属性,节省大量内存,禁用动态添加
- @property getter — 将方法变成只读属性
- @property setter — 添加验证逻辑,保证数据合法性
- @property deleter — 控制属性删除行为
- 性能优化 — 百万级对象可节省 80%+ 内存
- 最佳实践 — 私有变量前缀
_+ property 封装
下一篇文章将深入讲解多重继承与 Mixin 模式。
本文是 Python 教程系列的第二十九篇。
