Dynamically assign special methods to objects but not classes in Python

后端 未结 3 713
情书的邮戳
情书的邮戳 2020-12-18 10:07

I would like to do the following:

class A(object): pass

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

i = int(a)

Unfortunately, this throws:

相关标签:
3条回答
  • 2020-12-18 10:19

    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
    
    0 讨论(0)
  • 2020-12-18 10:37

    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.

    0 讨论(0)
  • 2020-12-18 10:41

    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.

    0 讨论(0)
提交回复
热议问题