欢迎光临
我们一直在努力

《Effective Python》读书笔记07: 元类与属性

作者: andylin02
学习章节: 第6章 – 元类与属性
关键词: @property, 描述符, getattr, init_subclass, set_name, 类装饰器, 元类, 属性验证


第6章思维导图:8条建议全景

#mermaid-svg-Q5gbY2S6bWftyBhh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q5gbY2S6bWftyBhh .error-icon{fill:#552222;}#mermaid-svg-Q5gbY2S6bWftyBhh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q5gbY2S6bWftyBhh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q5gbY2S6bWftyBhh .marker.cross{stroke:#333333;}#mermaid-svg-Q5gbY2S6bWftyBhh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q5gbY2S6bWftyBhh p{margin:0;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge{stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 text{fill:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon–1{font-size:40px;color:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge–1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth–1{stroke-width:17;}#mermaid-svg-Q5gbY2S6bWftyBhh .section–1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-0{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-0{stroke-width:14;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-1{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-1{stroke-width:11;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 text{fill:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-2{stroke-width:8;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-3{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-3{stroke-width:5;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-4{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-4{stroke-width:2;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-5{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-5{stroke-width:-1;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-6{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-6{stroke-width:-4;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-7{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-7{stroke-width:-7;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-8{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-8{stroke-width:-10;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-9{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-9{stroke-width:-13;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 polygon,#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 text{fill:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .node-icon-10{font-size:40px;color:black;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .edge-depth-10{stroke-width:-16;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled circle,#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:lightgray;}#mermaid-svg-Q5gbY2S6bWftyBhh .disabled text{fill:#efefef;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-root rect,#mermaid-svg-Q5gbY2S6bWftyBhh .section-root path,#mermaid-svg-Q5gbY2S6bWftyBhh .section-root circle,#mermaid-svg-Q5gbY2S6bWftyBhh .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-Q5gbY2S6bWftyBhh .section-root text{fill:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-root span{color:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .section-2 span{color:#ffffff;}#mermaid-svg-Q5gbY2S6bWftyBhh .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-Q5gbY2S6bWftyBhh .edge{fill:none;}#mermaid-svg-Q5gbY2S6bWftyBhh .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-Q5gbY2S6bWftyBhh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

第6章 元类与属性 8条建议

属性控制

第44条: 纯属性替代getter/setter

第45条: @property重构属性

第46条: 描述符复用验证逻辑

第47条: __getattr__惰性加载

子类管理

第48条: __init_subclass__验证子类

第49条: __init_subclass__注册类

元类与装饰器

第50条: __set_name__注解属性

第51条: 类装饰器优先于元类


第6章详细笔记:逐条精讲与源代码

本章打开 Python 元编程的大门:你不仅将学会如何精确控制属性访问,还会进入类创建和管理的更底层,编写出强大而自约束的框架代码。

第44条:用纯属性取代 getter 和 setter 方法

核心:Python 不需要预先定义访问方法。直接使用简洁的公有属性。当未来需要控制访问时,使用 @property 无缝迁移,无需修改任何调用代码。

# 初始设计:简洁的公有属性
class Resistor:
def __init__(self, ohms):
self.ohms = ohms

r = Resistor(50e3)
print(r.ohms) # 50000.0
r.ohms = 10e3
print(r.ohms) # 10000.0

# 后续需求:赋值时验证。使用 @property 无缝升级
class Resistor:
def __init__(self, ohms):
self.ohms = ohms # 会调用 setter

@property
def ohms(self):
return self._ohms

@ohms.setter
def ohms(self, ohms):
if ohms <= 0:
raise ValueError(f'阻值必须 > 0,当前 {ohms}')
self._ohms = ohms

# 对外接口完全不变
r = Resistor(50)
r.ohms = 100

关键收获:在 Python 中,开始就使用公有属性。等到确切需要时再引入 @property,这就是“延迟决策”的威力。


第45条:使用 @property 替代属性重构

核心:当类的某一属性需要计算得出时,不要一股脑先修改所有调用点。使用 @property 可以将旧属性转换为动态计算,保持接口稳定。

class Bucket:
def __init__(self, period):
self.period = period
self._queries = []

def add(self, query):
self._queries.append(query)

@property
def weekly_queries(self):
"""动态计算过去一周的查询数"""
cutoff = datetime.now() timedelta(days=7)
return [q for q in self._queries if q.date > cutoff]

