Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Concrete classes -- stylistic question

1 view
Skip to first unread message

Andrew Koenig

unread,
Oct 10, 2002, 11:54:44 AM10/10/02
to
On a few occasions I've wanted to define ``concrete classes'' --
classes that are so simple that their structure is their interface.
For example, I might want a little class to hold (x, y) pairs.

Of course, I can use a tuple, but then I have to remember the meaning
of each element. Moreover, if I want to have (x, y) pairs and (r, theta)
pairs, I don't have an easy way of checking which one is which in case
I use the wrong one by accident.

Of course, I could define little classes like this:

class xy(object):
def __init__(self, x, y):
self.x, self.y = x, y

class rtheta(object):
def __init__(self, r, theta):
self.r, self.theta = r, theta

Then, if I execute

foo = xy(3, 4)
foo.r

I will learn quickly that I used the wrong class.

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:

class Record(object):
def __init__(self, **args):
for i in args:
setattr(self, i, args[i])
def __repr__(self):
names = filter(
lambda x: not (len(x) >= 4 and x[:2] == x[-2:] == "__"),
dir(self))
names.sort()
return "Record(" + ", ".join(
map(lambda x: x + '=' + `getattr(self, x)`, names)) + ")"

Here's an example of how one might use it:

>>> foo = Record(x=3, y=4)
>>> foo
Record(x=3, y=4)
>>> foo.x
3
>>> foo.y
4
>>> foo.r
AttributeError: 'Record' object has no attribute 'r'

My questions:

1) Am I reinventing the wheel here? If so, where is the original?

2) Is this approach Pythonic? If not, why not, and what would you
recommend as an alternative?

--
Andrew Koenig, a...@research.att.com, http://www.research.att.com/info/ark

Gerhard Häring

unread,
Oct 10, 2002, 12:21:19 PM10/10/02
to
Andrew Koenig wrote in comp.lang.python:

> On a few occasions I've wanted to define ``concrete classes'' --
> classes that are so simple that their structure is their interface.
> For example, I might want a little class to hold (x, y) pairs.
>
> Of course, I can use a tuple, but then I have to remember the meaning
> of each element. Moreover, if I want to have (x, y) pairs and (r, theta)
> pairs, I don't have an easy way of checking which one is which in case
> I use the wrong one by accident.
> [...]

> My questions:
>
> 1) Am I reinventing the wheel here? If so, where is the original?

Check out Alex Martelli's 'bunch' recipe:

'The simple but handy "collector of a bunch of named stuff" class'
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308

-- Gerhard

Aahz

unread,
Oct 10, 2002, 12:30:11 PM10/10/02
to
In article <yu99r8ey...@europa.research.att.com>,

Andrew Koenig <a...@research.att.com> wrote:
>
>On a few occasions I've wanted to define ``concrete classes'' --
>classes that are so simple that their structure is their interface.
>For example, I might want a little class to hold (x, y) pairs.
>
>Of course, I can use a tuple, but then I have to remember the meaning
>of each element. Moreover, if I want to have (x, y) pairs and (r, theta)
>pairs, I don't have an easy way of checking which one is which in case
>I use the wrong one by accident.
>
>Of course, I could define little classes like this:
>
> class xy(object):
> def __init__(self, x, y):
> self.x, self.y = x, y
>
> class rtheta(object):
> def __init__(self, r, theta):
> self.r, self.theta = r, theta

You can also do the dirt-simple:

class xy: pass
class rtheta: pass

foo = xy(); foo.x,foo.y = 1,2

I'm not recommending this, of course. You can also do the even more
dirt-simple:

class Record: pass

foo = Record(); foo.x,foo.y = 1,2

It all depends on where you want your complexity to lie.
--
Aahz (aa...@pythoncraft.com) <*> http://www.pythoncraft.com/

Project Vote Smart: http://www.vote-smart.org/

Gerhard Häring

unread,
Oct 10, 2002, 12:47:13 PM10/10/02
to
Aahz wrote in comp.lang.python:

> Andrew Koenig <a...@research.att.com> wrote:
>>
>>On a few occasions I've wanted to define ``concrete classes'' --
>>classes that are so simple that their structure is their interface.
>>For example, I might want a little class to hold (x, y) pairs.
> You can also do the dirt-simple:
>
> class xy: pass
>
> foo = xy(); foo.x,foo.y = 1,2

I'd suggest:

class Point(object):
__slots__ = ["x", "y"]

p = Point()
p.x = 5
p.y = 6
p.z = 7 # raises AttributeError

Now who will come up with a neat example using metaclasses?

-- Gerhard

John Hunter

unread,
Oct 10, 2002, 12:43:16 PM10/10/02
to
>>>>> "Andrew" == Andrew Koenig <a...@research.att.com> writes:

Andrew> Am I reinventing the wheel here? If so, where is the
Andrew> original?

Does you approach have anything substantive over the following?

class Record:
pass

foo = Record()
foo.x, foo.y = 3, 4

print foo.x
print foo.__dict__

boo = Record()
boo.r, boo.theta = 1, 0.5

print boo.x #oops


Since your approach uses keyword args in Record, it doesn't seem like
you'll save any typing or clarity by using named keywords in the init
function rather than on the RHS of the tuple assignment.

Your __repr__ is a little prettier than printing __dict__, but no more
informative.

JDH

Jeff Epler

unread,
Oct 10, 2002, 12:58:25 PM10/10/02
to
On Thu, Oct 10, 2002 at 04:47:13PM +0000, Gerhard Häring wrote:
> Now who will come up with a neat example using metaclasses?

I'm using a function where a metaclass might serve the same purpose. As
a bonus, __init__ lets (well, forces) you fill out values in "the standard order",
without keyword arguments. An enhancement would let the subclass
specify the min # args and also take **kw.

(If you want to define other methods on your record, then don't use
MakeRecord, derive a class from Record explicitly. For instance,
class Polar:
__slots__ = "r theta".split()

def __mul__(self, other):
if isinstance(self, Polar):
return Polar(self.r * other.r,
math.fmod(self.theta + other.theta, 2*math.pi))
else:
return Polar(self.r * other, self.theta)
# ...
)

---------------------------------------------------------------------------
import new

class Record(object):
def __init__(self, *args):
if len(args) != len(self.__slots__):
raise TypeError, "%s takes exactly %s arguments (%s given)" % (
self.__class__.__name__, len(self.__slots__, len(args)))
for k, v in zip(self.__slots__, args):
setattr(self, k, v)

def MakeRecord(name, slots):
if isinstance(slots, str): slots = slots.split()
return new.classobj(name, (Record,), {'__slots__': slots})

Polar = MakeRecord('Polar', "r theta")
---------------------------------------------------------------------------

Michael Hudson

unread,
Oct 10, 2002, 1:23:56 PM10/10/02
to
Andrew Koenig <a...@research.att.com> writes:

> On a few occasions I've wanted to define ``concrete classes'' --
> classes that are so simple that their structure is their interface.
> For example, I might want a little class to hold (x, y) pairs.

I was bored, so I wrote this:

/>> class typed_property(object):
|.. def __init__(self, type, name):
|.. self.type = type
|.. self.name = name
|.. def __get__(self, ob, obclass):
|.. return getattr(ob, '%s_value'%self.name)
|.. def __set__(self, ob, value):
|.. if not isinstance(value, self.type):
|.. raise TypeError, "'%s' should be %r, not %r"%(self.name,
|.. self.type, type(value))
|.. setattr(ob, "%s_value"%self.name, value)
|.. def __delete__(self):
|.. delattr(ob, "%s_value"%self.name)
\__
/>> def TypedBunch(name, bases, ns):
|.. our_ns = {}
|.. propnames = [k for k in ns if not k.startswith('_')]
|.. for k in propnames:
|.. our_ns[k] = typed_property(ns[k], k)
|.. our_ns['__slots__'] = propnames + ['%s_value'%k for k in propnames]
|.. return type(name, bases, our_ns)
\__
/>> class XY:
|.. __metaclass__ = TypedBunch
|.. x = int
|.. y = (int, float)
\__
->> xy = XY()
->> xy.x = 1
->> xy.y = 1.1
->> xy.z = 1
Traceback (most recent call last):
File "<input>", line 1, in ?
AttributeError: 'XY' object has no attribute 'z'
->> xy.x
1
->> xy.x = "abc"
Traceback (most recent call last):
File "<input>", line 1, in ?
File "<input>", line 9, in __set__
TypeError: 'x' should be <type 'int'>, not <type 'str'>
->>

