Ordered lists with named fields for Python

February 25, 2007

I periodically find myself dealing with structures that are basically ordered lists with named fields, where elements 0, 1, and 2 are naturally named 'a', 'b', and 'c' and sometimes you want to refer to them by name instead of having to remember their position. This pattern even crops up in the standard Python library, often with functions that started out just returning an ordered list and grew the named fields portion later as people discovered how annoying it was to have to remember that the hour was field 3.

This being Python, I've built myself some general code to add named fields on top of sequence types like list or tuple. For maximum generality my code supports using field names both as attribute names and as indexes, so you can use both obj.field and obj["field"], and you can even do crazy things like obj["field":-1]. The code:

class GetMixin(object):
    fields = {}

    def _mapslice(self, key):
        s, e, step = key.start, key.stop, key.step
        if s in self.fields:
            s = self.fields[s]
        if e in self.fields:
            e = self.fields[e]
        return slice(s, e, step)
    def _mapkey(self, key):
        if isinstance(key, tuple):
            pass
        elif isinstance(key, slice):
            key = self._mapslice(key)
        elif key in self.fields:
            key = self.fields[key]
        return key

    def __getitem__(self, key):
        key = self._mapkey(key)
        return super(GetMixin, self).__getitem__(key)
    def __getattr__(self, name):
        if name in self.fields:
            return self[self.fields[name]]
        raise AttributeError, \
              "object has no attribute '%s'" % name

class SetMixin(GetMixin):
    def __setitem__(self, key, value):
        key = self._mapkey(key)
        super(SetMixin, self).__setitem__(key, value)
    def __setattr__(self, name, value):
        if name in self.fields:
            o = self.fields[name]
            self[o] = value
        else:
            self.__dict__[name] = value

class Example(SetMixin, list):
    fields = {'a': 0, 'b': 1, 'c': 2}

The fields class variable is a dictionary mapping the names of the fields to their index offsets; it need not include all fields, and not all named fields necessarily have values for a particular list (since nothing checks the list length). GetMixin just lets you read the named fields and can be mixed in with tuples; SetMixin lets you write to them by name too, and so needs to be mixed in with lists or other writable sequence types. The easiest way to generate the fields value for the usual case of sequential field names starting from the first element of the list is to use a variant of the enumerate function from the itertools recipes:

from itertools import *
def enum_args(*args):
    return izip(args, count())

class Example(SetMixin, list):
    fields = dict(enum_args('a', 'b', 'c'))

(If you're going to do this a lot, make a version of enum_args that does the dict() step too.)

Inheriting from list, tuple, etc does have one practical wart: you probably want to avoid using field names that are the names of methods that you want to use, because you won't be able to use the obj.field syntax for accessing them. Amusingly, you will be able to set them using that syntax, because __setattr__ gets called for everything, existing attributes included (which is why it needs the dance at the end with the instance's __dict__).

(This code is not quite neurotically complete; truly neurotically complete code would make the available fields appear in dir()'s output. But I don't want to try to think what sort of hacks that would take, since I am seeing visions of dancing metaclasses that automatically create properties for each field name.)

Written on 25 February 2007.
« How CSLab currently does email anti-spam stuff
Things I have learned about effective sysadmin meetings »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sun Feb 25 20:13:09 2007
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.