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
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
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/
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
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
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")
---------------------------------------------------------------------------
> 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
> 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
>>> 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...
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.
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)
I borrowed Guido's time machine to post about that:
(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
I think my MetaBunch at http://tinyurl.com/1wq1 is a better wheel though:-)
Alex
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
Wow - that's better than http://makeashorterlink.com/ ...
http://makeashorterlink.com/?O2BA22212
Tim Delaney
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
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
> 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
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
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