大家好,我是正在实战各种 AI 项目的程序员晚枫。

你有没有见过这样的代码:obj.some_property 看起来像属性访问,但背后其实执行了计算? 或者 obj.load_user 请求一个属性,对象自动帮你从数据库加载?这些都是动态属性和特性的功劳。今天咱们把 __getattr____setattr____slots__ 这些魔法方法一网打尽。


🎯 getattr 和 __getattribute__:属性访问的拦截器

两者的关键区别

方法触发时机使用场景
__getattr__属性不存在时延迟加载、动态属性
__getattribute__每次访问属性时属性访问监控、权限控制

__getattr__:延迟加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class LazyObject:
"""延迟加载属性 —— 访问时才创建"""

def __init__(self):
self._data = {}

def __getattr__(self, name):
"""属性不存在时调用"""
if name.startswith('load_'):
key = name[5:] # load_data → data
# 模拟从数据库加载
value = f"Loaded {key}"
# 缓存到实例属性,下次不再触发 __getattr__
setattr(self, name, value)
return value
raise AttributeError(
f"'{self.__class__.__name__}' has no attribute '{name}'"
)

obj = LazyObject()
print(obj.load_user) # Loaded user —— 首次访问,触发 __getattr__
print(obj.load_user) # Loaded user —— 第二次从缓存读取
# obj.nonexistent # AttributeError

__getattribute__:属性访问的全局监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MonitoredObject:
"""监控所有属性访问"""

def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
self._access_log = []

def __getattribute__(self, name):
if name.startswith('_'):
# 内部属性不记录
return super().__getattribute__(name)
# 记录访问
access_log = super().__getattribute__('_access_log')
access_log.append(name)
return super().__getattribute__(name)

obj = MonitoredObject(name="Alice", age=30)
print(obj.name) # Alice
print(obj.age) # 30
print(obj._access_log) # ['name', 'age']

⚠️ 注意__getattribute__ 中必须用 super().__getattribute__() 访问属性,否则会无限递归!


✏️ setattr 和 __delattr__:属性修改和删除的拦截

__setattr__:控制属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ValidatedObject:
"""带验证的属性赋值"""

def __setattr__(self, name, value):
# 验证规则
if name == 'age' and not isinstance(value, int):
raise TypeError("age 必须是整数")
if name == 'age' and value < 0:
raise ValueError("age 不能为负数")
if name == 'email' and '@' not in str(value):
raise ValueError("email 格式不正确")
# 用 super().__setattr__ 避免无限递归
super().__setattr__(name, value)

obj = ValidatedObject()
obj.name = "Alice" # ✅ 正常
obj.age = 30 # ✅ 正常
# obj.age = -1 # ❌ ValueError
# obj.age = "30" # ❌ TypeError
# obj.email = "bad" # ❌ ValueError

__delattr__:控制属性删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ProtectedObject:
"""受保护的属性删除"""

def __init__(self):
self.name = "Alice"
self._internal = "secret"

def __delattr__(self, name):
if name.startswith('_'):
raise AttributeError(f"不能删除内部属性: {name}")
super().__delattr__(name)

obj = ProtectedObject()
del obj.name # ✅ 允许删除
# del obj._internal # ❌ AttributeError

💾 __slots__:内存优化的利器

为什么需要 __slots__?

Python 对象默认用 __dict__ 存储属性,这是一个哈希表,占用大量内存。当你创建百万级实例时,内存浪费非常惊人。__slots__ 用固定数组替代哈希表,大幅节省内存。

基础用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 普通类(使用 __dict__)
class Person:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email

# 使用 __slots__(节省内存)
class EfficientPerson:
__slots__ = ['name', 'age', 'email']

def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email

# 对比
p1 = Person("Alice", 30, "alice@example.com")
p2 = EfficientPerson("Alice", 30, "alice@example.com")

print(p1.__dict__) # {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
# p2.__dict__ # AttributeError! __slots__ 没有 __dict__

import sys
print(f"普通类: {sys.getsizeof(p1.__dict__)} 字节 + 对象 {sys.getsizeof(p1)} 字节")
print(f"__slots__: 对象 {sys.getsizeof(p2)} 字节(无 __dict__)")