关键收获:@property 充当了“计算属性”的平滑迁移通道,避免了破坏性 API 变更。


第46条:用描述符复用 @property 验证逻辑

核心:如果多个属性需要相同的验证(如类型检查、数值范围),@property 会导致大量重复代码。此时应实现描述符协议(__get__/__set__),将逻辑封装为可复用的类。

class Grade:
def __init__(self):
self._value = 0

def __get__(self, instance, owner):
return self._value

def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('成绩必须在0~100之间')
self._value = value

class Exam:
math = Grade()
science = Grade()

exam = Exam()
exam.math = 90
exam.science = 85
# exam.math = 120 # 抛出 ValueError

描述符存储的陷阱:上面代码中 Grade 将值存在自身,导致所有 Exam 实例共享同一成绩。正确做法是将值存在实例的 __dict__ 中:

class Grade:
def __init__(self):
self.name = None

def __set_name__(self, owner, name):
self.name = name

def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name, 0)

def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('成绩必须在0~100之间')
instance.__dict__[self.name] = value

关键收获:描述符是 @property 的“工厂”,让你在类层面复用属性访问控制,但必须注意值的存储位置。


第47条:使用 __getattr__ 实现惰性属性

核心:__getattr__ 仅在常规属性查找失败时被调用,是实现惰性加载(使用时才计算)的理想钩子。它与 __getattribute__ 不同——后者拦截每一次属性访问,容易引发无限递归。

class LazyRecord:
def __init__(self):
self._data = None

def _load_data(self):
print("正在从数据库加载…")
self._data = {'name': 'Alice', 'age': 30}

def __getattr__(self, name):
if self._data is None:
self._load_data()
return self._data.get(name, None)

record = LazyRecord()
print(record.name) # 触发 __getattr__,加载数据并返回 'Alice'
print(record.age) # 不再触发加载,直接返回

属性查找顺序:普通属性 __getattribute__ -> 数据描述符 -> 实例 __dict__ -> 非数据描述符与父类 -> __getattr__。

关键收获:__getattr__ 是“最后一道防线”,用于惰性访问;__getattribute__ 则需极其谨慎,极易导致循环调用。


第48条:使用 __init_subclass__ 验证子类定义

核心:Python 3.6 引入的 __init_subclass__ 钩子在子类被定义时自动触发,是验证子类定义完整性的绝佳位置,比元类写法更简单直接。

class Polygon:
sides = None # 子类必须覆盖

def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if cls.sides is None or cls.sides < 3:
raise ValueError(f'{cls.__name__} 必须定义 sides >= 3')

class Triangle(Polygon):
sides = 3

class Square(Polygon):
sides = 4

# class Line(Polygon): # 这会在定义时直接抛出 ValueError
# sides = 2

关键收获:__init_subclass__ 让你在类定义时进行“静态检查”,在代码运行前就发现子类的结构性问题。


第49条:使用 __init_subclass__ 注册已有的类

核心:除了验证,__init_subclass__ 还是自动注册子类的理想工具。这在插件系统、序列化框架中极为常见。

registry = {}

class BaseSerializer:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
registry[cls.__name__] = cls

class JSONSerializer(BaseSerializer):
pass

class XMLSerializer(BaseSerializer):
pass

print(registry) # {'JSONSerializer': …, 'XMLSerializer': …}

关键收获:利用 __init_subclass__ 构建自动注册机制,避免手动维护“类表”,降低遗忘和错误的风险。


第50条:使用 __set_name__ 为类属性加上注解

核心:描述符可以通过实现 __set_name__ 方法来得知自己被赋予的属性名。这使得描述符可以基于属性名动态调整行为或存储位置,无需在描述符实例化时显式传入名称。

class Field:
def __set_name__(self, owner, name):
self.name = name
self.internal_name = '_' + name

def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.internal_name, None)

def __set__(self, instance, value):
setattr(instance, self.internal_name, value)

class Customer:
first_name = Field()
last_name = Field()

cust = Customer()
cust.first_name = 'John'
cust.last_name = 'Doe'
print(cust.first_name) # John

关键收获:__set_name__ 让描述符自动获取属性名,彻底消除了手动传入名称的重复劳动,尤其适用于 ORM 或表单框架。


第51条:优先使用类装饰器而不是元类

核心:元类极其强大,但也使代码难以理解和调试。在多数情况下,类装饰器能以更简单的方式达到相同目的:它在类定义之后被调用,接收并修改类对象。

