pythonic方式索引对象列表

时间:2016-05-27 17:04:35

标签: python dictionary indexing

我有一个对象列表。每个对象都有两个字段

obj1.status = 2
obj1.timestamp = 19211

obj2.status = 3
obj2.timestamp = 14211

obj_list = [obj1, obj2]

我会继续在列表中添加/删除对象,还会更改对象的属性,例如我可以将ob1.status更改为5。 现在我有两个词组

dict1 - <status, object>
dict2 - <timestamp, object> 

如何设计一个简单的解决方案,以便每当我修改/删除/插入列表中的元素时,地图都会自动更新。我对优雅和可扩展的pythonic解决方案感兴趣。例如,将来,我应该能够轻松添加另一个属性和dict

同样为了简单起见,我们假设所有属性值都不同。例如,没有两个对象具有相同的状态

4 个答案:

答案 0 :(得分:2)

这里的一种方法是为dict创建一个类级别MyObj,并使用property装饰器定义更新行为。每次更改或添加对象时,它都会反映在与该类关联的受尊重的词典中。

编辑:正如@BrendanAbel指出的那样,使用weakref.WeakValueDictionary代替dict处理从类级别dicts中删除对象。

from datetime import datetime
from weakref import WeakValueDictionary

DEFAULT_TIME = datetime.now()


class MyObj(object):
    """
    A sample clone of your object
    """
    timestamps = WeakValueDictionary()
    statuses   = WeakValueDictionary()

    def __init__(self, status=0, timestamp=DEFAULT_TIME):
        self._status    = status
        self._timestamp = timestamp

        self.status     = status
        self.timestamp  = timestamp

    def __update_class(self):
        MyObj.timestamps.update({self.timestamp: self})
        MyObj.statuses.update({self.status: self})

    def __delete_from_class(self):
        maybe_self = MyObj.statuses.get(self.status, None)
        if maybe_self is self is not None:
            del MyObj.statuses[self.status]

        maybe_self = MyObj.timestamps.get(self.timestamp, None)
        if maybe_self is self is not None:
            del MyObj.timestamps[self.timestamp]

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, val):
        self.__delete_from_class()
        self._status = val
        self.__update_class()

    @property
    def timestamp(self):
        return self._timestamp

    @timestamp.setter
    def timestamp(self, val):
        self.__delete_from_class()
        self._timestamp = val
        self.__update_class()

    def __repr__(self):
        return "MyObj: status={} timestamp={}".format(self.status, self.timestamp)


obj1 = MyObj(1)
obj2 = MyObj(2)
obj3 = MyObj(3)

lst = [obj1, obj2, obj3]

# In [87]: q.lst
# Out[87]: 
# [MyObj: status=1 timestamp=2016-05-27 13:43:38.158363,
#  MyObj: status=2 timestamp=2016-05-27 13:43:38.158363,
#  MyObj: status=3 timestamp=2016-05-27 13:43:38.158363]

# In [88]: q.MyObj.statuses[1]
# Out[88]: MyObj: status=1 timestamp=2016-05-27 13:43:38.158363

# In [89]: q.MyObj.statuses[1].status = 42

# In [90]: q.MyObj.statuses[42]
# Out[90]: MyObj: status=42 timestamp=2016-05-27 13:43:38.158363

# In [91]: q.MyObj.statuses[1]
# ---------------------------------------------------------------------------
# KeyError                                  Traceback (most recent call last)
# <ipython-input-91-508ab072bfc4> in <module>()
# ----> 1 q.MyObj.statuses[1]

# KeyError: 1

答案 1 :(得分:2)

您可以在设置值时覆盖对象上的__setattr__以更新索引。您可以对索引使用weakref字典,这样当您删除对象并且不再使用它们时,它们会自动从索引中删除。

import weakref
from bunch import Bunch


class MyObject(object):

    indexes = Bunch()  # Could just use dict()

    def __init__(self, **kwargs):
        super(MyObject, self).__init__()
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __setattr__(self, name, value):
        try:
            index = MyObject.indexes[name]
        except KeyError:
            index = weakref.WeakValueDictionary()
            MyObject.indexes[name] = index
        try:
            old_val = getattr(self, name)
            del index[old_val]
        except (KeyError, AttributeError):
            pass
        object.__setattr__(self, name, value)
        index[value] = self


