Почему атрибуты теряются после копирования Pandas DataFrame
Почему невозможно передать атрибуты экземпляра через копию? Я хочу передать атрибут name
другому фрейму.
import copy
df = pd.DataFrame([1,2,3])
df.name = 'sheet1'
df2 = copy.deepcopy(df)
print(f'df.name: {df.name}')
>> df.name: sheet1
print(f'df2.name: {df2.name}')
>> AttributeError
...
'DataFrame' object has no attribute 'name'
Точно так же почему это также не работает, когда создается класс и наследуется от него?
class DfWithName(pd.DataFrame):
def __init__(self, *args, **kwargs):
self.__init__ = super().__init__(*args, **kwargs)
print('lol')
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
и используя тот же код:
import copy
df = DfWithName([1,2,3])
df.name = 'sheet1'
df2 = copy.deepcopy(df)
print(f'df.name: {df2.name}')
>> AttributeError
...
'DataFrame' object has no attribute 'name'
Ответы
Ответ 1
Как отмечено в другом месте, класс DataFrame
имеет собственный метод __deepcopy__
который не обязательно копирует произвольные атрибуты, назначенные экземпляру, как и для обычного объекта.
Интересно, что существует внутренний атрибут _metadata
который, как представляется, предназначен для отображения дополнительных атрибутов NDFrame
которые должны храниться при копировании/сериализации. Это обсуждается здесь: https://github.com/pandas-dev/pandas/issues/9317
К сожалению, это до сих пор считается недокументированной внутренней деталью, поэтому его, вероятно, не следует использовать. От взгляда на код вы можете в принципе сделать:
mydf = pd.DataFrame(...)
mydf.name = 'foo'
mydf._metadata += ['name']
и когда вы его копируете, он должен взять имя вместе с ним.
Вы можете подклассифицировать DataFrame
чтобы сделать это по умолчанию:
import functools
class NamedDataFrame(pd.DataFrame):
_metadata = pd.DataFrame._metadata + ['name']
def __init__(self, name, *args, **kwargs):
self.name = name
super().__init__(*args, **kwargs)
@property
def _constructor(self):
return functools.partial(self.__class__, self.name)
Вы также можете сделать это, не полагаясь на этот внутренний атрибут _metadata
если вы предоставите свою собственную оболочку существующему методу copy
и, возможно, также __getstate__
и __setstate__
.
Обновление: Кажется, на самом деле использовать в _metadata
атрибута для расширения классов панды теперь документировано. Таким образом, приведенный выше пример должен работать более или менее. Эти документы больше подходят для разработки самого Pandas, поэтому он может быть немного изменчивым. Но именно так NDFrame
сама расширяет подклассы NDFrame
.
Ответ 2
copy.deepcopy
будет использовать собственный метод __deepcopy__
если он найден в MRO, который может вернуть все, что ему нравится (включая полностью фиктивные результаты). Действительно, __deepcopy__
метод __deepcopy__
:
def __deepcopy__(self, memo=None):
if memo is None:
memo = {}
return self.copy(deep=True)
Он делегирует self.copy
, где вы найдете эту заметку в docstring:
Notes
-----
When ''deep=True'', data is copied but actual Python objects
will not be copied recursively, only the reference to the object.
This is in contrast to 'copy.deepcopy' in the Standard Library,
which recursively copies object data (see examples below).
И вы найдете в примечаниях к выпуску v0.13 (объединенных в PR 4039):
__deepcopy__
теперь возвращает неглубокую копию (в настоящее время: представление) данных, что позволяет изменять метаданные.
Связанная тема: 17406.
Ответ 3
Прикрепление пользовательских метаданных к DataFrames кажется неподдерживаемым для панд. См. Этот ответ (возможно дублировать?) И этот вопрос github.
Ответ 4
Этот код работает:
>>> class test():
... @property
... def name(self):
... return self._name
... @name.setter
... def name(self, value):
... self._name = value
...
>>>
>>> a = test()
>>> a.name = 'Test123'
>>> import copy
>>> a2 = copy.deepcopy(a)
>>> print(a2.name)
Test123
поэтому я думаю, что поведение определяется pd.DataFrame
Я обнаружил, что панды определяют функцию __deepcopy__
, но я не могу полностью понять причину.
панды/ядро/индексы/base.py # l960