我有一个对象列表。每个对象都有两个字段
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
同样为了简单起见,我们假设所有属性值都不同。例如,没有两个对象具有相同的状态
答案 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
最初包含f
,b
最初包含h
a = [f]
b = [h]
词典:每个词都有一个项,其值是列表之一
d1 = {1:a}
d2 = {1:b}
d1[1]
是列表a
,其中包含f
且f.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)