繁体   English   中英

python list / dict属性最佳实践

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM