Подтвердить что ты не робот

Что такое быстрый питонический способ глубокого копирования только данных из файла или списка python?

Когда нам нужно скопировать полные данные из словаря, содержащего примитивные типы данных (для простоты, игнорировать наличие типов данных, таких как datetime и т.д.), самый очевидный выбор, который у нас есть, заключается в использовании deepcopy, но глубокая копия медленнее, чем некоторые другие хакерские методы достижения того же, т.е. использование сериализации-несериализации, например json-dump-json-load или msgpack-pack-msgpack-unpack. Разницу в эффективности можно увидеть здесь:

>>> import timeit
>>> setup = '''
... import msgpack
... import json
... from copy import deepcopy
... data = {'name':'John Doe','ranks':{'sports':13,'edu':34,'arts':45},'grade':5}
... '''
>>> print(timeit.timeit('deepcopy(data)', setup=setup))
12.0860249996
>>> print(timeit.timeit('json.loads(json.dumps(data))', setup=setup))
9.07182312012
>>> print(timeit.timeit('msgpack.unpackb(msgpack.packb(data))', setup=setup))
1.42743492126

Методы json и msgpack (или cPickle) быстрее, чем нормальная глубокая копия, что очевидно, поскольку глубокая копия будет делать гораздо больше при копировании всех атрибутов объекта.

Вопрос: Существует ли еще более сложный способ для создания или преобразования данных в словарь или список, не имея всех служебных данных, которые имеют deepcopy?

4b9b3361

Ответ 1

Это действительно зависит от ваших потребностей. deepcopy был построен с намерением сделать (наиболее) правильную вещь. Он хранит общие ссылки, не рекурсирует в бесконечные рекурсивные структуры и т.д. Он может это сделать, сохранив словарь memo, в который все встреченные "вещи" вставлены по ссылке. Это делает его довольно медленным для чистых копий данных. Однако я почти всегда говорил, что deepcopy самый пифонический способ копирования данных, даже если другие подходы могут быть быстрее.

Если в нем есть чистые данные и ограниченное количество типов, вы можете создать свой собственный deepcopy (строить примерно после реализации deepcopy в CPython):

_dispatcher = {}

def _copy_list(l, dispatch):
    ret = l.copy()
    for idx, item in enumerate(ret):
        cp = dispatch.get(type(item))
        if cp is not None:
            ret[idx] = cp(item, dispatch)
    return ret

def _copy_dict(d, dispatch):
    ret = d.copy()
    for key, value in ret.items():
        cp = dispatch.get(type(value))
        if cp is not None:
            ret[key] = cp(value, dispatch)

    return ret

_dispatcher[list] = _copy_list
_dispatcher[dict] = _copy_dict

def deepcopy(sth):
    cp = _dispatcher.get(type(sth))
    if cp is None:
        return sth
    else:
        return cp(sth, _dispatcher)

Это работает только для всех неизменяемых неконтейнерных типов и экземпляров list и dict. Вы можете добавить больше диспетчеров, если они вам понадобятся.

# Timings done on Python 3.5.3 - Windows - on a really slow laptop :-/

import copy
import msgpack
import json

import string

data = {'name':'John Doe','ranks':{'sports':13,'edu':34,'arts':45},'grade':5}

%timeit deepcopy(data)
# 11.9 µs ± 280 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit copy.deepcopy(data)
# 64.3 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit json.loads(json.dumps(data))
# 65.9 µs ± 2.53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit msgpack.unpackb(msgpack.packb(data))
# 56.5 µs ± 2.53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Давайте также посмотрим, как он выполняется при копировании большого словаря, содержащего строки и целые числа:

data = {''.join([a,b,c]): 1 for a in string.ascii_letters for b in string.ascii_letters for c in string.ascii_letters}

%timeit deepcopy(data)
# 194 ms ± 5.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit copy.deepcopy(data)
# 1.02 s ± 46.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit json.loads(json.dumps(data))
# 398 ms ± 20.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit msgpack.unpackb(msgpack.packb(data))
# 238 ms ± 8.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Ответ 2

Я думаю, вы можете вручную реализовать то, что вам нужно, переопределить object.__deepcopy__.

Питонический способ сделать это, я думаю, использует ваш пользовательский dict, расширенный из встроенного dict и реализующий ваш __deepcopy__.