为何len(obj)不经过__getattribute__,如何覆盖该行为?
首先得搞明白为什么len(obj)会绕过__getattribute__:这是CPython解释器为了性能做的针对性优化。像len()、dir()这种高频使用的内置函数,根本不会走实例的__getattribute__属性查找流程,而是直接从对象所属类的底层结构(比如类型对象的方法槽位)里获取对应的特殊方法(比如__len__)。毕竟这些是Python的基础操作,跳过属性查找能大幅提升执行效率。
看你给出的例子,obj.__len__()是明明白白从实例上获取__len__属性,所以必然会触发__getattribute__;但len(obj)是调用内置函数,解释器会直接去类的方法表中找__len__,完全绕开了实例层面的__getattribute__拦截。
那有没有办法让len(obj)强制走__getattribute__("__len__")呢?在标准CPython里,没有官方原生的方式能直接修改这个内置逻辑——因为这是硬编码在解释器核心里的。不过我们可以用点小技巧,让len(obj)间接触发__getattribute__。
可行方案:在__len__里手动触发__getattribute__
我们可以把类的__len__做成一个“中转方法”,让它主动调用__getattribute__来获取真正的长度计算逻辑。这样当len(obj)调用__len__时,就会顺带着触发__getattribute__。
修改你的示例代码如下:
class Custom: def __getattribute__(self, item): print(f"Accessing: {item}") # 这里可以根据需求动态返回__len__的实现 if item == "__len__": def dynamic_len(): return 50 return dynamic_len return super().__getattribute__(item) def __len__(self): # 主动通过__getattribute__获取长度计算逻辑 return self.__getattribute__("__len__")() obj = Custom() print(obj.__len__()) # 正常触发__getattribute__ print(len(obj)) # 现在也会触发__getattribute__
运行输出:
Accessing: __len__ 50 Accessing: __len__ 50
这个思路的核心是把len(obj)的调用链路,通过类的__len__中转,引到__getattribute__上。如果要对其他内置函数(比如dir()、repr())做同样的处理,只需要给对应的特殊方法(__dir__、__repr__)也加上类似的中转逻辑就行。
注意事项
这种方式只是模拟了你想要的效果,并没有真正改变内置函数的底层行为。解释器还是会直接查找类的特殊方法,只是我们在这个方法里手动加入了__getattribute__的调用。
另外,别想着用元类、描述器去拦截类层面的方法查找——内置函数的逻辑是直接和类型对象的底层槽位交互,完全绕开了常规的属性查找流程,这些手段根本碰不到它。
备注:内容来源于stack exchange,提问作者Ziv Sion

