开发者

python list/dict property best practice

开发者 https://www.devze.com 2023-02-17 20:03 出处:网络
I have a class object that stores some properties that are lists of other objects. Each of the items in the list has an identifier that can be accessed with the id property. I\'d like to be able to re

I have a class object that stores some properties that are lists of other objects. Each of the items in the list has an identifier that can be accessed with the id property. I'd like to be able to read and write from these lists but also be able to access a dictionary keyed by their identifier. Let me illustrate with an example:

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')])

This is a silly example, but it illustrates what I'm trying to do. I'd like to be able to interact with the classroom object like this:

#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)

But I'd also like to be able to access it like it's a dictionary, and the dictionary should be automatically updated when I modify the list:

#access like a dict
print classroom.childrenById['389']

I realize I could just make it a dict, but I want to avoid code like this:

classroom.childrendict[child.id] = child

I also might have several of these properties, so I don't want to add functions like addChild, which feels very un-pythonic anyway. Is there a way to somehow subclass dict and/or list and provide all of these functions easily with my class's properties? I'd also like to avoid as much code as开发者_开发问答 possible.


An indexed list class:

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)

which can be used as:

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

Edit: I had made a mistake in some of the exception-handling (catching KeyError instead of IndexError); it is fixed. I will add some unit-testing code. If you run into any further errors, please let me know!


You could subclass the collections.OrderedDict class. For example:

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])

This code outputs:

Child('389', 'pete')
Child('389', 'pete')
Child('2344', 'joe')
[Child('2344', 'joe')]


Maybe this is some code you wanted to avoid, but for small scale objects performance should be tolerable. I think it is at least within your constraint: I'd also like to avoid as much code as possible.

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

This will be always up-to-date, since it is computed on the fly. A little more optimized version could look like this:

    ...

    @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


Here's another variant:

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

It lets you get inconsistent by allowing you to do classroom.ChildById['123'] = Teacher('456', 'gabe') but it might be good enough to do what you're looking for.

0

精彩评论

暂无评论...
验证码 换一张
取 消