要让集合意识到其元素的变化,元素和集合之间必须存在某种联系,当变化发生时可以进行通信。出于这个原因,我们要么必须将实例绑定到集合,要么代理集合的元素,以使变更通信不会泄漏到元素的代码中。
关于我将要介绍的实现的说明,代理方法仅在通过直接设置更改属性时才有效,而不是在方法内部。届时将需要一个更复杂的簿记系统。
此外,假设您需要使用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)