Python: база данных объектов в памяти, которая поддерживает индексирование? - PullRequest
26 голосов
/ 02 марта 2011

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

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

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 и их сериализацияне может быть и речи (слишком медленно, нарушает идентичность).Конечно, я мог бы обойти это (например, сохранив все элементы в большом списке, затем сериализовав их индексы в этом списке ... Но это может занять немало времени).
  • Будут тысячиданных, и я буду выполнять с ними операции поиска (например, обход графов), поэтому должен быть в состоянии выполнять эффективные (т. е. индексированные) запросы.
  • Как и вНапример, данные неструктурированы , поэтому системы, которые требуют от меня предопределения схемы, будут хитрыми.

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

Ответы [ 9 ]

10 голосов
/ 02 марта 2011

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

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

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

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)
)
5 голосов
/ 02 марта 2011

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

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

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

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

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"]

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

5 голосов
/ 02 марта 2011

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

Функция 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'}]
3 голосов
/ 13 ноября 2012

Я написал простой модуль под названием 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
3 голосов
/ 02 марта 2011

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

База данных объектов Zope (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 был разработан для выполнения сложных операций со структурами данных в памяти, иможет быть частью вашего решения, если вы решите свернуть свое собственное.

0 голосов
/ 20 февраля 2017

Не уверен, что он соответствует всем вашим требованиям, но 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/.

0 голосов
/ 06 декабря 2013

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

Дайте мне знать, если вы все еще заинтересованы.

0 голосов
/ 02 марта 2011

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

Однако перед вами все будет довольно сложносделано!

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

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

0 голосов
/ 02 марта 2011

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

...