Concrete classes -- stylistic question

Kragen Sitaker kragen at pobox.com
Sun Oct 13 13:35:54 EDT 2002


Andrew Koenig <ark at research.att.com> writes:
> ...
> Of course, I could define little classes like this:
>         class xy(object):
>                 def __init__(self, x, y):
>                         self.x, self.y = x, y
> ...
> If I want a number of such classes, it's something of a pain to define
> each one separately.  So I came up with an idea:
> ...
>         def __init__(self, **args):
>             for i in args:
>                 setattr(self, i, args[i])

MetaPy.defrecord implements something similar, for similar reasons; it
even includes some syntactic sugar (semantic sugar?) to make it easy
to incrementally change from using tuples to using
MetaPy.defrecord.records.

>>> from MetaPy.defrecord import defrecord
>>> xy = defrecord('x', 'y') 
>>> p = xy(3.4, 5.2)
>>> p
MetaPy.defrecord.record(x=3.3999999999999999, y=5.2000000000000002)
>>> x, y = p
>>> print x, y
3.4 5.2

And it supports named arguments like the ones you used above as well:

>>> p2 = xy(x = 3, y = 2, z = 5)
>>> p3 = xy(x = 3, z = 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/usr/local/lib/python2.1/site-packages/MetaPy/defrecord.py", line 87, in __init__
    raise TypeError, (("takes exactly %d args " +
TypeError: takes exactly 2 args (1 given, first missing arg is y)
>>> p2
MetaPy.defrecord.record(x=3, y=2, z=5)

MetaPy is at http://pobox.com/~kragen/sw/MetaPy-7.tar.gz and
http://pobox.com/~kragen/sw/MetaPy-7.exe, works in Python 1.5.2 and
up, and is free software licensed under the GNU GPL.  The
implementation of defrecord is as follows:

def defrecord(*args, **kwargs):
    class Record:
        def __init__(self, *args, **kwargs):
            if len(args) > len(self.defrecord_tuplevals):
                raise TypeError, ("takes exactly %d args, given %d" %
                                  (len(self.defrecord_tuplevals), len(args)))
            for ii in range(len(self.defrecord_tuplevals)):
                attr = self.defrecord_tuplevals[ii]
                if ii < len(args):
                    if kwargs.has_key(attr):
                        raise TypeError, (("multiple values for " +
                                           "keyword argument '%s'") %
                                          attr)
                    setattr(self, attr, args[ii])
                elif (not kwargs.has_key(attr) and not hasattr(self, attr)):
                    # I wish this error message were more accurate

                    raise TypeError, (("takes exactly %d args " +
                                       "(%d given, first missing arg is %s)")
                                      % (len(self.defrecord_tuplevals),
                                         ii, self.defrecord_tuplevals[ii]))
            for key, value in kwargs.items():
                setattr(self, key, value)
        def __getitem__(self, ii):
            if self.defrecord_allow_getitem:
                return getattr(self, self.defrecord_tuplevals[ii])
            else:
                raise TypeError, "unsubscriptable object"
        def __len__(self):
            if self.defrecord_allow_getitem:
                return len(self.defrecord_tuplevals)
            else:
                raise TypeError, "len() of unsized object"
        # this isn't quite right, unfortunately.
        # evaluating this expression (assuming it's legal Python, which
        # it only will be if people choose attribute names that are
        # identifiers) will return an object with the same attributes,
        # but not the same __getitem__ or class.
        defrecord_classname = 'MetaPy.defrecord.record'
        def __repr__(self):
            rv = []
            for attr in dir(self):
                rv.append("%s=%s" % (attr, repr(getattr(self, attr))))
            return (self.defrecord_classname + '(' +
                    string.join(rv, ", ") + ")")
    if kwargs.has_key('allow_getitem'):
        Record.defrecord_allow_getitem = kwargs['allow_getitem']
    else:
        Record.defrecord_allow_getitem = 1
    if kwargs.has_key('name'): Record.defrecord_classname = kwargs['name']
    Record.defrecord_tuplevals = args
    return Record




More information about the Python-list mailing list