lazy arithmetic

P

pianomaestro

# This is what I have in mind:

class Item(object):
def __add__(self, other):
return Add(self, other)

class Add(Item):
def __init__(self, a, b):
self.a = a
self.b = b

a = Item()
b = Item()

c = a+b

# Now, I am going absolutely crazy with this idea
# and using it in a big way. So I'm looking at
# automating the process. As a first step,
# I thought maybe this would work:

class Item(object):
pass

class Add(Item):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

Item.__add__ = Add

x = Item()
y = Item()

print x, y

c = x+y

# This time, the Add constructor gets only the first two arguments:
"self" and "y".
# So, what happened to "x" ? Is this some kind of property voodoo going
on ?

# Simon.
 
G

Gabriel Genellina

At said:
# This is what I have in mind:

class Item(object):
def __add__(self, other):
return Add(self, other)

And this works fine... why make thinks complicated?
# Now, I am going absolutely crazy with this idea
# and using it in a big way. So I'm looking at
# automating the process. As a first step,
# I thought maybe this would work:

class Item(object):
pass

class Add(Item):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

Item.__add__ = Add

This doesn't make sense... __add__ should be a method, not a class...

x = Item()
y = Item()

print x, y

c = x+y

# This time, the Add constructor gets only the first two arguments:
"self" and "y".
# So, what happened to "x" ? Is this some kind of property voodoo going
on ?

x+y get translated to x.__add__(y)
x.__add__ is Add (the class), so Python calls Add, which means using
it as a constructor with y as the (only) argument.
So you see in the __init__ method: self=a new instance of Add, a=y,
b=None (default, as only one argument was provided).

If you really want to "inject" __add__ into the Item class, you could
use a factory:

def AddFactory(a,b):
return Add(a,b)
Item.__add__ = AddFactory

(but as you've said yourself, that's a bit crazy...)


Gabriel Genellina
Softlab SRL





__________________________________________________
Preguntá. Respondé. Descubrí.
Todo lo que querías saber, y lo que ni imaginabas,
está en Yahoo! Respuestas (Beta).
¡Probalo ya!
http://www.yahoo.com.ar/respuestas
 
S

Simon Forman

# This is what I have in mind:

class Item(object):
def __add__(self, other):
return Add(self, other)

class Add(Item):
def __init__(self, a, b):
self.a = a
self.b = b

a = Item()
b = Item()

c = a+b

# Now, I am going absolutely crazy with this idea
# and using it in a big way. So I'm looking at
# automating the process. As a first step,
# I thought maybe this would work:

class Item(object):
pass

class Add(Item):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

Item.__add__ = Add

x = Item()
y = Item()

print x, y

c = x+y

# This time, the Add constructor gets only the first two arguments:
"self" and "y".
# So, what happened to "x" ? Is this some kind of property voodoo going
on ?

# Simon.

Look at the signature for __add__:

__add__(self, other)

Now look at the signature for __init__:

__init__(self, a, b=None)

Now look at the output of your script:

|>>
<__main__.Item object at 0xb6faa4ac> <__main__.Item object at
0xb6faa3ec>
<__main__.Add object at 0xb6faa5ec> <__main__.Item object at
0xb6faa3ec> None


When python calls x.__add__ as a result of "x+y" it only passes it y as
'other', or, in this case, as 'a'. The 'self' arg gets filled in with
a brand new Add instance, because __add__ (rather than being a method
of Item) is really the class Add. (b will never be filled in using the
Add.__init__ method this way.)

Consider:

|>> x
<__main__.Item object at 0xb6f10f0c>
|>> x.__add__
<class '__main__.Add'>
|>> Badd = x.__add__
|>> Badd
<class '__main__.Add'>
|>> b = Badd(23, 18)
<__main__.Add object at 0xb6f10e8c> 23 18
|>> b
<__main__.Add object at 0xb6f10e8c>

Make more sense? :)

"Item.__add__ = Add" is a very strange thing to do, I'm not surprised
it didn't work.

HTH,
~Simon
 
P

pianomaestro

Gabriel said:
And this works fine... why make thinks complicated?

Yes, I agree it's simpler, and up until now that's the way
I always did it. Many Many times.
Now I am looking for a way to build ensembles of these lazy classes at
the meta-level.
(think abstract algebra).
This doesn't make sense... __add__ should be a method, not a class...

No, that's not true. It can be this callable:

def add(a, b):
return Add(a,b)
Item.__add__ = add

So my thinking was this: why can't I use a callable class ?
x+y get translated to x.__add__(y)

No that's not true at all. The self argument to __add__ ends
up being the Add instance, not the Item instance:

z=x+y is translated to z.__add__(y)

Simon.
 
P

pianomaestro