# 使用元类验证子类(复杂)
class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
if bases != (object,):
if class_dict.get('sides', 0) < 3:
raise ValueError(f'{name} sides 必须 >= 3')
return type.__new__(meta, name, bases, class_dict)

# 使用类装饰器(简单)
def validate_polygon(cls):
if cls.sides < 3:
raise ValueError(f'{cls.__name__} sides 必须 >= 3')
return cls

@validate_polygon
class Triangle:
sides = 3

@validate_polygon
class Square:
sides = 4

何时才用元类:

  • 需要控制类的创建过程(而不是仅修改结果)
  • 需要让所有子类自动继承某些行为(且不能通过继承实现)
  • 需要拦截类的 __init__ 行为

关键收获:能用类装饰器实现的功能,就不要动用元类。类装饰器至少简单一个数量级。


属性访问控制决策流程

#mermaid-svg-fQSk2aXBM0yEUrG7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fQSk2aXBM0yEUrG7 .error-icon{fill:#552222;}#mermaid-svg-fQSk2aXBM0yEUrG7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fQSk2aXBM0yEUrG7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .marker.cross{stroke:#333333;}#mermaid-svg-fQSk2aXBM0yEUrG7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fQSk2aXBM0yEUrG7 p{margin:0;}#mermaid-svg-fQSk2aXBM0yEUrG7 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster-label text{fill:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster-label span{color:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster-label span p{background-color:transparent;}#mermaid-svg-fQSk2aXBM0yEUrG7 .label text,#mermaid-svg-fQSk2aXBM0yEUrG7 span{fill:#333;color:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .node rect,#mermaid-svg-fQSk2aXBM0yEUrG7 .node circle,#mermaid-svg-fQSk2aXBM0yEUrG7 .node ellipse,#mermaid-svg-fQSk2aXBM0yEUrG7 .node polygon,#mermaid-svg-fQSk2aXBM0yEUrG7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .rough-node .label text,#mermaid-svg-fQSk2aXBM0yEUrG7 .node .label text,#mermaid-svg-fQSk2aXBM0yEUrG7 .image-shape .label,#mermaid-svg-fQSk2aXBM0yEUrG7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-fQSk2aXBM0yEUrG7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .rough-node .label,#mermaid-svg-fQSk2aXBM0yEUrG7 .node .label,#mermaid-svg-fQSk2aXBM0yEUrG7 .image-shape .label,#mermaid-svg-fQSk2aXBM0yEUrG7 .icon-shape .label{text-align:center;}#mermaid-svg-fQSk2aXBM0yEUrG7 .node.clickable{cursor:pointer;}#mermaid-svg-fQSk2aXBM0yEUrG7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .arrowheadPath{fill:#333333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQSk2aXBM0yEUrG7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fQSk2aXBM0yEUrG7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQSk2aXBM0yEUrG7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster text{fill:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 .cluster span{color:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fQSk2aXBM0yEUrG7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fQSk2aXBM0yEUrG7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-fQSk2aXBM0yEUrG7 .icon-shape,#mermaid-svg-fQSk2aXBM0yEUrG7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQSk2aXBM0yEUrG7 .icon-shape p,#mermaid-svg-fQSk2aXBM0yEUrG7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fQSk2aXBM0yEUrG7 .icon-shape .label rect,#mermaid-svg-fQSk2aXBM0yEUrG7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQSk2aXBM0yEUrG7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fQSk2aXBM0yEUrG7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fQSk2aXBM0yEUrG7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

需要控制属性访问

是否需要简单验证?

使用@property

多个属性需要相同验证?

实现描述符

需要惰性加载?

使用__getattr__

需要验证子类定义?

使用__init_subclass__

需要自动注册类?

使用__init_subclass__

需要修改类创建行为?

类装饰器,必要时才用元类

保持简单:公有属性


元类 vs 类装饰器选择决策流程

