1. 从 __init__ 开始的构造与调用顺序
在 Python 的对象生命周期中,__init__ 和 __new__ 构成了对象创建与初始化的核心流程。__new__ 负责分配并返回新对象,而 __init__ 负责对该对象进行属性初始化。这两者的调用顺序决定了实例最终的状态,因此理解它们的关系对自定义类行为至关重要。
通常情况下,你只需要在类中实现 __init__,Python 会在实例化时自动顺序调用 __new__(从父类继承的实现)和 __init__。如果你需要改变对象创建时的行为,例如实现单例模式或返回自定义子类,才需要重写 __new__。
__new__ 的工作原理与使用场景
__new__ 是一个静态方法,负责创建并返回对象实例,通常接收与 __init__ 相同的参数。只有在你确实需要控制对象创建逻辑时才会重写它。常见场景包括实现单例、元类、以及创建不可变对象时的自定义创建过程。
class MySingleton:_instance = Nonedef __new__(cls, *args, **kwargs):if cls._instance is None:cls._instance = super().__new__(cls)return cls._instancedef __init__(self, value):self.value = valuea = MySingleton(10)
b = MySingleton(20)
print(a is b) # True,只有一个实例
print(a.value) # 20,__init__ 仍然会执行,但实例相同
在 __init__ 中进行初始化的最佳实践
__init__ 的职责是初始化对象的状态,而不是重新创建对象。为避免重复初始化,可以在实例中设置一个初始化标志位,确保多次创建同一对象时不会重复执行耗时操作。
class Counter:def __init__(self, start=0):if not hasattr(self, "_initialized"):self.value = startself._initialized = Truec1 = Counter(5)
c2 = Counter(100)
print(c1.value) # 5,且不会被第二次赋值覆盖
print(c2.value) # 100
2. __str__ 与 __repr__:对象文本表示的艺术
文本表示是与外部交互的重要入口。__repr__ 应提供一个明确且可调试的表示,通常用于开发与调试;__str__ 则偏向于对用户的友好显示,常用于日志或打印对象时的输出。
好的实现会将两者区分开来,避免混淆。通过 实现 __format__,还可以为对象提供灵活的格式化输出,以适应不同场景的展示需求。
__repr__ 与调试输出
__repr__ 的目标是“有意义且可重建”的文本表示。若对象的 __repr__ 能提供足够的信息,开发者在调试时就可以快速定位状态。
class Point:def __init__(self, x, y):self.x = xself.y = ydef __repr__(self):return f"Point(x={self.x!r}, y={self.y!r})"p = Point(3, 4)
print(repr(p)) # Point(x=3, y=4)
print(p) # 会调用 __str__,若未实现则回退到 __repr__
__str__ 的用户友好输出
__str__ 的输出应简洁易读,方便在终端或日志中查看对象的核心信息。若同时实现 __repr__,应确保两者的风格互补而非重复。
class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return f"点({self.x}, {self.y})"p = Point(7, 8)
print(str(p)) # 点(7, 8)
__format__:自定义格式化输出
通过实现 __format__,可以根据 format_spec 指定的格式对对象进行自定义格式化,提升数据展示的灵活性。
class Point:def __init__(self, x, y):self.x = xself.y = ydef __format__(self, spec):if spec == 'p':return f"Point({self.x}, {self.y})"return f"({self.x}, {self.y})"p = Point(2, 5)
print(f"{p:p}") # Point(2, 5)
print(f"{p}") # (2, 5)
3. 描述符与属性访问:__getattr__、__getattribute__、__setattr__、__delattr__ 及描述符协议
属性访问的拦截能力使得对象的行为更加灵活。__getattribute__ 会拦截所有属性访问,而 __getattr__ 只在普通访问找不到时触发,二者搭配能够实现强大的延迟加载与代理模式。同时,描述符协议(__get__, __set__, __delete__)提供了更通用的属性管理方式。
下面的示例展示了常见的组合:如何区分两种拦截,以及如何通过描述符实现共享属性的访问控制。
__getattribute__ 与 __getattr__ 的区别
__getattribute__ 是在每次属性访问时调用的底层钩子,因此需要谨慎实现,以避免造成无限递归。相对地,__getattr__ 仅在寻址失败时触发,适合实现“默认值”或“惰性加载”的逻辑。
class AccessorDemo:def __init__(self, value):self.value = valuedef __getattribute__(self, name):if name == "value":print("Accessing value directly")return object.__getattribute__(self, name)def __getattr__(self, name):# 当属性不存在时返回一个默认值if name == "missing":return 42raise AttributeError(name)d = AccessorDemo(10)
print(d.value) # 直接访问,会打印提示
print(d.missing) # 使用 __getattr__ 提供默认值 42
描述符协议与属性控制
描述符是实现自定义属性访问语义的底层工具,通过实现 __get__、__set__、__delete__,你可以把属性的存取逻辑从对象中解耦到描述符类中。

