给定一组具有各种属性(姓名、社保号、电话、电子邮件地址、信用卡号等)的 Person 对象,现在想象一个简单的网站:
- 使用一个人的电子邮件地址作为唯一的登录名。
- 允许用户编辑他们的属性(包括他们的电子邮件地址)。
如果这个网站有大量的用户,那么在字典中存储按电子邮件地址索引的 Person 对象是有意义的,以便在登录时快速检索 Person。然而,当一个人的电子邮件地址被编辑时,该 Person 的字典键也需要被更改。这是一个有点令人厌恶的问题。
我们正在寻找关于如何解决以下一般问题的建议:
给定一组具有共同方面(aspect)的实体。该方面用于快速访问实体和每个实体的功能。该方面应该放在哪里:
- 每个实体内(这不利于快速访问)
- 仅索引(这不利于每个实体的功能)
- 既在每个实体内又在索引中(重复数据/引用)
- 其他地方/以某种不同的方式
如果我们要使用多个索引来索引数据(社保号、信用卡号等),那么这个问题可能会扩展。最终,我们可能会得到一堆 SQL 表。
我们正在寻找具有以下属性的东西(如果你能想到更多,那就更好了):
# 在类的属性上创建一个索引
magical_index = magical_index_factory(class, class.attribute)
# 创建对象
obj = class()
# 设置对象的属性
obj.attribute= value
# 使用属性作为索引从索引中检索对象
magical_index[value]
# 将对象属性更改为新值
obj.attribute= new_value
# 自动检索使用新属性值的对象
magical_index[new_value]
# 摆脱物质主义:摆脱生活中的对象
del obj
# 对象真的不见了
magical_index[new_value]
KeyError: new_value
2、解决方案
方法一:使用观察者模式
考虑这个方案:
class Person( object ):
def __init__( self, name, addr, email, etc. ):
self.observer= []
... etc. ...
@property
def name( self ): return self._name
@name.setter
def name( self, value ):
self._name= value
for observer in self.observedBy: observer.update( self )
... etc. ...
这个观察者属性实现了一个 Observable,它将更新通知给它的观察者。这是必须被通知更新的观察者的列表。
每个属性都用属性包装。使用描述符可能更好,因为它可以节省重复观察者通知。
class PersonCollection( set ):
def __init__( self, *args, **kw ):
self.byName= collections.defaultdict(list)
self.byEmail= collections.defaultdict(list)
super( PersonCollection, self ).__init__( *args, **kw )
def add( self, person ):
super( PersonCollection, self ).append( person )
person.observer.append( self )
self.byName[person.name].append( person )
self.byEmail[person.email].append( person )
def update( self, person ):
"""This person changed. Find them in old indexes and fix them."""
changed = [(k,v) for k,v in self.byName.items() if id(person) == id(v) ]
for k, v in changed:
self.byName.pop( k )
self.byName[person.name].append( person )
changed = [(k,v) for k,v in self.byEmail.items() if id(person) == id(v) ]
for k, v in changed:
self.byEmail.pop( k )
self.byEmail[person.email].append( person)
... etc. ... for all methods of a collections.Set.
使用 collections.ABC 了解更多关于必须实现的内容。
方法二:使用通用索引集合
如果想要“通用”索引,那么集合可以使用属性名称进行参数化,可以使用 getattr 从底层对象获取这些命名的属性。
class GenericIndexedCollection( set ):
attributes_to_index = [ ] # List of attribute names
def __init__( self, *args, **kw ):
self.indexes = dict( (n, {}) for n in self.attributes_to_index ]
super( PersonCollection, self ).__init__( *args, **kw )
def add( self, person ):
super( PersonCollection, self ).append( person )
for i in self.indexes:
self.indexes[i].append( getattr( person, i )
注意。为了正确地模拟数据库,使用集合而不是列表。数据库表(理论上)是集合。在实践中,它们是无序的,索引将允许数据库拒绝重复项。一些 RDBMS 不拒绝重复的行,因为——没有索引——检查太昂贵了。