【问题标题】:Python list class that indexes its object-elements' properties索引其对象元素属性的 Python 列表类
【发布时间】:2012-10-28 17:05:41
【问题描述】:

我正在寻找在 python 中创建列表的最佳方法,该列表为放入列表中的对象的所有属性创建散列索引 (dicts)。

>>> foo = IndexingList([{ 'id': 1, 'name': 'cat' }, { 'id': 2, 'name': 'dog' }])
>>> foo[0]
{'id': 1, 'name': 'cat'}

>>> foo.findall('id', 2)
[{'id': 2, 'name': 'dog'}]

>>> foo += {'id': 3, 'name': 'dog'}
>>> foo.findall('name', 'dog')
[{'id': 2, 'name': 'dog'}, {'id': 3, 'name': 'dog'}]

我想 IndexingList 的数据结构将如下所示:

{
    'items': [
        { 'id': 1, 'name': 'cat' }, 
        { 'id': 2, 'name': 'dog' }
    ],
    'indexes': {
        'id': {
            1: [{ 'id': 1, 'name': 'cat' }],
            2: [{ 'id': 2, 'name': 'dog' }]
        },
        'name': {
            'cat': [{ 'id': 1, 'name': 'cat' }],
            'dog': [
                { 'id': 2, 'name': 'dog' },
                { 'id': 3, 'name': 'dog' }
            ]
        }
    }
}

“indexes”节点中的对象引用“items”中的相同对象。

我认为本身是对象的属性值可以通过使用 str(property) 来获得唯一的索引键,以获取一些东西以粘贴在“索引”中。

【问题讨论】:

    标签: python list indexing


    【解决方案1】:

    这实际上很容易使用一些collections.defaultdict()s 来完成 - 尽管如果你经常使用它,你可能会考虑使用一个实际的数据库。

    from collections import defaultdict
    from functools import partial
    
    class IndexingList:
        def __init__(self, items):
            self.items = []
            self.indices = defaultdict(partial(defaultdict, list))
            self.extend(items)
    
        def append(self, item):
            try:
                for index, value in item.items():
                    self.indices[index][value].append(item)
            except AttributeError as e:
                raise ValueError("All children of an IndexingList must be "
                                 "dict-like. '{0}' is not.".format(item)) from e
            self.items.append(item)
    
        def extend(self, iterable):
            for item in iterable:
                self.append(item)
    
        def __iadd__(self, other):
            self.extend(other)
            return self
    
        def __getitem__(self, item):
            return self.items[item]
    
        def __setitem__(self, item, value):
            self.items[item] = value
    
        def __delitem__(self, item):
            del self.items[item]
            for index, value in item.items():
                self.indices[index][value].remove(item)
    
        def find_all(self, index, value):
            return self.indices[index][value]
    
        def __repr__(self):
            return repr(self.items)
    

    这样使用:

    >>> foo = IndexingList([{ 'id': 1, 'name': 'cat' }, { 'id': 2, 'name': 'dog' }])
    >>> foo[0]
    {'id': 1, 'name': 'cat'}
    >>> foo.find_all("id", 2)
    [{'id': 2, 'name': 'dog'}]
    >>> foo += [{'id': 3, 'name': 'dog'}]
    >>> foo.find_all('name', 'dog')
    [{'id': 2, 'name': 'dog'}, {'id': 3, 'name': 'dog'}]
    

    【讨论】:

    • 感谢您提供这么漂亮的简单代码。如果您不介意,有两个后续问题:(1)您是否有特定原因没有使用 class IndexingList(list) 的类签名并使用例如super(IndexingList, self).append(item) 而不是 self.items.append(item)? (2) 您是否知道修改所包含元素的基础属性可以“通知”IndexingList 以相应地更新其索引的方式?我的用例实际上不需要这个,只是好奇是否有这样的机制。对此评论缺少分段表示歉意。
    • 首先,没有什么特别的原因,我只是认为这种方式更清晰、更简单——在 Python 中,我们并没有被锁定在硬类型层次结构中,所以我认为这里的子类化没有任何真正的好处(除了速度之外,除非被证明是,否则这并不是真正的优先事项)。至于第二个,您可以制作自己的字典对象来执行该通知,但您必须小心它的使用方式 - 我想它会有些脆弱。
    【解决方案2】:

    我必须说 Lattyware 提供了一个非常好的解决方案。我仍然会提供我自己的快速而肮脏的方法,因为当索引独特的项目时,它是一个简单的单行。我有时会在某个列上创建索引,而不是构建一个漂亮的包装容器:

    my_list = [('aap', 123), ('noot', 234), ('mies', 345), ('mies', 456)]
    

    如果该列中的键是唯一的,并且我们不会向列表中添加任何新元素,也不会修改我们可能使用的索引值:

    def mk_unique_index(data, col):
      g = ((elem[col], elem) for elem in data)
      return dict(g)
    

    所以我们可以像这样使用它:

    >>> idx = mk_unique_index(my_list, 1)
    >>> idx[123]
    ('aap', 123)
    

    但是,如果我们希望在第 0 列上建立索引,我们必须使用 defaultdict

    from collections import defaultdict
    def mk_index(data, col):
      d = defaultdict(list)
      for elem in data:
        d[elem[col]].append(elem)
      return d
    

    用法:

    >>> idx = mk_index(my_list, 0)
    >>> idx['mies']
    [('mies', 345), ('mies', 456)]
    

    如果您使用字典而不是元组而不是命名元组(假设所有元素都有您要索引的字段),您可以只提供列的字段名称 显然,也可以选择在内存中使用临时的sqlite 数据库。

    【讨论】:

      猜你喜欢
      • 2012-11-19
      • 2011-09-29
      • 2013-10-10
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多