class Descriptor:def __init__(self, name):self.name = namedef __get__(self, instance, owner):if instance is None:return selfreturn instance.__dict__.get(self.name, None)def __set__(self, instance, value):instance.__dict__[self.name] = valueclass MyClass:value = Descriptor("value")obj = MyClass()
obj.value = 123
print(obj.value) # 123
4. 容器协议与迭代:__len__、__iter__、__contains__、__getitem__
容器与序列的核心在于是否支持迭代、长度查询及成员测试。实现 __iter__ 或实现 __getitem___ 即可让你的自定义对象具备迭代能力,结合 __len__ 与 __contains__,还能实现丰富的集合行为。
通过这些方法,你的类就可以和 Python 的内置函数如 len、in、for 循环等自然配合,提升可用性与可维护性。
实现可迭代对象:__iter__ 与迭代器
把序列的迭代逻辑放在 __iter__ 中,返回一个迭代器对象;迭代器则实现 __next__,逐步产出元素。
class SimpleList:def __init__(self, items):self._items = list(items)def __iter__(self):return iter(self._items)sl = SimpleList([1, 2, 3])
for x in sl:print(x)
容器协议的组合:__len__、__getitem__、__contains__
如果没有实现 __iter__,你也可以通过实现 __getitem__ 来让对象成为一个序列;再结合 __len__,你就能与切片、索引操作等自然协作。
class MySequence:def __init__(self, items):self._items = list(items)def __len__(self):return len(self._items)def __getitem__(self, index):return self._items[index]def __contains__(self, item):return item in self._itemsseq = MySequence([10, 20, 30])
print(len(seq)) # 3
print(seq[1]) # 20
print(20 in seq) # True
5. 上下文管理器与可调用对象:__enter__/__exit__、__call__
上下文管理器让资源的获取与释放变得清晰可控,常用于文件、锁、事务等场景;而实现 __call__ 则让对象具备“可调用对象”的特性,可以像函数一样被直接调用,从而实现更自然的 API 设计。
下面的示例展示了常见的组合用法,帮助你用最少的代码实现强大行为。
上下文管理器:__enter__ 与 __exit__
通过实现这两个方法,你可以在 with 语句块中确保资源的正确释放,__exit__ 可以选择性处理异常,也支持返回 False 以传播异常。
class SimpleContext:def __enter__(self):print("进入上下文")return selfdef __exit__(self, exc_type, exc, tb):print("退出上下文")return False # 不拦截异常with SimpleContext() as ctx:print("在上下文中执行")
可调用对象:__call__ 的实用用法
实现 __call__ 可以让对象具备函数式特性,参数化对象行为、构建函数族或策略对象,从而实现更灵活的 API。
class Multiplier:def __init__(self, factor):self.factor = factordef __call__(self, value):return value * self.factortimes3 = Multiplier(3)
print(times3(10)) # 30


