can't rebind magic methods

M

Michael Tobis

I'd appreciate an explanation of why this doesn't work and any
workarounds.

It's not a showstopper, but I'd like to pseudo-inherit a bunch of magic
methods from an attribute, and would prefer to abstract the definitions
into a loop rather than write them all out.

thanks
mt


###########
import new

class myint(object):

def __init__(self,val):
self.val = int(val)
def mystr(self):
return self.val.__str__()
self.__str__ = new.instancemethod(mystr,self,mint) #doesn't
work

self.str = new.instancemethod(mystr,self,mint)

"""

# this works



def __str__(self):

return self.val.__str__()

"""

if __name__ == "__main__":
a = myint(3)
b = a
a.val = 42
print b # want "42"

print b.str() # works
 
A

Alex Martelli

Michael Tobis said:
I'd appreciate an explanation of why this doesn't work and any
workarounds.

Special methods are looked up (for their automatic use) on the type, not
the instance. The workaround is therefore to set them on the type, too:
class myint(object):

def __init__(self,val):
self.val = int(val)
def mystr(self):
return self.val.__str__()
self.__str__ = new.instancemethod(mystr,self,mint) #doesn't
work

What you need is to have *** as myint.__str__ *** the method you want to
get called. If you have a bunch of special methods that you want to
ensure delegate to self.val.(samemethod), you don't have to write them
out -- you can assign them as attributes of myint, with a simple loop of
setattr right after the class statement, or with a custom metaclass, or
whatever you find handiest.

For example (warning, untested code):

def _setdelegate(cls, delegate_cls, name):
method = getattr(delegate_cls, name)
def f(self, *a, **k): return method(self.val, *a, **k)
f.func_name = name
setattr(cls, name, f)

class myint(object):
def __init__(self, val):
self.val = int(val)

for spec in 'str repr hash hex oct'.split():
_setdelegate(myint, '__%s__' % spec)

This may be worth refactoring into a custom metaclass if you need more
than one class like this myint, but that's quite a side issue.


Alex
 
M

Michael Tobis

Thanks! Only a minor correction: the last line should be

_setdelegate(myint, int,'__%s__' % spec)

The business with f.func_name struck me as unnecessary, but I was quite
wrong.

This was an interesting exercise for me. Thanks.

Michael
 
M

Michael Tobis

Still a bit confused actually. Any explanation of the following?

mt

####

def getf(method,name):
def f(self, *a, **k): return method(self.val, *a, **k)
f.func_name = name
return f

class myint(object):
def __init__(self, val):
self.val = int(val)

for spec in 'str repr hash hex oct pow add'.split():
name = '__%s__' % spec
method = getattr(int, name)

# comment this out to get the non-working case

setattr(myint,name,getf(method,name)) # works


# uncomment three lines to get the non-working case
# raises TypeError: "expected 1 arguments, got 0" at method
invocation
# why isn't this equivalent to the setattr above?

# def f(self, *a, **k): return method(self.val, *a, **k)

# f.func_name = name

# setattr(myint,name,f) # doesn't work


if __name__ == "__main__":
a = myint(42)
print a
print oct(a)
 
A

Alex Martelli

Michael Tobis said:
Still a bit confused actually. Any explanation of the following?

I believe the problem you're having is with WHEN a name is looked up --
which tends to be 'as late as possible, but no later'.
def getf(method,name):
def f(self, *a, **k): return method(self.val, *a, **k)
f.func_name = name
return f

Here, 'method' is looked up at each execution of f -- but each instance
of f is separate and has a separate closure, so 'method' is what it's
worth in THAT call to getf (as desired).

for spec in 'str repr hash hex oct pow add'.split():
name = '__%s__' % spec
method = getattr(int, name)

Here, we're rebinding method in the same scope at each pass; in the end,
it's left bound to the last value it had -- the getaddr of 'add' in int.
# comment this out to get the non-working case

setattr(myint,name,getf(method,name)) # works

# uncomment three lines to get the non-working case
# raises TypeError: "expected 1 arguments, got 0" at method
invocation
# why isn't this equivalent to the setattr above?

Because it's looking up 'method' in the scope of the "def f" -- and in
that scope, at the time f executes, the loop is over, so name 'method'
is always bound to 'getattr(int, "add")'.
# def f(self, *a, **k): return method(self.val, *a, **k)

# f.func_name = name

# setattr(myint,name,f) # doesn't work

HTH...


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,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top