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

Сохранение словарей в файл (numpy и Python 2/3)

Я хочу использовать иерархическое хранилище ключей в Python, которое в основном сводится к хранению словарей в файлах. Под этим я подразумеваю любой тип словарной структуры, который может содержать другие словари, массивы numpy, сериализуемые объекты Python и т.д. Не только это, я хочу, чтобы он хранил массивы numpy, оптимизированные по пространству, и хорошо играл между Python 2 и 3.

Ниже приведены методы, которые я знаю. Мой вопрос в том, что отсутствует в этом списке, и есть ли альтернатива, которая уклоняется от всех моих разрывов?

  • Модуль Python pickle (разблокировка транзакций: многократно увеличивает размер массивов numpy)
  • Numpy save/savez/load (разблокировка сделки: Несовместимый формат через Python 2/3)
  • Замена PyTables для numpy.savez (разблокирование транзакций: обрабатывает только массивы numpy)
  • Использование PyTables вручную (разблокирование транзакций: я хочу это для постоянного изменения кода исследования, поэтому очень удобно иметь возможность выгружать словари в файлы, вызывая одну функцию)

Замена PTTables numpy.savez является многообещающей, поскольку мне нравится идея использования hdf5 и она эффективно сжимает массивы numpy, что является большим плюсом. Однако он не требует какой-либо структуры словаря.

В последнее время то, что я делал, это использовать что-то похожее на замену PyTables, но улучшая его, чтобы хранить записи любого типа. Это действительно работает очень хорошо, но я нахожу, что храню примитивные типы данных в CA-образцах длины-1, что немного неудобно (и неоднозначно для массивов с длиной-1), хотя я установил chunksize в 1, поэтому он не занимать столько места.

Есть ли что-то подобное уже там?

Спасибо!

4b9b3361

Ответ 1

Спросив об этом два года назад, я начал кодировать мою собственную замену pickle/ np.save на основе HDF5. С тех пор он созрел в стабильном пакете, поэтому я подумал, что, наконец, отвечу и соглашусь с моим собственным вопросом, потому что именно по дизайну именно то, что я искал:

Ответ 2

Недавно я столкнулся с аналогичной проблемой, для которой я написал несколько функций для сохранения содержимого dicts в группу в файле PyTables и загрузки их обратно в dicts.

Они рекурсивно обрабатывают вложенные словарные и групповые структуры и обрабатывают объекты с типами, которые изначально не поддерживаются PyTables, путем травления их и хранения их в виде массивов строк. Это не идеально, но, по крайней мере, такие вещи, как массивы numpy, будут эффективно храниться. Там также включена проверка, чтобы избежать непреднамеренной загрузки огромных структур в память при чтении содержимого группы обратно в dict.

import tables
import cPickle

def dict2group(f, parent, groupname, dictin, force=False, recursive=True):
    """
    Take a dict, shove it into a PyTables HDF5 file as a group. Each item in
    the dict must have a type and shape compatible with PyTables Array.

    If 'force == True', any existing child group of the parent node with the
    same name as the new group will be overwritten.

    If 'recursive == True' (default), new groups will be created recursively
    for any items in the dict that are also dicts.
    """
    try:
        g = f.create_group(parent, groupname)
    except tables.NodeError as ne:
        if force:
            pathstr = parent._v_pathname + '/' + groupname
            f.removeNode(pathstr, recursive=True)
            g = f.create_group(parent, groupname)
        else:
            raise ne
    for key, item in dictin.iteritems():
        if isinstance(item, dict):
            if recursive:
                dict2group(f, g, key, item, recursive=True)
        else:
            if item is None:
                item = '_None'
            f.create_array(g, key, item)
    return g


def group2dict(f, g, recursive=True, warn=True, warn_if_bigger_than_nbytes=100E6):
    """
    Traverse a group, pull the contents of its children and return them as
    a Python dictionary, with the node names as the dictionary keys.

    If 'recursive == True' (default), we will recursively traverse child
    groups and put their children into sub-dictionaries, otherwise sub-
    groups will be skipped.

    Since this might potentially result in huge arrays being loaded into
    system memory, the 'warn' option will prompt the user to confirm before
    loading any individual array that is bigger than some threshold (default
    is 100MB)
    """

    def memtest(child, threshold=warn_if_bigger_than_nbytes):
        mem = child.size_in_memory
        if mem > threshold:
            print '[!] "%s" is %iMB in size [!]' % (child._v_pathname, mem / 1E6)
            confirm = raw_input('Load it anyway? [y/N] >>')
            if confirm.lower() == 'y':
                return True
            else:
                print "Skipping item \"%s\"..." % g._v_pathname
        else:
            return True
    outdict = {}
    for child in g:
        try:
            if isinstance(child, tables.group.Group):
                if recursive:
                    item = group2dict(f, child)
                else:
                    continue
            else:
                if memtest(child):
                    item = child.read()
                    if isinstance(item, str):
                        if item == '_None':
                            item = None
                else:
                    continue
            outdict.update({child._v_name: item})
        except tables.NoSuchNodeError:
            warnings.warn('No such node: "%s", skipping...' % repr(child))
            pass
    return outdict

Также стоит упомянуть joblib.dump и joblib.load, которые отмечают все ваши поля, кроме кросс-совместимости Python 2/3. Под капотом они используют np.save для массивов numpy и cPickle для всего остального.

Ответ 3

Я попытался сыграть с np.memmap для сохранения массива словарей. Скажем, у нас есть словарь:

a = np.array([str({'a':1, 'b':2, 'c':[1,2,3,{'d':4}]}])

сначала я попытался напрямую сохранить его в memmap:

f = np.memmap('stack.array', dtype=dict, mode='w+', shape=(100,))
f[0] = d
# CRASHES when reopening since it looses the memory pointer

f = np.memmap('stack.array', dtype=object, mode='w+', shape=(100,))
f[0] = d
# CRASHES when reopening for the same reason

способ, которым он работал, - преобразование словаря в строку:

f = np.memmap('stack.array', dtype='|S1000', mode='w+', shape=(100,))
f[0] = str(a)

это работает, а затем вы можете eval(f[0]) вернуть значение.

Я не знаю преимущества этого подхода над другими, но он заслуживает более пристального взгляда.

Ответ 4

Я абсолютно рекомендую базу данных объектов python, такую ​​как ZODB. Это кажется очень хорошо подходящим для вашей ситуации, учитывая, что вы храните объекты (буквально, что вам нравится) в словаре - это означает, что вы можете хранить словари внутри словарей. Я использовал его в широком спектре проблем, и приятно, что вы можете просто передать кому-то файл базы данных (тот, у которого расширение .fs). При этом они смогут прочитать его и выполнить любые запросы, которые они пожелают, и изменить свои собственные локальные копии. Если вы хотите, чтобы несколько программ одновременно обращались к одной и той же базе данных, я бы не стал смотреть на ZEO.

Просто глупый пример того, как начать:

from ZODB import DB
from ZODB.FileStorage import FileStorage
from ZODB.PersistentMapping import PersistentMapping
import transaction
from persistent import Persistent
from persistent.dict import PersistentDict
from persistent.list import PersistentList

# Defining database type and creating connection.
storage = FileStorage('/path/to/database/zodbname.fs') 
db = DB(storage)
connection = db.open()
root = connection.root()

# Define and populate the structure.
root['Vehicle'] = PersistentDict() # Upper-most dictionary
root['Vehicle']['Tesla Model S'] = PersistentDict() # Object 1 - also a dictionary
root['Vehicle']['Tesla Model S']['range'] = "208 miles"
root['Vehicle']['Tesla Model S']['acceleration'] = 5.9
root['Vehicle']['Tesla Model S']['base_price'] = "$71,070"
root['Vehicle']['Tesla Model S']['battery_options'] = ["60kWh","85kWh","85kWh Performance"]
# more attributes here

root['Vehicle']['Mercedes-Benz SLS AMG E-Cell'] = PersistentDict() # Object 2 - also a dictionary
# more attributes here

# add as many objects with as many characteristics as you like.

# commiting changes; up until this point things can be rolled back
transaction.get().commit()
transaction.get().abort()
connection.close()
db.close()
storage.close()

После создания базы данных это очень простое использование. Поскольку это объектная база данных (словарь), вы можете легко получить доступ к объектам:

#after it opened (lines from the very beginning, up to and including root = connection.root() )
>> root['Vehicles']['Tesla Model S']['range'] 
'208 miles'

Вы также можете отображать все ключи (и выполнять все другие стандартные словарные операции, которые вы, возможно, захотите):

>> root['Vehicles']['Tesla Model S'].keys()
['acceleration', 'range', 'battery_options', 'base_price']

Последнее, что я хочу упомянуть, это то, что ключи можно изменить: Изменение значения ключа в словаре python. Значения также могут быть изменены - поэтому, если ваши результаты исследования меняются, потому что вы меняете свой метод или что-то, что вам не нужно запускать всю базу данных с нуля (особенно, если все остальное все еще в порядке). Будьте осторожны, выполняя оба этих действия. Я включил меры безопасности в свой код базы данных, чтобы убедиться, что я знаю о своих попытках перезаписать ключи или значения.

** ДОБАВЛЕНО **

# added imports
import numpy as np
from tempfile import TemporaryFile
outfile = TemporaryFile()

# insert into definition/population section
np.save(outfile,np.linspace(-1,1,10000))
root['Vehicle']['Tesla Model S']['arraydata'] = outfile

# check to see if it worked
>>> root['Vehicle']['Tesla Model S']['arraydata']
<open file '<fdopen>', mode 'w+b' at 0x2693db0>

outfile.seek(0)# simulate closing and re-opening
A = np.load(root['Vehicle']['Tesla Model S']['arraydata'])

>>> print A
array([-1.        , -0.99979998, -0.99959996, ...,  0.99959996,
    0.99979998,  1.        ])

Вы также можете использовать numpy.savez() для сжатого сохранения нескольких массивов numpy точно так же.

Ответ 5

Это не прямой ответ. В любом случае, вас может заинтересовать и JSON. Посмотрите 13.10. Сериализация типов данных, не поддерживаемых JSON. В нем показано, как расширить формат для неавторизованных типов.

Вся глава "Погружения в Python 3" Марка Пилигрима, безусловно, хорошо читается, по крайней мере, для того, чтобы знать...

Обновление: Возможно, не связанная с этим идея, но... Я где-то читал, что одной из причин, почему XML был окончательно принят для обмена данными в гетерогенной среде, было исследование, в котором сравнивался специализированный двоичный формат с заархивированным XML. Вывод для вас может заключаться в том, чтобы использовать возможно не столь эффективное для пространства решение и сжать его с помощью zip или другого хорошо известного алгоритма. Использование известного алгоритма помогает, когда вам нужно отлаживать (распаковывать, а затем просматривать текстовый файл на глаз).