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

Python: база данных объектов в памяти, которая поддерживает индексирование?

Я делаю некоторые манипуляции с данными, которые были бы намного проще, если бы я мог вставлять кучу словарей в базу данных в памяти, а затем запускать с ним просто запросы.

Например, что-то вроде:

people = db([
    {"name": "Joe", "age": 16},
    {"name": "Jane", "favourite_color": "red"},
])
over_16 = db.filter(age__gt=16)
with_favorite_colors = db.filter(favorite_color__exists=True)

Есть три смешающих фактора:

  • Некоторые из значений будут объектами Python, и сериализация их не может быть и речи (слишком медленно, ломает личность). Конечно, я мог бы обойти это (например, сохраняя все элементы в большом списке, а затем сериализуя свои индексы в этом списке... Но это может привести к честной игре).
  • Будут тысячи данных, и я буду выполнять операции с избыточным весом (например, обходы графиков) против них, поэтому он должен иметь возможность выполнять эффективные (то есть индексированные) запросы.
  • Как и в примере, данные неструктурированы, поэтому системы, требующие предопределения схемы, будут сложными.

Итак, существует ли такая вещь? Или мне нужно что-то смешать?

4b9b3361

Ответ 1

Как насчет использования базы данных SQLite в памяти через стандартный библиотечный модуль sqlite3, используя специальное значение :memory: для подключения? Если вы не хотите писать свои инструкции SQL, вы всегда можете использовать ORM, например SQLAlchemy, чтобы получить доступ к базе данных SQLite в памяти.

EDIT. Я заметил, что вы указали, что значения могут быть объектами Python, а также что вам нужно избегать сериализации. Требование сохранения произвольных объектов Python в базе данных также требует сериализации.

Могу ли я предложить практическое решение, если вы должны соблюдать эти два требования? Почему бы просто не использовать словари Python в качестве индексов в вашей коллекции словарей Python? Похоже, у вас будут особые потребности в построении каждого из ваших показателей; выяснить, какие значения вы хотите запросить, а затем написать функцию для генерации и индексации для каждого. Возможные значения для одного ключа в вашем списке dicts будут ключами для индекса; значения индекса будут списком словарей. Запросите индекс, указав значение, которое вы ищете в качестве ключа.

import collections
import itertools

def make_indices(dicts):
    color_index = collections.defaultdict(list)
    age_index = collections.defaultdict(list)
    for d in dicts:
        if 'favorite_color' in d:
            color_index[d['favorite_color']].append(d)
        if 'age' in d:
            age_index[d['age']].append(d)
    return color_index, age_index


def make_data_dicts():
    ...


data_dicts = make_data_dicts()
color_index, age_index = make_indices(data_dicts)
# Query for those with a favorite color is simply values
with_color_dicts = list(
        itertools.chain.from_iterable(color_index.values()))
# Query for people over 16
over_16 = list(
        itertools.chain.from_iterable(
            v for k, v in age_index.items() if age > 16)
)

Ответ 2

Единственное решение, которое я знаю, это пакет, который я наткнулся несколько лет назад на PyPI, PyDbLite. Это нормально, но есть несколько проблем:

  • Он по-прежнему хочет сериализовать все на диск, как файл pickle. Но это было достаточно просто для меня, чтобы вырваться. (Это также не нужно. Если вставленные объекты являются сериализуемыми, то и коллекция в целом.)
  • Основной тип записи - это словарь, в который он вставляет свои собственные метаданные, два ints под клавишами __id__ и __version__.
  • Индексирование очень простое, основанное только на значении словаря записи. Если вам нужно что-то более сложное, например, на основе атрибута объекта в записи, вам придется самому его закодировать. (Что-то я хотел сделать сам, но никогда не добирался.)

Кажется, что автор работает над этим время от времени. Там были некоторые новые функции, когда я использовал его, включая хороший синтаксис для сложных запросов.

Предполагая, что вы сорвите травление (и я могу рассказать вам, что я сделал), ваш пример будет (непроверенный код):

from PyDbLite import Base

db = Base()
db.create("name", "age", "favourite_color")

# You can insert records as either named parameters
# or in the order of the fields
db.insert(name="Joe", age=16, favourite_color=None)
db.insert("Jane", None, "red")

# These should return an object you can iterate over
# to get the matching records.  These are unindexed queries.
#
# The first might throw because of the None in the second record
over_16 = db("age") > 16
with_favourite_colors = db("favourite_color") != None

# Or you can make an index for faster queries
db.create_index("favourite_color")
with_favourite_color_red = db._favourite_color["red"]

Надеюсь, этого будет достаточно, чтобы вы начали.

Ответ 3

Если решение базы данных в базе данных заканчивается слишком большой работой, вот какой способ его фильтрации, который может вам пригодиться.

