Pandas DataFrame复制后属性丢失及子类复制失效问题咨询
我来帮你拆解这两个场景下属性丢失的核心原因,再给你对应的解决办法:
1. 直接给DataFrame实例加自定义属性的问题
当你手动给pd.DataFrame实例贴个name这种自定义属性时,用copy.deepcopy()复制后属性消失,本质是因为Pandas的DataFrame有自己一套专属的序列化/反序列化逻辑——也就是Python深拷贝依赖的__reduce__方法。
Pandas在处理深拷贝时,只会序列化它本身预设的核心数据:比如表格里的数值、索引、列名、官方定义的元数据这些,完全不会管你后来手动添加上去的额外属性。深拷贝的过程是通过Pandas的逻辑重新生成一个全新的DataFrame实例,而不是把原对象的所有属性一股脑复制过去,所以你加的name自然就没了。
就像你写的这段代码:
import copy import pandas as pd df = pd.DataFrame([1,2,3]) df.name = 'sheet1' df2 = copy.deepcopy(df) # 这里访问df2.name会抛出AttributeError
deepcopy全程走的是Pandas自己的复制流程,根本没考虑你手动加的属性。
2. 继承DataFrame的自定义类属性丢失的问题
你的DfWithName类有两个致命问题,导致深拷贝后属性丢失:
问题一:__init__方法写崩了
你在__init__里写了self.__init__ = super().__init__(*args, **kwargs),这完全是错误的操作——你把实例的__init__方法直接覆盖成了父类__init__的返回值(而pd.DataFrame.__init__返回的是None),这会导致你的类实例初始化时就出问题,连_name属性的存储都不正常。正确的写法应该是直接调用父类的初始化方法:
class DfWithName(pd.DataFrame): def __init__(self, *args, **kwargs): # 直接调用父类初始化,别乱赋值给self.__init__ super().__init__(*args, **kwargs)
问题二:Pandas复制逻辑不认子类属性
就算你修正了__init__,直接用copy.deepcopy还是会丢属性,因为Pandas的__reduce__逻辑在复制时,默认会返回父类pd.DataFrame的构造方法,而不是你的子类DfWithName。这意味着深拷贝出来的对象根本不是DfWithName实例,而是个普通的pd.DataFrame,自然就没有name属性了。
对应的解决办法
场景一:只是偶尔给DataFrame加属性
如果你不想写子类,只是临时给DataFrame加个自定义属性,最简单的办法就是深拷贝后手动把属性复制过去:
df2 = copy.deepcopy(df) df2.name = df.name
或者用Pandas 1.2版本之后支持的attrs属性——这是官方推荐的存储自定义元数据的方式,会被Pandas的复制逻辑自动保留:
df = pd.DataFrame([1,2,3]) df.attrs['name'] = 'sheet1' df2 = copy.deepcopy(df) print(df2.attrs['name']) # 会输出'sheet1'
场景二:需要长期用带自定义属性的DataFrame子类
修正__init__,同时重写Pandas的_constructor方法(这是Pandas官方推荐的子类化姿势),再通过__reduce__和__setstate__确保深拷贝时保留自定义属性:
import copy import pandas as pd class DfWithName(pd.DataFrame): def __init__(self, *args, name=None, **kwargs): super().__init__(*args, **kwargs) self._name = name @property def name(self): return self._name @name.setter def name(self, value): self._name = value # 重写_constructor,让Pandas复制时返回当前子类的实例 @property def _constructor(self): return DfWithName # 重写__reduce__,把自定义属性加入序列化状态 def __reduce__(self): parent_state = super().__reduce__() # 把自定义的_name加到状态末尾 new_state = parent_state[2] + (self._name,) return (parent_state[0], parent_state[1], new_state) # 重写__setstate__,从状态中恢复自定义属性 def __setstate__(self, state): # 取出最后一位的_name,剩下的传给父类处理 self._name = state[-1] super().__setstate__(state[:-1]) # 测试一下 df = DfWithName([1,2,3], name='sheet1') df2 = copy.deepcopy(df) print(df2.name) # 输出'sheet1',完美保留属性
内容的提问来源于stack exchange,提问作者A H

