Взаимосвязь между рассолом и глубиной

В чем именно заключается связь между 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 для выполнения своей задачи.