[英]python list/dict property best practice
我有一个存储一些属性的类对象,这些属性是其他对象的列表。 列表中的每个项目都有一个标识符,可以使用id
属性进行访问。 我希望能够从这些列表中进行读取和写入,但也能够访问以其标识符为键的字典。 让我用一个例子来说明:
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Teacher(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Classroom(object):
def __init__(self, children, teachers):
self.children = children
self.teachers = teachers
classroom = Classroom([Child('389','pete')],
[Teacher('829','bob')])
这是一个愚蠢的例子,但是它说明了我正在尝试做的事情。 我希望能够与教室对象进行如下交互:
#access like a list
print classroom.children[0]
#append like it's a list
classroom.children.append(Child('2344','joe'))
#delete from like it's a list
classroom.children.pop(0)
但是我也希望能够像访问字典一样访问它,并且当我修改列表时,字典应该自动更新:
#access like a dict
print classroom.childrenById['389']
我意识到我可以把它变成字典,但是我想避免这样的代码:
classroom.childrendict[child.id] = child
我可能还具有这些属性中的几个,所以我不想添加诸如addChild
函数,无论如何,它们感觉都是非常不符合Python的。 有没有办法以某种方式子类dict和/或列出并轻松提供类属性的所有这些功能? 我也想避免尽可能多的代码。
索引列表类:
class IndexedList(list):
def __init__(self, items, attrs):
super(IndexedList,self).__init__(items)
# do indexing
self._attrs = tuple(attrs)
self._index = {}
_add = self._addindex
for obj in self:
_add(obj)
def _addindex(self, obj):
_idx = self._index
for attr in self._attrs:
_idx[getattr(obj, attr)] = obj
def _delindex(self, obj):
_idx = self._index
for attr in self._attrs:
try:
del _idx[getattr(obj,attr)]
except KeyError:
pass
def __delitem__(self, ind):
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
return list.__delitem__(self, ind)
def __delslice__(self, i, j):
for ind in xrange(i,j):
self.__delitem__(ind)
def __getitem__(self, ind):
try:
return self._index[ind]
except KeyError:
return list.__getitem__(self, ind)
def __getslice__(self, i, j):
return IndexedList(list.__getslice__(self, i, j))
def __setitem__(self, ind, new_obj):
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
self._addindex(new_obj)
return list.__setitem__(ind, new_obj)
def __setslice__(self, i, j, newItems):
_get = self.__getitem__
_add = self._addindex
_del = self._delindex
newItems = list(newItems)
# remove indexing of items to remove
for ind in xrange(i,j):
_del(_get(ind))
# add new indexing
if isinstance(newList, IndexedList):
self._index.update(newList._index)
else:
for obj in newList:
_add(obj)
# replace items
return list.__setslice__(self, i, j, newList)
def append(self, obj):
self._addindex(obj)
return list.append(self, obj)
def extend(self, newList):
newList = list(newList)
if isinstance(newList, IndexedList):
self._index.update(newList._index)
else:
_add = self._addindex
for obj in newList:
_add(obj)
return list.extend(self, newList)
def insert(self, ind, new_obj):
# ensure that ind is a numeric index
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._addindex(new_obj)
return list.insert(self, ind, new_obj)
def pop(self, ind=-1):
# ensure that ind is a numeric index
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
return list.pop(self, ind)
def remove(self, ind_or_obj):
try:
obj = self._index[ind_or_obj]
ind = list.index(self, obj)
except KeyError:
ind = list.index(self, ind_or_obj)
obj = list.__getitem__(self, ind)
self._delindex(obj)
return list.remove(self, ind)
可以用作:
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Teacher(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Classroom(object):
def __init__(self, children, teachers):
self.children = IndexedList(children, ('id','name'))
self.teachers = IndexedList(teachers, ('id','name'))
classroom = Classroom([Child('389','pete')], [Teacher('829','bob')])
print classroom.children[0].name # -> pete
classroom.children.append(Child('2344','joe'))
print len(classroom.children) # -> 2
print classroom.children[1].name # -> joe
print classroom.children['joe'].id # -> 2344
print classroom.children['2344'].name # -> joe
p = classroom.children.pop('pete')
print p.name # -> pete
print len(classroom.children) # -> 1
编辑:我在某些异常处理中犯了一个错误(捕获KeyError而不是IndexError); 它是固定的。 我将添加一些单元测试代码。 如果您遇到任何其他错误,请告诉我!
您可以将collections.OrderedDict类作为子类。 例如:
import collections
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
def __repr__(self):
return 'Child(\'%s\', \'%s\')' % (self.id, self.name)
class MyOrderedDict(collections.OrderedDict):
def __init__(self, *args, **kwds):
super(MyOrderedDict, self).__init__()
if len(args) > 0:
for i in args[0]:
super(MyOrderedDict, self).__setitem__(i.id, i)
def __getitem__(self, key):
if isinstance(key, int):
return super(MyOrderedDict, self).__getitem__(self.keys()[key])
if isinstance(key, slice):
return [super(MyOrderedDict, self).__getitem__(k) for k in self.keys()[key]]
return super(MyOrderedDict, self).__getitem__(key)
def append(self, item):
super(MyOrderedDict, self).__setitem__(item.id, item)
def pop(self, key = None, default = object()):
if key is None:
return self.popitem()
return super(MyOrderedDict, self).pop(self.keys()[key], default = default)
class Classroom(object):
def __init__(self, children):
self.children = MyOrderedDict(children)
classroom = Classroom([Child('389', 'pete')])
print repr(classroom.children[0])
classroom.children.append(Child('2344', 'joe'))
print repr(classroom.children.pop(0))
print repr(classroom.children['2344'])
print repr(classroom.children[0:1])
此代码输出:
Child('389', 'pete')
Child('389', 'pete')
Child('2344', 'joe')
[Child('2344', 'joe')]
也许这是您想要避免的一些代码,但是对于小规模的对象,性能应该是可以容忍的。 我认为这至少在您的限制之内: 我也想避免尽可能多的代码。
class Classroom(object):
""" Left out the teachers part for simplicity """
def __init__(self, children):
self.children = children
self._child_by_id = {}
@property
def child_by_id(self):
""" Return a dictionary with Child ids as keys and Child objects
as corresponding values.
"""
self._child_by_id.clear()
for child in self.children:
self._child_by_id[child.id] = child
return self._child_by_id
由于它是即时计算的,因此它将始终是最新的。 经过优化的版本可能如下所示:
...
@property
def child_by_id(self):
scbi, sc = self._child_by_id, self.children
scbi.clear()
for child in sc:
scbi[child.id] = child
return scbi
这是另一个变体:
class Classroom(object):
def __init__(self, objects):
for obj in objects:
self.add(obj)
def add(self, obj):
name = obj.__class__.__name__ + "ById"
if name not in self.__dict__:
self.__dict__[name] = {}
self.__dict__[name][obj.id] = obj
def remove(self, obj):
name = obj.__class__.__name__ + "ById"
del self.__dict__[name][obj.id]
def listOf(self, name):
return self.__dict__[name + "ById"].values()
classroom = Classroom([Child('389','pete'),
Teacher('829','bob')])
print classroom.ChildById['389']
classroom.ChildById['123'] = Child('123', 'gabe')
print classroom.listOf('Child')
classroom.remove(classroom.listOf('Teacher')[0])
print classroom.TeacherById
通过允许您去classroom.ChildById['123'] = Teacher('456', 'gabe')
它使您变得前后不一致classroom.ChildById['123'] = Teacher('456', 'gabe')
但它可能足以满足您的需求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.