2011-03-16 78 views
2

我有一个类对象,它存储了一些属性,这些属性是其他对象的列表。列表中的每个项目都有一个可以通过id属性访问的标识符。我希望能够读取和写入这些列表,但也能够访问由其标识符键入的字典。让我用一个例子来说明:python list/dict属性最佳做法

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,这感觉非常pythonic无论如何。有没有办法以某种方式将dict和/或列表进行子类化并轻松地将所有这些功能与我的类的属性一起提供?我也想避免尽可能多的代码。

+0

'classroom.childrenBy('Id')['389']'或者类似的东西? – Gabe 2011-03-16 23:13:08

+0

这可以工作,但我仍然必须为每个属性定义一个childrenBy方法,然后我必须通过子列表进行线性搜索以找到匹配的id。我想在列表更改时更新字典。 – jterrace 2011-03-16 23:18:26

+0

当你说“我也可能有几个这些属性”时,我认为你的意思是像'id'这样的几个属性。你的意思是像“小孩”这样的几个属性?如果是这样,你可以有'教室.ById('孩子')['389']'? – Gabe 2011-03-16 23:23:07

回答

3

索引列表类:

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);它是固定的。我将添加一些单元测试代码。如果您遇到任何进一步的错误,请让我知道!

+0

这太好了。你只是鞭打这个? – jterrace 2011-03-17 05:06:13

+0

是的,所以要小心 - 没有完全测试!此外,索引还没有正确处理重复的字段值 - 添加Child('1234','bob')和Child('2550','bob')将会引起注意。 self._index应该是列表的字典,然后在_delindex中需要一些额外的代码来删除正确的对象; __getitem__应该只返回第一个值,并且应该有一个findAll方法返回整个列表;此外,我正在考虑单独的属性索引,以便您可以执行classroom.children [id = 3],同时默认搜索所有属性。 – 2011-03-17 13:00:40

+0

增加__contains__并获取函数。修改__getitem__以返回keyerror。在这里创建了一个要点:https://gist.github.com/895134 – jterrace 2011-03-30 19:39:41

0

也许这是你想避免的一些代码,但对于小规模的对象性能应该是可以容忍的。我认为这至少在你的约束之内:我也想避免尽可能多的代码。

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 
1

你可以继承的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')] 
+0

OrderedDict仅在python 2.7中可用我相信吗?任何方式在2.6中做到这一点? – jterrace 2011-03-17 00:04:59

+2

@jterrace:http://pypi.python.org/pypi/ordereddict – miku 2011-03-17 00:07:41

+0

得到它与OrderedDict的pypi版本一起工作,但它在其他列表操作上破解,如classroom.children [0:1] – jterrace 2011-03-17 00:37:20

0

这里的另一种变体:

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'),但它可能是不够好做你正在寻找的东西。