Функция get_filter принимает аргументы, чтобы определить, как вы хотите отфильтровать словарь, и возвращает функцию, которая может быть передана во встроенную функцию filter для фильтрации списка словарей.

import operator

def get_filter(key, op=None, comp=None, inverse=False):
    # This will invert the boolean returned by the function 'op' if 'inverse == True'
    result = lambda x: not x if inverse else x
    if op is None:
        # Without any function, just see if the key is in the dictionary
        return lambda d: result(key in d)

    if comp is None:
        # If 'comp' is None, assume the function takes one argument
        return lambda d: result(op(d[key])) if key in d else False

    # Use 'comp' as the second argument to the function provided
    return lambda d: result(op(d[key], comp)) if key in d else False

people = [{'age': 16, 'name': 'Joe'}, {'name': 'Jane', 'favourite_color': 'red'}]

print filter(get_filter("age", operator.gt, 15), people)
# [{'age': 16, 'name': 'Joe'}]
print filter(get_filter("name", operator.eq, "Jane"), people)
# [{'name': 'Jane', 'favourite_color': 'red'}]
print filter(get_filter("favourite_color", inverse=True), people)
# [{'age': 16, 'name': 'Joe'}]

Это довольно легко расширяется для более сложной фильтрации, например, для фильтрации на основе того, соответствует ли значение регулярному выражению:

p = re.compile("[aeiou]{2}") # matches two lowercase vowels in a row
print filter(get_filter("name", p.search), people)
# [{'age': 16, 'name': 'Joe'}]

Ответ 4

Что касается "идентичности" всего, что хешируется, вы должны иметь возможность сравнивать, чтобы отслеживать идентичность объекта.

База данных Zope Object (ZODB): http://www.zodb.org/

PyTables работает хорошо: http://www.pytables.org/moin

Также Metakit для Python работает хорошо: http://equi4.com/metakit/python.html
supports columns, and sub-columns but not unstructured data

Исследование "Обработка потоков", если ваши наборы данных чрезвычайно велики, это может быть полезно: http://www.trinhhaianh.com/stream.py/

Любая база данных в памяти, которая может быть сериализована (записана на диск), будет иметь вашу проблему с идентификацией. Я бы предложил представить данные, которые вы хотите сохранить как собственные типы (list, dict), а не объекты, если это вообще возможно.

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

Ответ 5

Я написал простой модуль под названием Jsonstore, который решает (2) и (3). Вот как будет выглядеть ваш пример:

from jsonstore import EntryManager
from jsonstore.operators import GreaterThan, Exists

db = EntryManager(':memory:')
db.create(name='Joe', age=16)
db.create({'name': 'Jane', 'favourite_color': 'red'})  # alternative syntax

db.search({'age': GreaterThan(16)})
db.search(favourite_color=Exists())  # again, 2 different syntaxes

Ответ 6

Если вы готовы работать над сериализацией, MongoDB может работать на вас. PyMongo обеспечивает интерфейс, почти идентичный тому, что вы описываете. Если вы решите сериализовать, хит будет не так плох, поскольку Mongodb будет отображаться в памяти.

Ответ 7

Должно быть возможно сделать то, что вы хотите сделать, только с помощью isinstance(), hasattr(), getattr() и setattr().

Тем не менее, все будет довольно сложно, прежде чем вы закончите!

Я предполагаю, что можно хранить все объекты в большом списке, затем запускать запрос для каждого объекта, определять, что это такое и искать данный атрибут или значение, а затем возвращать значение и объект в виде списка кортежей. Тогда вы можете легко сортировать свои возвращаемые значения. copy.deepcopy будет вашим лучшим другом и вашим злейшим врагом.

Звучит весело! Удачи!

Ответ 8

Я начал разрабатывать один вчера, и он еще не опубликован. Он индексирует ваши объекты и позволяет запускать быстрые запросы. Все данные хранятся в ОЗУ, и я думаю о умных нагрузках и методах сохранения. Для целей тестирования это загрузка и сохранение с помощью cPickle.

Сообщите мне, если вы все еще заинтересованы.

Ответ 9

Не уверен, что он соответствует всем вашим требованиям, но TinyDB (с использованием памяти) также, вероятно, стоит попробовать:

>>> from tinydb import TinyDB, Query
>>> from tinydb.storages import MemoryStorage
>>> db = TinyDB(storage=MemoryStorage)
>>> db.insert({'name': 'John', 'age': 22})
>>> User = Query()
>>> db.search(User.name == 'John')
[{'name': 'John', 'age': 22}]

Его простота и мощный механизм запросов делают его очень интересным инструментом для некоторых случаев использования. Подробнее см. http://tinydb.readthedocs.io/.