Not sure it's useful, but it was fun :)

Hmm, the __delete__ method looks broken. Oh well.

Cheers,
M.

--
MARVIN: What a depressingly stupid machine.
-- The Hitch-Hikers Guide to the Galaxy, Episode 7

Michael Hudson

unread,
Oct 10, 2002, 1:33:19 PM10/10/02
to
Gerhard Häring <gerhard...@gmx.de> writes:

> Now who will come up with a neat example using metaclasses?

I posted my article before reading this, honest :)

Cheers,
M.

--
The rapid establishment of social ties, even of a fleeting nature,
advance not only that goal but its standing in the uberconscious
mesh of communal psychic, subjective, and algorithmic interbeing.
But I fear I'm restating the obvious. -- Will Ware, comp.lang.python

Steve Holden

unread,
Oct 10, 2002, 1:43:01 PM10/10/02
to
I think you're being a bit too complex here. A long time ago I remember Alex
Martelli posting someting along the follwoing lines, which still works well
today. Forgive me if it doesn't meet some part of your requirements.

>>> class Record(object):
... def __init__(self, **args):
... self.__dict__.update(args)
...
>>> pt = Record(x=1, y=2)
>>> pt.x
1
>>> pt.z


Traceback (most recent call last):

File "<stdin>", line 1, in ?
AttributeError: 'Record' object has no attribute 'z'
>>>

Any good?

regards
-----------------------------------------------------------------------
Steve Holden http://www.holdenweb.com/
Python Web Programming http://pydish.holdenweb.com/pwp/
Previous .sig file retired to www.homeforoldsigs.com
-----------------------------------------------------------------------
"Andrew Koenig" <a...@research.att.com> wrote in message
news:yu99r8ey...@europa.research.att.com...

Andrew Koenig

unread,
Oct 10, 2002, 1:57:34 PM10/10/02
to
Aahz> You can also do the dirt-simple:

Aahz> class xy: pass
Aahz> class rtheta: pass

Aahz> foo = xy(); foo.x,foo.y = 1,2

Aahz> I'm not recommending this, of course. You can also do the even more
Aahz> dirt-simple:

Aahz> class Record: pass

Aahz> foo = Record(); foo.x,foo.y = 1,2

Aahz> It all depends on where you want your complexity to lie.

Understood.

I was looking for a more compact notation for constructing
such objects.

Aahz

unread,
Oct 10, 2002, 2:05:22 PM10/10/02
to
In article <lkk7kq5...@pc150.maths.bris.ac.uk>,

Michael Hudson <m...@python.net> wrote:
>
>Not sure it's useful, but it was fun :)

You're sick. Go sit over by the Martellibot.

Andrew Koenig

unread,
Oct 10, 2002, 2:01:37 PM10/10/02
to
John> Does you approach have anything substantive over the following?

John> class Record:
John> pass

John> foo = Record()
John> foo.x, foo.y = 3, 4

Yes: It lets me write f(Record(x=3, y=4)) instead of having to write

temp = Record()
temp.x, temp.y = 3, 4
f(temp)

Alex Martelli

unread,
Oct 10, 2002, 5:29:15 PM10/10/02
to
Gerhard Häring wrote:
...

> Now who will come up with a neat example using metaclasses?

