Взаимосвязь между рассолом и глубиной
В чем именно заключается связь между pickle
и copy.deepcopy
? Какие механизмы они разделяют, и как?
Понятно, что эти два являются тесно связанными операциями и совместно используют некоторые механизмы/протоколы, но я не могу обернуть голову вокруг деталей.
Некоторые (запутанные) вещи, которые я узнал:
- Если класс определяет
__[gs]etstate__
, они вызываются на deepcopy
своих экземпляров. Сначала это меня удивило, потому что я думал, что они специфичны для pickle
, но потом я обнаружил, что Классы могут использовать одни и те же интерфейсы для управления копированием, которые они используют для управления травлением. Однако нет документации о том, как __[gs]etstate__
используется при глубокой копировании (как используется значение, возвращаемое из __getstate__
, то, что передается на __setstate__
?)
- Наивная альтернативная реализация
deepcopy
будет pickle.loads(pickle.dumps(obj))
. Тем не менее, это не может быть эквивалентно deepcopy'ing, потому что если класс определяет операцию __deepcopy__
, он не будет вызван с использованием этой глубокой копии, основанной на pickle. (Я также наткнулся на утверждение, что глубокая копия является более общей, чем рассол, и существует много типов, которые являются глубококопируемыми, но не разборчивыми.)
(1) указывает общность, а (2) указывает разницу между pickle
и deepcopy
.
Кроме того, я нашел эти два противоречивых утверждения:
copy_reg: модули рассола, cPickle и copy используют эти функции при травлении/копировании этих объектов
и
Модуль copy не использует модуль регистрации copy_reg
Это, с одной стороны, является еще одним признаком отношения/общности между pickle
и deepcopy
, а с другой стороны, способствует моей путанице...
[Мой опыт работы с python2.7, но я также ценю любые указатели на различия в рассоле/глубине между python2 и python3]
Ответы
Ответ 1
Вас не следует путать с (1) и (2). В общем, Python пытается включить разумные спады для недостающих методов. (Например, достаточно определить __getitem__
, чтобы иметь итерируемый класс, но может быть более эффективным также реализовать __iter__
. Аналогично для операций, таких как __add__
, с дополнительным __iadd__
и т.д.)
__deepcopy__
- это самый специализированный метод, deepcopy()
будет искать метод deepcopy()
, но если он не существует, возвращение к протоколу рассола - разумная вещь. Он действительно не вызывает dumps()
/loads()
, потому что он не полагается на промежуточное представление как строку, но он будет косвенно использовать __getstate__
и __setstate__
(через __reduce__
), как вы заметили.
В настоящее время документация по- прежнему заявляет
... Модуль копирования не использует модуль регистрации copy_reg.
но это, кажется, ошибка, которая была исправлена тем временем (возможно, ветвь 2.7 не получила достаточного внимания здесь).
Также обратите внимание, что это довольно глубоко интегрировано в Python (по крайней мере в наши дни); сам класс object
реализует __reduce__
(и его вариант с версией _ex), который ссылается на copy_reg.__newobj__
для создания свежих экземпляров данного объектно-производного класса.
Ответ 2
Хорошо, мне нужно было прочитать исходный код для этого, но похоже, что это довольно простой ответ.
http://svn.python.org/projects/python/trunk/Lib/copy.py
copy
просматривает некоторые встроенные типы, в которых он знает, для чего похожи конструкторы (зарегистрированные в словаре _copy_dispatch
, и когда он не знает, как скопировать основной тип, он импортирует copy_reg.dispatch_table
... который является тем местом, где pickle
регистрирует методы, которые он знает для создания новых копий объектов. По сути, это словарь типа объекта и "функция для создания нового объекта" - эта "функция для создания новый объект" - это в значительной степени то, что вы пишете, когда вы пишете метод __reduce__
или __reduce_ex__
для объекта (и если один из них отсутствует или нуждается в помощи, он отсылает __setstate__
, __getstate__
и т.д. методов.
Итак, copy
. В основном... (с некоторыми дополнительными предложениями...)
def copy(x):
"""Shallow copy operation on arbitrary Python objects.
See the module __doc__ string for more info.
"""
cls = type(x)
copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
copier = getattr(cls, "__copy__", None)
if copier:
return copier(x)
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
else:
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(2)
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
else:
raise Error("un(shallow)copyable object of type %s" % cls)
deepcopy
делает то же самое, что и выше, но дополнительно проверяет каждый объект и обеспечивает наличие копии для каждого нового объекта, а не ссылки на указатель. deepcopy
создает собственную таблицу _deepcopy_dispatch
(a dict), где он регистрирует функции, гарантирующие, что созданные новые объекты не имеют ссылок на оригиналы (возможно, с помощью функций __reduce__
, зарегистрированных в copy_reg.dispatch_table
)
Следовательно, для написания метода __reduce__
(или аналогичного) и регистрации его с помощью copy_reg
следует включить copy
и deepcopy
для выполнения своей задачи.