批量创建的性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys
import time

# 普通类
class NormalPoint:
def __init__(self, x, y):
self.x = x
self.y = y

# __slots__ 类
class SlotPoint:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y

# 创建100万个点
N = 1_000_000

start = time.time()
normal_points = [NormalPoint(i, i*2) for i in range(N)]
normal_time = time.time() - start
normal_size = sum(sys.getsizeof(p) + sys.getsizeof(p.__dict__) for p in normal_points[:100])

start = time.time()
slot_points = [SlotPoint(i, i*2) for i in range(N)]
slot_time = time.time() - start
slot_size = sum(sys.getsizeof(p) for p in slot_points[:100])

print(f"普通类: 创建耗时 {normal_time:.2f}s, 估算100个实例内存 {normal_size/1024:.1f}KB")
print(f"__slots__: 创建耗时 {slot_time:.2f}s, 估算100个实例内存 {slot_size/1024:.1f}KB")
print(f"内存节省: {(1 - slot_size/normal_size)*100:.1f}%")

slots 的注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base:
__slots__ = ['a']

class Child(Base):
__slots__ = ['b'] # 只需要定义新增的属性
# 继承的 'a' 不需要重复

c = Child()
c.a = 1 # ✅ 继承自 Base
c.b = 2 # ✅ 自身定义

# 陷阱1:不能动态添加新属性
# c.c = 3 # AttributeError!

# 陷阱2:子类不定义 __slots__ 会退回 __dict__
class BadChild(Base):
pass # 没有 __slots__,会有 __dict__

bc = BadChild()
bc.a = 1
bc.extra = "动态属性" # ✅ 可以,因为有了 __dict__

# 陷阱3:__slots__ 和 __dict__ 可以共存
class Flexible:
__slots__ = ['a', '__dict__'] # 加上 __dict__

f = Flexible()
f.a = 1 # 存在 slot 中
f.extra = "动态" # 存在 __dict__ 中

🏗️ 实战案例:动态 ORM 模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Model:
"""简易 ORM 模型基类"""
__slots__ = ['_fields', '_dirty']

def __init__(self, **kwargs):
object.__setattr__(self, '_fields', {})
object.__setattr__(self, '_dirty', set())
for key, value in kwargs.items():
self._fields[key] = value

def __getattr__(self, name):
if name in self._fields:
return self._fields[name]
raise AttributeError(f"'{self.__class__.__name__}' 没有字段 '{name}'")

def __setattr__(self, name, value):
if name.startswith('_'):
object.__setattr__(self, name, value)
else:
self._fields[name] = value
self._dirty.add(name)

def save(self):
"""保存脏数据"""
if not self._dirty:
print("没有需要保存的数据")
return
print(f"保存字段: {self._dirty}")
self._dirty.clear()

def to_dict(self):
return dict(self._fields)

# 使用
class User(Model):
pass

user = User(name="Alice", age=30, email="alice@example.com")
print(user.name) # Alice
print(user.age) # 30
user.age = 31 # 标记为脏数据
user.save() # 保存字段: {'age'}
print(user.to_dict()) # {'name': 'Alice', 'age': 31, 'email': 'alice@example.com'}

🎯 本讲总结

getattr:属性不存在时触发,适合延迟加载和动态属性。缓存后不再触发。

getattribute:每次属性访问都触发,适合全局监控。小心无限递归!

setattr / delattr:拦截属性赋值和删除,适合验证和权限控制。

slots:用固定数组替代 __dict__,百万级实例可节省 40%+ 内存。但牺牲了动态添加属性的灵活性。

实战场景:ORM 模型、延迟加载、属性验证、内存优化。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》

学习路线: 零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》


🎓 加入《流畅的 Python》直播共读营

学到这里,如果你想系统吃透这本书——欢迎加入我的直播共读课。

  • 每周直播精讲,逐章拆解核心知识点
  • 专属学习群,随时答疑交流
  • 试运营特惠:499 元299 元

👉 【立即报名《流畅的 Python》共读课】https://mp.weixin.qq.com/s/ivHJwn1nNx5ug4TFrapvGg

🔗 课程导航

上一讲:描述符 | 下一讲:类元编程


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!

fluent-python.png