I borrowed Guido's time machine to post about that:

http://tinyurl.com/1wq1

(AKA, long URL likely to be split:
http://groups.google.com/groups?q=oe=utf-8&selm=0qZV8.51942%24vm5.1927874%40news2.tin.it&rnum=2
)


Alex

Alex Martelli

unread,
Oct 10, 2002, 5:33:43 PM10/10/02
to
Gerhard Häring wrote:
...

>> 1) Am I reinventing the wheel here? If so, where is the
>> original?
>
> Check out Alex Martelli's 'bunch' recipe:
>
> 'The simple but handy "collector of a bunch of named stuff" class'
> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308

I think my MetaBunch at http://tinyurl.com/1wq1 is a better wheel though:-)


Alex

Ian Bicking

unread,
Oct 10, 2002, 8:25:31 PM10/10/02
to
On Thu, 2002-10-10 at 16:29, Alex Martelli wrote:
> Gerhard Häring wrote:
> ...
> > Now who will come up with a neat example using metaclasses?
>
> I borrowed Guido's time machine to post about that:
>
> http://tinyurl.com/1wq1

I should really learn to understand what's going on with metaclasses,
but here's my attempt at similar functionality without metaclasses...

# Use Required if you don't want an attribute to have a default
# (i.e., a value has to be given at instantiation)
class Required: pass

def bunchbuilder(className, **kw):

class Bunch(object):

__slots__ = kw.keys()
__defaults = kw
__name = className

def __init__(self, **kw):
for var in self.__slots__:
if self.__defaults[var] is Required \
and not kw.has_key(var):
raise TypeError, "%s() did not provide required keyword argument %s" % (self.__name, var)
if kw.has_key(var):
setattr(self, var, kw[var])
del kw[var]
else:
setattr(self, var, self.__defaults[var])
if kw:
raise TypeError, "%s() got unexpected keyword argument %s" % (self.__name, kw.keys())

def __repr__(self):
names = [name for name in dir(self) if not name.startswith('__') and not name.startswith('_Bunch__')]
names.sort()
return "%s(%s)" % (self.__name, ", ".join(
["%s=%s" % (name, repr(getattr(self, name))) for name in names]))

return Bunch

# Example, create a Point class:
Point = bunchbuilder('Point', x=Required, y=Required, color='grey')
p = Point(x=4.3, y=0.3)
print p
p.x = 10


Delaney, Timothy

unread,
Oct 10, 2002, 8:15:09 PM10/10/02
to
> From: Alex Martelli [mailto:al...@aleax.it]
>
> http://tinyurl.com/1wq1

Wow - that's better than http://makeashorterlink.com/ ...

http://makeashorterlink.com/?O2BA22212

Tim Delaney

Alex Martelli

unread,
Oct 11, 2002, 2:31:48 AM10/11/02
to
On Friday 11 October 2002 02:25, Ian Bicking wrote:
> On Thu, 2002-10-10 at 16:29, Alex Martelli wrote:
> > Gerhard Häring wrote:
> > ...
> >
> > > Now who will come up with a neat example using metaclasses?
> >
> > I borrowed Guido's time machine to post about that:
> >
> > http://tinyurl.com/1wq1
>
> I should really learn to understand what's going on with metaclasses,
> but here's my attempt at similar functionality without metaclasses...

Pretty good! A metaclass that only defines __new__, as metaMetaBunch
does, is roughly equivalent to a factory function, such as yours. Even
then, of course, using a metaclass lets you use normal class syntax rather
than function-call syntax -- class XX(blah): etc rather than
XX = blah(etc) -- but metaclasses do offer more.

Say that you have metaMetaBunch in some library, and a new
requirement comes up -- in some cases you want repr(X), where
X is a class with that meta, to follow the usual convention, so that
eval(repr(X)) == X. If type(X) is forced upon you, i.e. if you do NOT
use a custom meta, you just can't -- you don't have any control on
the results of repr(X) nor on the comparison of two such classes.