obj1 = MyObject(status=1, timestamp=123123)
obj2 = MyObject(status=2, timestamp=2343)


print MyObject.indexes.status[1]
print obj1.indexes.timestamp[2343]
obj1.status = 5
print obj2.indexes['status'][5]

我在这里使用Bunch,因为它允许您使用.name表示法访问索引,但您可以使用dict代替['name']语法。

答案 2 :(得分:1)

要使集合意识到其元素的变异,元素与该集合之间必须存在某种连接,这些连接可以在发生变化时进行通信。因此,我们必须将实例绑定到集合或代理集合的元素,以便更改 - 通信不会泄漏到元素的代码中。

关于我将要呈现的实现的注释,代理方法仅在通过直接设置而不是方法内部更改属性时才有效。那么就需要一个更复杂的簿记系统。

此外,它假定所有属性的完全重复都不存在,因为您需要使用set个对象而不是list构建索引

from collections import defaultdict

class Proxy(object):
    def __init__(self, proxy, collection):
        self._proxy = proxy
        self._collection = collection

    def __getattribute__(self, name):
        if name in ("_proxy", "_collection"):
           return object.__getattribute__(self, name)
        else:
           proxy = self._proxy
           return getattr(proxy, name)

    def __setattr__(self, name, value):
        if name in ("_proxy", "collection"):
           object.__setattr__(self, name, value)
        else:
           proxied = self._proxy
           collection = self._collection
           old = getattr(proxied, name)
           setattr(proxy, name, value)
           collection.signal_change(proxied, name, old, value)


class IndexedCollection(object):
     def __init__(self, items, index_names):
         self.items = list(items)
         self.index_names = set(index_names)
         self.indices = defaultdict(lambda: defaultdict(set))

     def __len__(self):
         return len(self.items)

     def __iter__(self):
         for i in range(len(self)):
             yield self[i]    

     def remove(self, obj):
         self.items.remove(obj)
         self._remove_from_indices(obj)

     def __getitem__(self, i):
         # Ensure consumers get a proxy, not a raw object
         return Proxy(self.items[i], self)

     def append(self, obj):
         self.items.append(obj)
         self._add_to_indices(obj)

     def _add_to_indices(self, obj):
          for indx in self.index_names:
              key = getattr(obj, indx)
              self.indices[indx][key].add(obj)

     def _remove_from_indices(self, obj):
          for indx in self.index_names:
              key = getattr(obj, indx)
              self.indices[indx][key].remove(obj)

     def signal_change(self, obj, indx, old, new):
          if indx not in self.index_names:
               return
          # Tell the container to update its indices for a
          # particular attribute and object
          self.indices[indx][old].remove(obj)
          self.indices[indx][new].add(obj)

答案 3 :(得分:-1)

我不确定这是否是你要求的但是......

物件:

import operator
class Foo(object):
    def __init__(self):
        self.one = 1
        self.two = 2

f = Foo()
f.name = 'f'
g = Foo()
g.name = 'g'
h = Foo()
h.name = 'h'

name = operator.attrgetter('name')

列表:a最初包含fb最初包含h

a = [f]
b = [h]

词典:每个词都有一个项,其值是列表之一

d1 = {1:a}
d2 = {1:b}

d1[1]是列表a,其中包含ff.one为1

>>> d1
{1: [<__main__.Foo object at 0x03F4CA50>]}
>>> name(d1[1][0])
'f'
>>> name(d1[1][0]), d1[1][0].one
('f', 1)

在字典

中更改f.one
>>> f.one = '?'
>>> name(d1[1][0]), d1[1][0].one
('f', '?')
>>> 

d2[1]是列表b,其中包含h

>>> d2
{1: [<__main__.Foo object at 0x03F59070>]}
>>> name(d2[1][0]), d2[1][0].one
('h', 1)

将一个对象添加到b,并在字典中看到

>>> b.append(g)
>>> b
[<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>]
>>> d2
{1: [<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>]}
>>> name(d2[1][1]), d2[1][1].one
('g', 1)