Python自定义FileSet类更新后被重置为内置set类型问题
我自定义了一个继承自set的类,支持文件读写功能,其余行为与普通set完全一致,类实现代码如下:
class FileSet(set): def __init__(self, file): self.file = file self.elements = {line.strip() for line in open(self.file, "r")} super().__init__(self.elements) def load(self): self.elements = {line.strip() for line in open(self.file, "r")} super().__init__(self.elements) def save(self): with open(self.file, "w") as f: f.write("\n".join(list(self)))
问题现象
当与其他FileSet对象配合使用-=、|=操作时,对象始终保持FileSet类型;但执行更新操作后,对象会变回普通内置set类型,自定义方法无法正常调用。更新前执行print(type(obj))输出为<class '__main__.FileSet'>,更新后输出变为<class 'set'>。
排查结论
该问题仅在使用dict.keys()(类型为<class 'dict_keys'>)更新FileSet对象时触发,其余场景运行正常,临时解决方案是将传入的dict.keys()通过set(dict.keys())包裹转换为普通set后再做运算。
根本原因
Python内置set的原地运算符(|=、-=、&=、^=)有特殊逻辑:如果右操作数是set或set子类实例,会原地修改当前对象并返回自身;如果右操作数是其他可迭代类型(比如dict_keys、list等),会回退为普通的非原地运算,生成新的普通set对象返回,导致原变量被覆盖为内置set类型。
永久修复方案
重写所有用到的原地运算魔术方法,强制返回当前实例即可,不需要手动转换传入参数。同时原实现中冗余维护了self.elements属性,set本身已经存储所有元素,可以一并简化实现:
class FileSet(set): def __init__(self, file): self.file = file super().__init__(line.strip() for line in open(self.file, "r")) def load(self): self.clear() self.update(line.strip() for line in open(self.file, "r")) def save(self): with open(self.file, "w") as f: f.write("\n".join(self)) def __ior__(self, other): # 对应 |= 并集运算 super().__ior__(other) return self def __isub__(self, other): # 对应 -= 差集运算 super().__isub__(other) return self def __iand__(self, other): # 对应 &= 交集运算 super().__iand__(other) return self def __ixor__(self, other): # 对应 ^= 对称差集运算 super().__ixor__(other) return self
修复后,无论传入的是dict_keys、list还是其他可迭代对象,原地运算后都会保持FileSet类型,自定义的load、save方法可以正常调用。
感谢各位解答,特别感谢@chepner提供的帮助 :)
祝好
julius
内容的提问来源于stack exchange,提问作者ju1ius