Simon said:
"Item.__add__ = Add" is a very strange thing to do, I'm not surprised
it didn't work.

Yes it is strange.
I also tried this even stranger thing:

class Item(object):
class __add__(object):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

:)

Simon.
 
S

Simon Forman

Yes, I agree it's simpler, and up until now that's the way
I always did it. Many Many times.
Now I am looking for a way to build ensembles of these lazy classes at
the meta-level.
(think abstract algebra).


No, that's not true. It can be this callable:

def add(a, b):
return Add(a,b)
Item.__add__ = add

But that's a function, not a class. When you assign add to an
attribute of Item it "magically" becomes a method of Item:

|>> add
<function add at 0xb6f3b95c>
|>> Item.__add__
<unbound method Item.add>
|>> x.__add__
<bound method Item.add of <__main__.Item object at 0xb6f3af2c>>

So it works fine.

So my thinking was this: why can't I use a callable class ?

When you assign a class to an attibute (be it __add__ or __banana__ or
anything else) the "magic" doesn't take place:

|>> Add
<class '__main__.Add'>
|>> Item.__banana__ = Add
|>> Item.__banana__
<class '__main__.Add'>


No that's not true at all. The self argument to __add__ ends
up being the Add instance, not the Item instance:

No, that's *exactly* true. "x+y" does mean "x.__add__(y)" and
"x.__add__" *is* the class Add, so you're really calling "Add(y)",
nothing more nor less.

x+y =>
x.__add__(y) =>
(x.__add__)(y) =>
Add(Y) =>
Hello new Add instance, here's a y for you... :)
z=x+y is translated to z.__add__(y)

Nope. Just plain wrong. Not sure why you'd think that.
(No offense intended.)

Peace,
~Simon F.
 
S

Simon Forman

Yes it is strange.
I also tried this even stranger thing:

class Item(object):
class __add__(object):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

:)

Simon.

That didn't work either did it? :)

~Simon F.
 
G

Gabriel G

At said:
No that's not true at all. The self argument to __add__ ends
up being the Add instance, not the Item instance:

z=x+y is translated to z.__add__(y)

Well, I deleted my response after I noticed that Simon Forman has
done it very well.
Just a note: print z after this line and compare with self inside
__init__ if you're still not convinced.



Gabriel Genellina
Softlab SRL





__________________________________________________
Preguntá. Respondé. Descubrí.
Todo lo que querías saber, y lo que ni imaginabas,
está en Yahoo! Respuestas (Beta).
¡Probalo ya!
http://www.yahoo.com.ar/respuestas
 
P

Peter Otten

# This is what I have in mind:

class Item(object):
def __add__(self, other):
return Add(self, other)

class Add(Item):
def __init__(self, a, b):
self.a = a
self.b = b

a = Item()
b = Item()

c = a+b

# Now, I am going absolutely crazy with this idea
# and using it in a big way. So I'm looking at
# automating the process. As a first step,
# I thought maybe this would work:

class Item(object):
pass

class Add(Item):
def __init__(self, a, b=None):
print self, a, b
self.a = a
self.b = b

Item.__add__ = Add

x = Item()
y = Item()

print x, y

c = x+y

# This time, the Add constructor gets only the first two arguments:
"self" and "y".
# So, what happened to "x" ? Is this some kind of property voodoo going
on ?

Indeed. Read up on descriptors:

http://users.rcn.com/python/download/Descriptor.htm

The following seems to work:

from new import instancemethod

class Add:
class __metaclass__(type):
def __get__(self, inst, cls):
if inst is None:
return cls
return instancemethod(cls, inst, cls)
def __init__(self, a, b=None):
self.a = a
self.b = b
def __str__(self):
return "%s(%s, %s)" % (self.__class__.__name__, self.a, self.b)

Add.__add__ = Add
print Add(1, 2) + 42

but makes the simple

def __add__(self, other):
return Add(self, other)

all the more attractive.

Peter
 
P

pianomaestro

Simon said:
But that's a function, not a class. When you assign add to an
attribute of Item it "magically" becomes a method of Item:

Yes, I am looking to understand this magic.
Sounds like I need to dig into these descriptor thingies (again).

(sound of brain exploding)..

Simon.
 
A

Alex Martelli

Yes, I am looking to understand this magic.
Sounds like I need to dig into these descriptor thingies (again).

(sound of brain exploding)..

It's really very simple: Python-coded functions (whether their def is in
a classbody or not) are descriptors (objects with a method named __get__
which binds the first argument); classes (and C-coded functions, etc)
are not descriptors (they have no method named __get__ etc etc). If you
insist, you can write a custom metaclass with a __get__ so that classes
instantiating it ARE descriptors, for example.


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top