【问题标题】:Python: in-memory object database which supports indexing?Python:支持索引的内存对象数据库?
【发布时间】:2011-07-06 21:05:44
【问题描述】:

我正在做一些数据处理,如果我可以将一堆字典放在内存数据库中,然后对它运行简单的查询,这将变得相当简单。

例如:

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 对象,序列化它们是不可能的(太慢,破坏身份)。当然,我可以解决这个问题(例如,通过将所有项目存储在一个大列表中,然后在该列表中序列化它们的索引......但这可能需要相当多的摆弄)。
  • 将有成千上万的数据,我将对它们运行大量查找操作(如图形遍历),因此它必须能够执行高效(即索引)查询。
  • 在示例中,数据是非结构化,因此需要我预定义架构的系统会很棘手。

那么,这样的事情存在吗?还是我需要拼凑一些东西?

【问题讨论】:

  • 我不确定任何解决方案都能满足您的所有三个要求。特别是,为你做索引的东西不太可能与笨拙的 Python 对象一起工作。如果是这样,您需要对其进行序列化,或者构建自己的索引。
  • 为什么你认为 Python 对象的序列化会很笨拙?两种主要类型的索引——哈希表和二叉树——只需要对象来实现哈希函数或比较运算符,而 Python 对象可以完成所有这些……
  • 但是,是的——可能没有人建立一个通用的对象数据库,它的行为完全符合我的意愿……但我认为这值得一问。
  • 您可能对 my answer here 或 Divmod 的 Axiom 库感兴趣(不幸的是,他们的网站最近遭遇了严重中断,所以没有链接,但这里有一个 saved version)。

标签: python database data-munging


【解决方案1】:

如何通过sqlite3 standard library module 使用内存中的SQLite 数据库,使用特殊值:memory: 进行连接?如果您不想编写 on SQL 语句,您可以随时使用 ORM,例如SQLAlchemy,来访问内存中的 SQLite 数据库。

编辑:我注意到您说这些值可能是 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)
)

【讨论】:

【解决方案2】:

如果您愿意解决序列化问题,MongoDB 可以为您工作。 PyMongo 提供的界面几乎与您描述的相同。如果您决定序列化,则命中不会那么糟糕,因为 Mongodb 是内存映射的。

【讨论】:

  • 不仅需要序列化,还意味着运行单独的数据库服务器 =\
【解决方案3】:

应该可以只用 isinstance()、hasattr()、getattr() 和 setattr() 来做你想做的事情。

但是,在您完成之前,事情会变得相当复杂!

我想可以将所有对象存储在一个大列表中,然后对每个对象运行查询,确定它是什么并查找给定的属性或值,然后将值和对象作为元组列表返回。然后你可以很容易地对你的返回值进行排序。 copy.deepcopy 将是你最好的朋友,也是你最大的敌人。

听起来很有趣!祝你好运!

【讨论】:

  • 胡? isinstancedeepcopy 和朋友们有什么关系?此外,我意识到有几种方法我可以实现它......但我宁愿找到一些其他人已经编写的代码,然后解决这些问题。
  • 好吧,假设有一个对象的串联,在尝试询问它们之前告诉它们是什么类型的对象会很方便。您确实说过某些值将是 python 对象。如果您必须克隆和更改对象,Deepcopy 可以避免副作用。抱歉,如果它以某种方式冒犯,我真的不知道如何衡量其他人的知识,我自己的知识如此之少。 :-)
【解决方案4】:

如果内存数据库解决方案最终工作量太大,这里有一种自己过滤它的方法,您可能会发现它很有用。

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'}]

【讨论】:

  • 此解决方案为n 字典提供每个查询O(n) 性能,因为filter 将遍历每个字典。对于大量字典和查询,性能将是不可接受的,问题作者指出就是这种情况。这就是为什么基于哈希(每个查询摊销O(1))解决方案至关重要的原因。这就是数据库本身具有此类工作索引的原因。
【解决方案5】:

我知道的唯一解决方案是几年前我在 PyPI 上偶然发现的一个包,PyDbLite。没关系,但有几个问题:

  1. 它仍然希望将所有内容序列化到磁盘,作为一个 pickle 文件。但这对我来说很简单。 (这也是不必要的。如果插入的对象是可序列化的,那么整个集合也是可序列化的。)
  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"]

希望这足以让您入门。

【讨论】:

  • PyDbLite 看起来很有趣,尤其是它用来启用漂亮语法的技术。感谢您指出。
【解决方案6】:

就“身份”而言,您应该能够比较任何可散列的东西,以跟踪对象身份。

Zope 对象数据库 (ZODB): http://www.zodb.org/

PyTables 运行良好: http://www.pytables.org/moin

用于 Python 的 Metakit 也运行良好: http://equi4.com/metakit/python.html
supports columns, and sub-columns but not unstructured data

研究“流处理”,如果您的数据集非常大,这可能很有用: http://www.trinhhaianh.com/stream.py/

任何可以序列化(写入磁盘)的内存数据库都会出现身份问题。如果可能的话,我建议您将要存储的数据表示为原生类型(列表、字典)而不是对象。

请记住,NumPy 旨在对内存数据结构执行复杂的操作,如果您决定推出自己的解决方案,它可能会成为您解决方案的一部分。

【讨论】:

    【解决方案7】:

    我编写了一个名为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
    

    【讨论】:

      【解决方案8】:

      我昨天开始开发一个,还没有发布。它索引您的对象并允许您运行快速查询。所有数据都保存在 RAM 中,我正在考虑智能加载和保存方法。出于测试目的,它通过 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/

        【讨论】:

          猜你喜欢
          • 2014-04-23
          • 2017-06-24
          • 2012-02-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-19
          • 2017-06-14
          • 2019-11-12
          相关资源
          最近更新 更多