Сохранение словарей в файл (NumPy и Python 2/3 дружественные)

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

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

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

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

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

есть что-то подобное уже есть?

спасибо!

5 ответов


недавно я столкнулся с аналогичной проблемой, для которой я написал пару функций для сохранения содержимого 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 для всего остального.


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


Я пытался играть с 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]) чтобы вернуть значение.

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


Я абсолютно рекомендую базу данных объектов python, такую как ЗОДБ. Это кажется довольно хорошо подходит для вашей ситуации, учитывая, что вы храните объекты (буквально все, что вам нравится) в словаре - это означает, что вы можете хранить словари внутри словарей. Я использовал его в широком спектре проблем, и хорошо, что вы можете просто передать кому-то файл базы данных (тот, у которого есть .расширение ФС). С помощью этого они смогут прочитать его и выполнить любые запросы, которые они пожелают, и измените собственные локальные копии. Если вы хотите, чтобы несколько программ одновременно обращались к одной базе данных, я бы обязательно посмотрел на ЗЭО.

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

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'] = ",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's 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 точно таким же образом.


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

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

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