如何强制Pandas不对用作索引的对象进行缓存?
问题:Pandas MultiIndex 缓存导致自定义对象实例被复用
自定义实现了__hash__和__eq__方法的DemoObj类后,通过pd.MultiIndex.from_tuples创建多层索引并设置为DataFrame索引时,索引中对象的唯一id数量为4,但预期应为8。经测试确认这是Pandas MultiIndex的缓存逻辑导致,现需解决如何禁用该缓存的问题。
原始测试代码
import pandas as pd class DemoObj: def __init__(self, x): self.x = x def __hash__(self): return hash(self.x) def __eq__(self, other): return self.x == other.x df = pd.DataFrame({'a': [1, 2, 3, 4]}) df.index = pd.MultiIndex.from_tuples(((DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1')), (DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1')))) print(len({id(e) for i in df.index for e in i})) # 输出4,预期8
补充验证代码及输出
indexes = ((DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1')), (DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1'))) df.index = pd.MultiIndex.from_tuples(indexes) print(len({id(e) for i in indexes for e in i})) # 输出8 print(len({id(e) for i in df.index for e in i})) # 输出4
原因分析
Pandas的MultiIndex在创建过程中,会对通过__eq__判断为相等的对象进行缓存复用。由于DemoObj根据x的值判断相等且哈希值相同,相同x的不同实例会被替换成同一个,最终导致唯一id数量减少。
解决方案
目前Pandas没有直接暴露禁用该缓存的参数,可通过以下两种方式绕过:
1. 修改自定义对象的哈希逻辑,避免实例被视为相等
修改__hash__方法,将实例自身的id()加入哈希计算,让相同x的不同实例拥有不同哈希值,从而阻止Pandas复用:
class DemoObj: def __init__(self, x): self.x = x def __hash__(self): # 加入实例id,确保每个实例哈希唯一 return hash((self.x, id(self))) def __eq__(self, other): # 可保留原有的x相等逻辑,也可改为严格实例相等(加上id判断) return self.x == other.x
注意:此方法会让x相同的实例被视为不同的索引项,若需保留"x相同即索引项相等"的逻辑,不建议使用。
2. 手动构建MultiIndex的levels和codes,跳过缓存逻辑
直接通过pd.MultiIndex(levels=[...], codes=[...])的方式构建索引,手动指定每个层级的所有实例和对应位置代码,避免Pandas自动去重缓存:
import pandas as pd class DemoObj: def __init__(self, x): self.x = x def __hash__(self): return hash(self.x) def __eq__(self, other): return self.x == other.x indexes = ((DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1')), (DemoObj('a0'), DemoObj('b0')), (DemoObj('a1'), DemoObj('b1'))) # 拆分每个层级的原始实例 level0 = [t[0] for t in indexes] level1 = [t[1] for t in indexes] # 手动构建MultiIndex,保留所有原始实例 df = pd.DataFrame( {'a': [1,2,3,4]}, index=pd.MultiIndex( levels=[level0, level1], codes=[[0,1,2,3], [0,1,2,3]] ) ) print(len({id(e) for i in df.index for e in i})) # 输出8
此方法保留了DemoObj原有的相等和哈希逻辑,同时让所有原始实例都保留在索引中。
内容的提问来源于stack exchange,提问作者DeepSpace
相关产品推荐
相关产品推荐

