__init__ 是初始化方法而非构造函数,应避免耗时操作;__str__ 面向用户,__repr__ 面向开发者且需可执行;重写 __eq__ 必须同步处理 __hash__;__getattr__ 是属性缺失兜底,__getattribute__ 全拦截需谨慎。

__init__ 不是构造函数,别在里头做耗时操作
Python 中 __init__ 真正的作用是初始化已创建的对象,不是“构造”它——对象其实在调用 __new__ 之后就已经存在了。这意味着如果在 __init__ 里做文件读取、网络请求、数据库连接这类事,会拖慢所有实例化过程,而且一旦出错,对象其实已经半成品地留在内存里了。
- 常见错误现象:
__init__报错后发现isinstance(obj, MyClass)居然为True,但对象状态不一致 - 推荐做法:只做轻量赋值,比如
self.name = name;把重活交给显式方法(如load_config())或延迟加载属性 - 参数差异:
__init__第一个参数固定是self,但你可以加任意位置/关键字参数,不过要和__new__的签名对得上,否则继承时容易出TypeError: object.__new__() takes exactly one argument
__str__ 和 __repr__ 别混用,print() 和调试器看的不是同一个东西
__str__ 是给人看的,__repr__ 是给开发者看的。很多同学只实现 __str__,结果在 REPL 或日志里打出来全是 <MyClass object at 0x...>,根本看不出内容。
- 使用场景:
print(obj)、str(obj)走__str__;repr(obj)、交互式环境直接敲变量名、logging.debug()默认走__repr__ - 实操建议:
__repr__应该尽量返回可执行的 Python 表达式(比如Point(x=1, y=2)),至少包含类名和关键字段;__str__可以简洁友好(比如"Point at (1, 2)") - 容易踩的坑:在
__repr__里调用str()或格式化大量数据,可能引发递归(比如__str__又去调__repr__)
__eq__ 必须配对实现 __hash__,不然对象进不了 set 或当 dict 键
只要重写了 __eq__,Python 就自动把 __hash__ 设为 None,哪怕你没动它。这时候如果尝试把实例放进 set 或作为 dict 的键,会报 TypeError: unhashable type。
- 为什么这样做:Python 要求相等的对象必须有相同哈希值,而默认
__hash__基于 id,和自定义__eq__冲突 - 实操建议:如果类逻辑上可哈希(比如所有用于比较的字段都不可变),就手动实现
__hash__,比如return hash((self.a, self.b));如果字段可变,显式设__hash__ = None,避免误用 - 性能影响:
__hash__返回值最好缓存(尤其字段多或计算重),否则每次查dict都重新算
别为了“魔法”而写魔法方法,__getattr__ 和 __getattribute__ 差得远
__getattr__ 是兜底的,只有属性真不存在时才触发;__getattribute__ 是全拦截的,每次访问任何属性(包括 __dict__、方法)都会过它——写错一句就可能死循环。
立即学习“Python免费学习笔记(深入)”;
- 常见错误现象:在
__getattribute__里直接访问self.xxx,触发无限递归;或者忘了调用super().__getattribute__(name),导致连方法都找不到了 - 使用场景:
__getattr__适合做动态代理、API 封装(比如obj.user_id自动转成api.get("user", "id"));__getattribute__极少需要,一般只用于监控、审计等底层框架 - 安全做法:优先用
__getattr__;非要用__getattribute__,一律通过object.__getattribute__(self, name)获取原值,绝不走self.xxx
== 触发 __eq__,in 触发 __contains__,甚至 len() 都依赖 __len__。写之前先想清楚,这个行为是不是真的该由类型自己定义。