#mermaid-svg-y4yzImd6dgWS97h1{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-y4yzImd6dgWS97h1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-y4yzImd6dgWS97h1 .error-icon{fill:#552222;}#mermaid-svg-y4yzImd6dgWS97h1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-y4yzImd6dgWS97h1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-y4yzImd6dgWS97h1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-y4yzImd6dgWS97h1 .marker.cross{stroke:#333333;}#mermaid-svg-y4yzImd6dgWS97h1 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-y4yzImd6dgWS97h1 p{margin:0;}#mermaid-svg-y4yzImd6dgWS97h1 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-y4yzImd6dgWS97h1 .cluster-label text{fill:#333;}#mermaid-svg-y4yzImd6dgWS97h1 .cluster-label span{color:#333;}#mermaid-svg-y4yzImd6dgWS97h1 .cluster-label span p{background-color:transparent;}#mermaid-svg-y4yzImd6dgWS97h1 .label text,#mermaid-svg-y4yzImd6dgWS97h1 span{fill:#333;color:#333;}#mermaid-svg-y4yzImd6dgWS97h1 .node rect,#mermaid-svg-y4yzImd6dgWS97h1 .node circle,#mermaid-svg-y4yzImd6dgWS97h1 .node ellipse,#mermaid-svg-y4yzImd6dgWS97h1 .node polygon,#mermaid-svg-y4yzImd6dgWS97h1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-y4yzImd6dgWS97h1 .rough-node .label text,#mermaid-svg-y4yzImd6dgWS97h1 .node .label text,#mermaid-svg-y4yzImd6dgWS97h1 .image-shape .label,#mermaid-svg-y4yzImd6dgWS97h1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-y4yzImd6dgWS97h1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-y4yzImd6dgWS97h1 .rough-node .label,#mermaid-svg-y4yzImd6dgWS97h1 .node .label,#mermaid-svg-y4yzImd6dgWS97h1 .image-shape .label,#mermaid-svg-y4yzImd6dgWS97h1 .icon-shape .label{text-align:center;}#mermaid-svg-y4yzImd6dgWS97h1 .node.clickable{cursor:pointer;}#mermaid-svg-y4yzImd6dgWS97h1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-y4yzImd6dgWS97h1 .arrowheadPath{fill:#333333;}#mermaid-svg-y4yzImd6dgWS97h1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-y4yzImd6dgWS97h1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-y4yzImd6dgWS97h1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y4yzImd6dgWS97h1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-y4yzImd6dgWS97h1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y4yzImd6dgWS97h1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-y4yzImd6dgWS97h1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-y4yzImd6dgWS97h1 .cluster text{fill:#333;}#mermaid-svg-y4yzImd6dgWS97h1 .cluster span{color:#333;}#mermaid-svg-y4yzImd6dgWS97h1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-y4yzImd6dgWS97h1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-y4yzImd6dgWS97h1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-y4yzImd6dgWS97h1 .icon-shape,#mermaid-svg-y4yzImd6dgWS97h1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y4yzImd6dgWS97h1 .icon-shape p,#mermaid-svg-y4yzImd6dgWS97h1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-y4yzImd6dgWS97h1 .icon-shape .label rect,#mermaid-svg-y4yzImd6dgWS97h1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y4yzImd6dgWS97h1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-y4yzImd6dgWS97h1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-y4yzImd6dgWS97h1 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

需在类创建时介入

仅需修改或增强类?

使用类装饰器

需要影响所有子类?

能否通过继承实现?

使用基类+__init_subclass__

考虑元类

需要拦截类创建中间态?


第6章总结:元编程的掌控之道

  • 属性演进路径:公有属性 -> @property -> 描述符。从简到繁,只在实际需要时才增加复杂度。
  • 惰性加载:利用 __getattr__ 实现按需加载,避免构造函数中的过度开销。
  • 子类治理:__init_subclass__ 是验证和注册子类的现代利器,优先于元类。
  • 描述符完善:通过 __set_name__ 自动获取属性名,让描述符编写更清洁。
  • 克制地使用元类:类装饰器能解决大部分需求;元类是终极武器,非必要时不轻易动用。
  • 掌握这些,你将具备编写框架级 Python 代码的能力——既强大又可控。


    下一章预告:第7章 – 并发与并行

    属性与元类的内功修毕后,我们将直面现代编程中最棘手的挑战之一:并发与并行。

    你将学到:

    • Python 线程(threading)、进程(multiprocessing)与协程(asyncio)的本质区别与适用场景
    • 如何用 Queue 安全地协调线程,用 concurrent.futures 简化并发执行
    • 如何避免竞态条件、死锁等令人头疼的并发陷阱

    第7章将让你在多核时代写出高效且正确的并发代码。


    本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
    👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

    赞(0)
    未经允许不得转载:171主机测评 » 《Effective Python》读书笔记07: 元类与属性
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址