Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically assign special methods to objects but not classes in Python

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.

like image 284
Oliver Zheng Avatar asked Dec 29 '22 04:12

Oliver Zheng


1 Answers

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
like image 200
Alex Martelli Avatar answered Jan 19 '23 00:01

Alex Martelli