With the custom metaclass, it becomes easy -- even if you need to
leave the original metaclass alone, you can reuse by inheritance, as
usual: subclass the meta and add __repr__ and __eq__ to it -- such
methods defined in the metaclass control behavior of the metaclass's
instances, i.e., the classes that instantiate it, just as for other cases
of class / instance relationships.


Alex

to

Ian Bicking

unread,
Oct 11, 2002, 3:06:28 AM10/11/02
to
On Fri, 2002-10-11 at 01:31, Alex Martelli wrote:
> Pretty good! A metaclass that only defines __new__, as metaMetaBunch
> does, is roughly equivalent to a factory function, such as yours. Even
> then, of course, using a metaclass lets you use normal class syntax rather
> than function-call syntax -- class XX(blah): etc rather than
> XX = blah(etc) -- but metaclasses do offer more.
>
> Say that you have metaMetaBunch in some library, and a new
> requirement comes up -- in some cases you want repr(X), where
> X is a class with that meta, to follow the usual convention, so that
> eval(repr(X)) == X. If type(X) is forced upon you, i.e. if you do NOT
> use a custom meta, you just can't -- you don't have any control on
> the results of repr(X) nor on the comparison of two such classes.

Yes, I hadn't realized it exactly, but I have run up against this
problem with a relational-object mapper that I wrote. In it, you
created a class, and in particular the class would have an instance
variable _columns. Then you'd run a function against it, and it would
create methods to fetch and set columns, with caching and all that. The
other mapper I'd looked at (MiddleKit) accomplished similar things, but
with code generation, which I didn't like.

But the code lacked a certain flexibility. In particular, I'd want to
have certain things triggered when some columns were set, or any number
of little hooks and the sort. Of course, using a more conventional
inheritance structure, you'd do something like:

class MyTable(AutoGeneratedTable):

def setName(self, value):
# some action that you want triggered when the name is changed...
AutoGeneratedTable.setName(self, value)

But I couldn't do that, since there was no parent class. Instead I had
to create two methods for every setter or getter, and if I wanted to
customize I'd use "self.reallyTrulySetName(value)" instead of
"AutoGeneratedTable.setName(self, value)". Okay, not
reallyTrulySetName, but something like that.

Anyway, it still worked, but as I kept adding more features the builder
got more complicated -- never overwhelming, but I think from seeing this
that a metaclass could accomplish the same thing in a more elegant
fashion. Especially since this is a small but essential piece of code
to much of my programming, I'm interested in keeping it tight -- I never
felt as comfortable with MiddleKit, which introduced a toolchain into my
code where otherwise I had none.

Ian

Alex Martelli

unread,
Oct 11, 2002, 4:21:21 AM10/11/02
to
Aahz wrote:

> In article <lkk7kq5...@pc150.maths.bris.ac.uk>,
> Michael Hudson <m...@python.net> wrote:
>>
>>Not sure it's useful, but it was fun :)
>
> You're sick. Go sit over by the Martellibot.

Not sure I'm all that happy to sit near somebody who's
busy implementing such systematic typechecking...!!!-)


Alex

Michael Hudson

unread,
Oct 11, 2002, 6:44:15 AM10/11/02
to al...@aleax.it
Alex Martelli <al...@aleax.it> writes:

The only reason I did that was because you had to write *something* on
the right hand side of "attr = " (really! and because I wondered how
easy it would be).

Your example[0] using defaults is nicer.

Cheers,
M.

[0] I don't know how I failed to find that; I was sure I'd seen
something like what I was doing before. I tried hunting around on
google, but didn't see it.

--
> With Python you can start a thread, but you can't stop it. Sorry.
> You'll have to wait until reaches the end of execution.
So, just the same as c.l.py, then?
-- Cliff Wells & Steve Holden, comp.lang.python

Kragen Sitaker

unread,
Oct 13, 2002, 1:35:54 PM10/13/02
to
Andrew Koenig <a...@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

0 new messages