1. 引言
Python 中的「特殊方法」(也叫魔术方法、双下划线方法)是解释器在特定时机自动调用的钩子。它们让自定义类能够像内置类型一样参与对象创建、销毁、字符串转换、属性访问等操作。本文围绕六个最常见的特殊方法展开,并给出可直接运行的示例。
2. 六个常见特殊方法速览
| 特殊方法 | 触发时机 | 典型用途 |
|---|---|---|
__new__() |
对象实例化时第一个被调用 | 控制对象创建过程,常用于不可变类型、单例 |
__init__() |
对象创建完成、返回给调用者之前 | 初始化实例属性 |
__del__() |
对象被垃圾回收时 | 释放外部资源(慎用) |
__str__() |
对实例调用 str() / print() |
面向人类可读的输出 |
__repr__() |
对实例调用 repr() / 交互式回显 |
面向机器可读、可复现的输出 |
__getattribute__() |
每次访问属性时 | 属性访问拦截、代理、日志 |
3. __new__():对象创建的第一站
__new__() 是一个静态方法 (即使不写 @staticmethod 也会被隐式当作静态方法),它的第一个参数是类本身 cls,负责创建并返回 实例。只有它返回了该类的实例,__init__() 才会被调用。
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("__new__ 被调用:创建实例")
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
print("__init__ 被调用")
self.name = name
a = Singleton("A")
b = Singleton("B")
print(a is b) # True
print(a.name) # B(第二次 __init__ 覆盖了属性)
要点:
__new__()返回的不是本类实例时,__init__()不会被调用。- 继承不可变类型(如
int、str、tuple)时,必须重写__new__()才能改变创建结果。
python
class PositiveInt(int):
def __new__(cls, value):
if value < 0:
raise ValueError("必须是非负数")
return super().__new__(cls, value)
print(PositiveInt(10)) # 10
# PositiveInt(-1) # ValueError: 必须是非负数
4. __init__():实例初始化
__init__() 在实例已经创建之后被调用,用于设置初始状态。它不应该返回任何值 (返回非 None 会抛 TypeError)。
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x, p.y) # 1 2
__new__() 与 __init__() 的执行顺序:
#mermaid-svg-jSCisJESn03KIQUS{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-jSCisJESn03KIQUS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jSCisJESn03KIQUS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jSCisJESn03KIQUS .error-icon{fill:#552222;}#mermaid-svg-jSCisJESn03KIQUS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jSCisJESn03KIQUS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jSCisJESn03KIQUS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jSCisJESn03KIQUS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jSCisJESn03KIQUS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jSCisJESn03KIQUS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jSCisJESn03KIQUS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jSCisJESn03KIQUS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jSCisJESn03KIQUS .marker.cross{stroke:#333333;}#mermaid-svg-jSCisJESn03KIQUS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jSCisJESn03KIQUS p{margin:0;}#mermaid-svg-jSCisJESn03KIQUS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jSCisJESn03KIQUS .cluster-label text{fill:#333;}#mermaid-svg-jSCisJESn03KIQUS .cluster-label span{color:#333;}#mermaid-svg-jSCisJESn03KIQUS .cluster-label span p{background-color:transparent;}#mermaid-svg-jSCisJESn03KIQUS .label text,#mermaid-svg-jSCisJESn03KIQUS span{fill:#333;color:#333;}#mermaid-svg-jSCisJESn03KIQUS .node rect,#mermaid-svg-jSCisJESn03KIQUS .node circle,#mermaid-svg-jSCisJESn03KIQUS .node ellipse,#mermaid-svg-jSCisJESn03KIQUS .node polygon,#mermaid-svg-jSCisJESn03KIQUS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jSCisJESn03KIQUS .rough-node .label text,#mermaid-svg-jSCisJESn03KIQUS .node .label text,#mermaid-svg-jSCisJESn03KIQUS .image-shape .label,#mermaid-svg-jSCisJESn03KIQUS .icon-shape .label{text-anchor:middle;}#mermaid-svg-jSCisJESn03KIQUS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jSCisJESn03KIQUS .rough-node .label,#mermaid-svg-jSCisJESn03KIQUS .node .label,#mermaid-svg-jSCisJESn03KIQUS .image-shape .label,#mermaid-svg-jSCisJESn03KIQUS .icon-shape .label{text-align:center;}#mermaid-svg-jSCisJESn03KIQUS .node.clickable{cursor:pointer;}#mermaid-svg-jSCisJESn03KIQUS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jSCisJESn03KIQUS .arrowheadPath{fill:#333333;}#mermaid-svg-jSCisJESn03KIQUS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jSCisJESn03KIQUS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jSCisJESn03KIQUS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jSCisJESn03KIQUS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jSCisJESn03KIQUS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jSCisJESn03KIQUS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jSCisJESn03KIQUS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jSCisJESn03KIQUS .cluster text{fill:#333;}#mermaid-svg-jSCisJESn03KIQUS .cluster span{color:#333;}#mermaid-svg-jSCisJESn03KIQUS 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-jSCisJESn03KIQUS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jSCisJESn03KIQUS rect.text{fill:none;stroke-width:0;}#mermaid-svg-jSCisJESn03KIQUS .icon-shape,#mermaid-svg-jSCisJESn03KIQUS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jSCisJESn03KIQUS .icon-shape p,#mermaid-svg-jSCisJESn03KIQUS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jSCisJESn03KIQUS .icon-shape .label rect,#mermaid-svg-jSCisJESn03KIQUS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jSCisJESn03KIQUS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jSCisJESn03KIQUS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jSCisJESn03KIQUS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
调用 Point(1, 2)
new(cls, 1, 2) 创建实例
返回的是 cls 实例?
init(self, 1, 2) 初始化
跳过 init,直接返回
返回实例给调用者
5. __del__():对象销毁器
__del__() 在对象被垃圾回收时调用。需要注意:执行 del xxx 只是删除一个引用,并不会主动调用 __del__(),只有当该对象的引用计数降为 0(且被 GC 回收)时才会触发。
python
import gc
class Resource:
def __init__(self, name):
self.name = name
print(f"打开资源 {self.name}")
def __del__(self):
print(f"释放资源 {self.name}")
r1 = Resource("db")
r2 = r1
del r1 # 只是删掉一个引用,引用计数仍为 1,不会触发 __del__
print("删除 r1 之后")
del r2 # 引用计数归零,触发 __del__
print("删除 r2 之后")
gc.collect()
输出大致为:
text
打开资源 db
删除 r1 之后
释放资源 db
删除 r2 之后
使用建议:
__del__()的调用时机不确定,不要依赖它做关键资源释放 ,优先使用with语句与上下文管理器。- 存在循环引用时,
__del__()可能延迟甚至不被调用。
6. __str__() 与 __repr__():两种可读性
__str__():面向人类 ,追求可读性,print()和str()会调用它。__repr__():面向机器/开发者 ,理想情况下输出能作为有效的 Python 代码重新构造该对象,交互式环境回显和repr()会调用它。
python
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name}({self.age} 岁)"
def __repr__(self):
return f"User(name={self.name!r}, age={self.age!r})"
u = User("Alice", 30)
print(str(u)) # Alice(30 岁)
print(repr(u)) # User(name='Alice', age=30)
print(u) # Alice(30 岁),print 优先使用 __str__
补充规则:
- 只定义
__repr__()时,str()会退化为调用__repr__();反之不成立。 - 容器(list、dict 等)打印元素时使用的是元素的
__repr__()。
python
class OnlyRepr:
def __repr__(self):
return "OnlyRepr()"
print(str(OnlyRepr())) # OnlyRepr()
print([OnlyRepr()]) # [OnlyRepr()]
7. __getattribute__():属性访问拦截器
__getattribute__() 会在每一次 属性访问时被调用(无论属性是否存在),是属性访问的最外层入口。它必须返回属性值,否则会抛 AttributeError。
p.secret → 自动传 "secret" → name = "secret"
p.public → 自动传 "public" → name = "public"
python
class Logged:
def __init__(self):
self.value = 42
def __getattribute__(self, name):
print(f"访问属性:{name}")
return super().__getattribute__(name)
obj = Logged()
print(obj.value) # 先打印「访问属性:value」,再打印 42
一个更实用的例子:给属性访问加白名单与日志。
python
class Protected:
def __init__(self):
self.public = "公开数据"
self._secret = "机密数据"
def __getattribute__(self, name):
if name.startswith("_"):
raise AttributeError(f"禁止访问私有属性:{name}")
return super().__getattribute__(name)
p = Protected()
print(p.public) # 公开数据
# print(p._secret) # AttributeError: 禁止访问私有属性:_secret
注意事项:
- 在
__getattribute__()内部访问self.xxx会递归调用自身 ,务必使用super().__getattribute__(name)或object.__getattribute__(self, name)。 - 若只想在属性不存在 时兜底,应使用
__getattr__(),它只在常规查找失败后调用。
python
class Defaults:
def __getattr__(self, name):
return f"{name} 不存在,返回默认值"
d = Defaults()
print(d.anything) # anything 不存在,返回默认值
8. 综合示例:一个带完整生命周期的类
python
class Tracked:
def __new__(cls, *args, **kwargs):
print("1. __new__")
return super().__new__(cls)
def __init__(self, name):
print("2. __init__")
self.name = name
def __getattribute__(self, item):
if item == "name":
print("3. __getattribute__ 拦截 name")
return super().__getattribute__(item)
def __str__(self):
return f"Tracked({self.name})"
def __repr__(self):
return f"Tracked(name={self.name!r})"
def __del__(self):
print("5. __del__")
t = Tracked("demo") # 1 -> 2
print(str(t)) # 3 -> Tracked(demo)
print(repr(t)) # 3 -> Tracked(name='demo')
del t # 5
9. 小结
__new__()负责创建 ,__init__()负责初始化,前者返回非本类实例时后者不执行。__del__()只在引用计数归零、对象被回收时触发,del本身不保证调用它。__str__()面向人类,__repr__()面向机器;只写__repr__()时str()会退化使用它。__getattribute__()拦截所有 属性访问,内部务必用super().__getattribute__()避免递归;仅需兜底缺失属性时用__getattr__()。