views:

196

answers:

3

I would like to do the following:

class A(object): pass

a = A()
a.__int__ = lambda self: 3

i = int(a)

Unfortunately, this throws:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string or a number, not 'A'

This only seems to work if I assign the "special" method to the class A instead of an instance of it. Is there any recourse?

One way I thought of was:

def __int__(self):
    # No infinite loop
    if type(self).__int__.im_func != self.__int__.im_func:
        return self.__int__()
    raise NotImplementedError()

But that looks rather ugly.

Thanks.

+4  A: 

Python always looks up special methods on the class, not the instance (except in the old, aka "legacy", kind of classes -- they're deprecated and have gone away in Python 3, because of the quirky semantics that mostly comes from looking up special methods on the instance, so you really don't want to use them, believe me!-).

To make a special class whose instances can have special methods independent from each other, you need to give each instance its own class -- then you can assign special methods on the instance's (individual) class without affecting other instances, and live happily ever after. If you want to make it look like you're assigning to an attribute the instance, while actually assigning to an attribute of the individualized per-instance class, you can get that with a special __setattr__ implementation, of course.

Here's the simple case, with explicit "assign to class" syntax:

>>> class Individualist(object):
...   def __init__(self):
...     self.__class__ = type('GottaBeMe', (self.__class__, object), {})
... 
>>> a = Individualist()
>>> b = Individualist()
>>> a.__class__.__int__ = lambda self: 23
>>> b.__class__.__int__ = lambda self: 42
>>> int(a)
23
>>> int(b)
42
>>> 

and here's the fancy version, where you "make it look like" you're assigning the special method as an instance attribute (while behind the scene it still goes to the class of course):

>>> class Sophisticated(Individualist):
...   def __setattr__(self, n, v):
...     if n[:2]=='__' and n[-2:]=='__' and n!='__class__':
...       setattr(self.__class__, n, v)
...     else:
...       object.__setattr__(self, n, v)
... 
>>> c = Sophisticated()
>>> d = Sophisticated()
>>> c.__int__ = lambda self: 54
>>> d.__int__ = lambda self: 88
>>> int(c)
54
>>> int(d)
88
Alex Martelli
Thanks. This looks cleaner than doing Thomas's approach, since I want to have every special method there is. Speed should increase too. I'll just override the __new__ in the base class then.
MTsoul
@MTsoul, you can do it with `__new__` if you wish, of course, but, as I've shown in code samples by editing my answer, `__init__` is fine too (since in it you *can** reassign `self.__class__` if you want).
Alex Martelli
How does the code sample work if both c and d change their class attribute? Don't all instances of a class share the same class attribute? That's why I wanted to use new, and create a new class for each object that wanted its own special methods.
MTsoul
@MTsoul, `assert c.__class__ is not d.__class__`: that's because `__init__` (which `Sophisticated` inherits from `Individualist`) has a statement `self.__class__ = ...` which makes and sets a pristine new class object for each member. `__new__` could do it just as well as `__init__`, but I prefer to use `__init__` where feasible (like here!) and `__new__` only for jobs `__init__` can't do.
Alex Martelli
Ah that is brilliant, indeed! Thanks a bunch, Alex.
MTsoul
+2  A: 

The only recourse that works for new-style classes is to have a method on the class that calls the attribute on the instance (if it exists):

class A(object):
    def __int__(self):
        if '__int__' in self.__dict__:
            return self.__int__()
        raise ValueError

a = A()
a.__int__ = lambda: 3
int(a)

Note that a.__int__ will not be a method (only functions that are attributes of the class will become methods) so self is not passed implicitly.

Thomas Wouters
I should have added that you don't have to call the instance attribute you call from the special method the same as the special method -- in fact, you should probably not do that.
Thomas Wouters
A: 

I have nothing to add about the specifics of overriding __int__. But I noticed one thing about your sample that bears discussing.

When you manually assign new methods to an object, "self" is not automatically passed in. I've modified your sample code to make my point clearer:

class A(object): pass

a = A()
a.foo = lambda self: 3

a.foo()

If you run this code, it throws an exception because you passed in 0 arguments to "foo" and 1 is required. If you remove the "self" it works fine.

Python only automatically prepends "self" to the arguments if it had to look up the method in the class of the object and the function it found is a "normal" function. (Examples of "abnormal" functions: class methods, callable objects, bound method objects.) If you stick callables in to the object itself they won't automatically get "self".

If you want self there, use a closure.

Larry Hastings