python list / dict属性最佳实践

时间:2011-03-16 22:58:24

标签: python

我有一个类对象,它存储一些属性,这些属性是其他对象的列表。列表中的每个项目都有一个可以使用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这样的函数,无论如何都感觉非常pythonic。有没有办法以某种方式子类dict和/或列表,并使用我的类的属性轻松提供所有这些功能?我也想避免尽可能多的代码。

4 个答案:

答案 0 :(得分: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);它是固定的。我将添加一些单元测试代码。如果您遇到任何进一步的错误,请告诉我!

答案 1 :(得分: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')]

答案 2 :(得分: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

答案 3 :(得分: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')会让您感到不一致,但它可能足以让您完成所需的工作。