Welcome, guest | Sign In | My Account | Store | Cart

A simple recipe for building tuple-like classes with attribute acessors.

Python, 27 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def superTuple(name, attributes):
    """Creates a Super Tuple class."""
    dct = {}
    #Create __new__.
    nargs = len(attributes)
    def _new_(cls, *args):
        if len(args) != nargs:
            raise TypeError("%s takes %d arguments (%d given)." % (cls.__name__,
                                                                   nargs,
                                                                   len(args)))
        return tuple.__new__(cls, args)
    dct["__new__"] = staticmethod(_new_)
    #Create __repr__.
    def _repr_(self):
        contents = [repr(elem) for elem in self]
        return "%s<%s>" % (self.__class__.__name__,
                           ", ".join(contents))
    dct["__repr__"] = _repr_
    #Create attribute properties.
    def getter(i):
        return lambda self: self.__getitem__(i)
    for index, attribute in enumerate(attributes):
        dct[attribute] = property(getter(index))
    #Set slots.
    dct["__slots__"] = []
    #Return class.
    return type(name, (tuple,), dct)

Data is often passed around via tuples, with tuples playing the role of structs or simple-minded records. But remembering what field corresponds to what position can be a real PITA. This recipe attempts to solve that situation.

The recipe is very simple and consists in a unique factory function. As arguments it takes the name of the class and a list of attributes's names. The following simple example illustrates it's use. First we create the class:

>>> Point = superTuple("Point", ["x", "y"])
>>> Point



>>> Point.mro()
[, , ]
>>> Point.__name__
'Point'

So far, so good. Now we create an instance:

>>> p = Point(1, 2, 3)
Traceback (most recent call last):
  File "", line 1, in ?
  File "C:\Gonçalo\Programming\Python\Code\Library\Containers\superTuple.py", line 16, in _new_
    raise TypeError("%s takes %d arguments (%d given)." % (cls.__name__,
TypeError: Point takes 2 arguments (3 given).

Our constructor is working OK.

>>> p = Point(1, 2)
>>> p



>>> p.x
1
>>> p.y
2

As far as implementation goes we just build __new__ and __repr__ methods and for each attribute in the argument attributes list we make a read-only property fetching the corresponding element in the tuple. Then we just call the type constructor with the correct arguments. We have also made __slots__ to be an empty list. In general, I dislike the __slots__ hack, but in this case, since superTuple's are tuple subclasses anyway I figured it was the best thing to do -- all internal state is kept in the tuple elements.

2 comments

Gonçalo Rodrigues (author) 20 years, 7 months ago  # | flag

some text was eaten... Some of the text was eaten. For example, Point.mro() was munged. It's all those greater-than and less-than signs being mistaken for HTML tags.

Raymond Hettinger 20 years, 4 months ago  # | flag

Alternate Implementation. This has a slightly different interface, but is simple and fast:

def SuperTuple(*fields):
    mapping = dict([(fname, fnum) for fnum, fname in enumerate(fields)])
    class Temp(tuple):
        def __getattr__(self, key, fields=mapping):
            return self[fields[key]]
    return Temp

Point = SuperTuple('x', 'y')

>>> p = Point([1,2])
>>> p.x
1
>>> p.y
2

If the field names need to be visible, there is a more direct approach:

def SuperTuple(*fields):
    class AttrTuple(tuple):
        pass
    for i, name in enumerate(fields):
        def geti(self, i=i):
            return self[i]
        setattr(AttrTuple, name, property(geti))
    return AttrTuple

Point = SuperTuple('x', 'y')

p = Point([1,2])
>>> p.x
1
>>> p.y
2
>>> 'x' in dir(p)
True
Created by Gonçalo Rodrigues on Thu, 28 Aug 2003 (PSF)
Python recipes (4591)
Gonçalo Rodrigues's recipes (9)

Required Modules

  • (none specified)

Other Information and Tasks