четверг, 26 июня 2014 г.

python copy/deepcopy

Всем привет!

Продолжаем ломать копья об питоновскую парадигму изменяемых/неизменяемых объектов.
Разберем поверхностное и глубокое копирование словаря.

>>>s = {"str": "1", "list": [1, 2], "dict": {"first": "test1"}}
>>>f = s
>>>f['second'] = '2'

>>>print s

Надеюсь понятно какой вывод будет в консоли. Если нет, то смотрим ссылки.
Для того чтобы не ссылаться на один объект, нужно скопировать словарь s.

Но метод copy обладает неприятной особенностью, он поверхностно копирует словарь.
Смотрим на пример (s из предыдущего примера).

>>>f = s.copy()
>>>f['second'] = '3'
>>>f['dict']['third'] = '3'
>>>print s

{'dict': {'third': '3', 'first': 'test1'}, 'list': [1, 2], 'str': '1'}

Проблема решается использованием модуля copy.

>>>import copy

>>>f = copy.deepcopy(s)
>>>f['second'] = '3'
>>>f['dict']['third'] = '3'
>>>print s

>>>print f

Как работает copy.deepcopy

Начнем с аргументов

deepcopy(x, memo=None, _nil=[])

x - копируемый объект. Копируемым объектом может быть любой стандартный тип данных, а также кастомных объектов.
memo - словарь, в котором будут сопоставлены id созданных объектов и их значения. По-умолчанию None(если не понятно почему, то смотрим в ссылки [3]). При желании можно реализовать собственный "копир", если есть потребность в специфичном поведении копирования.
_nil - вспомогательный атрибут, используемый во время копирования

Пара примеров для понимания работы deepcopy

>>>s = {"i": 1, "s": "2", "l": [1, 2, 3]}
>>>memo = {}
>>>d = copy.deepcopy(s, memo)

>>>print id(s)
44501728

>>>print id(d)
44502304

>>>print d
{'i': 1, 's': '2', 'l': [1, 2, 3]}

>>>print id(memo)
44502016

>>>print memo

{44501728: {'i': 1, 's': '2', 'l': [1, 2, 3]}, 31711104: '2', 31565540: 2, 44502016: [1, 'i', '2', 's', 2, 3, [1, 2, 3], 'l', {'i': 1, 's': '2', 'l': [1, 2, 3]}], 31266408: 'i', 31565552: 1, 31565528: 3, 31268304: 's', 31708488: 'l', 44497976: [1, 2, 3]}

Словарь memo хранит id всех копируемых объектов, их значения., а также свой id.

>>>dt = copy.deepcopy(s, memo)

>>>print id(dt)
44502304

print dt
{'i': 1, 's': '2', 'l': [1, 2, 3]}

Как видим, deepcopy возвращает уже копировавшийся объект d

>>>s1 = {"i": 1, "s": "2", "l": [1, 2, 3]}
>>>dth = copy.deepcopy(s1, memo)
>>>print id(s1)
44516240

>>>print(id(dth))
44517248

>>>print(dth)
{'i': 1, 's': '2', 'l': [1, 2, 3]}

>>>print(id(memo))
44502016

>>>print(memo)
{44501728: {'i': 1, s': '2', 'l': [1, 2, 3]}, 31711104: '2', 44516240: {'i': 1,  's': '2', 'l': [1, 2, 3]}, 31565540: 2, 44502016: [1, 'i', '2', 's', 2, 3, [1, 2, 3], 'l', {'i': 1, 's': '2', 'l': [1, 2, 3]}, [1, 2, 3], {'i': 1, 's': '2', 'l': [1, 2, 3]}], 31266408: 'i', 31565552: 1, 31565528: 3, 44498856: [1, 2, 3], 31268304: 's', 31708488: 'l', 44497976: [1, 2, 3]}

Реализация deepcopy

Сначала происходит инициализация memo, если словарь не задан. Если memo задан тогда проверяется наличие id копируемого объекта, если такой объект уже есть в memo, то происходит его возврат.

d = id(x)
y = memo.get(d, _nil)
if y is not _nil:
    return y

Т.е. если после копирования нужно все время получать новый объект, то тогда поле memo нужно оставлять по-умолчанию (это мы видели на примере выше).

Далее определяется тип копируемого объекта и если он есть в описании стандартных типов, то вызывается функция копирования

copier = _deepcopy_dispatch.get(cls)
if copier:
    y = copier(x, memo)

Например, для словаря вызывается _deepcopy_dict

def _deepcopy_dict(x, memo):
    y = {}
    memo[id(x)] = y
    for key, value in x.iteritems():
        y[deepcopy(key, memo)] = deepcopy(value, memo)
    return y

Код довольно простой, создается новый словарь, в цикле копируются значения. Обратите внимание копирование идет - рекурсивным вызовом deepcopy, сделано это на случай того, если словарь составной и содержит вложенные объекты.
После успешного копирования обновляется словарь memo и вызывается функция _keep_alive, которая записывает в memo, сам копируемый объект, т.е. если вызвать memo[id(memo)], то получим список всех объектов, которые копировались.

Ссылки
[1]https://docs.python.org/2/library/stdtypes.html#dict.copy
[2]https://docs.python.org/2/library/copy.html
[3]http://evgenqa.blogspot.ru/2014/05/blog-post_23.html


Комментариев нет:

Отправить комментарий