Issue with new-style classes and operators

Bengt Richter bokr at oz.net
Mon Nov 25 21:00:53 EST 2002


On Mon, 25 Nov 2002 18:36:45 -0500, "Terry Reedy" <tjreedy at udel.edu> wrote:

>
>"Jan Decaluwe" <jan at jandecaluwe.com> wrote in message
>news:3DE215D1.D9A3FEF9 at jandecaluwe.com...
>> Hi:
>>
>> I'm confused by the following behavior of new-style classes
>> and operators:
>
>I think you have a right to be.
>
>>
>>     class MyInt(object):
>>         def __init__(self, val):
>>             self.val = val
>>         def __getattr__(self, attr):
>>             return getattr(self.val, attr)
>
>> >>> a = MyInt(3)
>> >>> a.__add__(4)
>> 7
>
>This works because '__add__' is seen as just an attribute name,
>nothing special, which is resolved because of __gettattr__ booting
>attribute lookup to self.val.  As far as the interpreter is concerned,
>this might as well have been a.whatever(4).
>
>> >>> a + 4
>> Traceback (most recent call last):
>>   File "<stdin>", line 1, in ?
>> TypeError: unsupported operand types for +: 'MyInt' and 'int'
>
>Lookup of special methods invoked by syntax other than explicit
>instance.attr appears to have been changed for new classes by stopping
>the lookup process before calling __getattr__.
>
>This appears to contradict the current new-class doc at
>http://python.org/2.2/descrintro.html
>
>"Note that while in general operator overloading works just as for
>classic classes, there are some differences. "
>
>Which are...?
>
>"(The biggest one is the lack of support for __coerce__; new-style
>classes should always use the new-style numeric API, which passes the
>other operand uncoerced to the __add__ and __radd__ methods, etc.) "
>
>what else?  but then
>
>"There's a new way of overriding attribute access.  The __getattr__
>hook, if defined, works the same way as it does for classic classes:
>it is only called if the regular way of searching for the attribute
>doesn't find it.  But you can now also override __getattribute__, a
>new operation that is called for *all* attribute references."
>
>But you found a difference in __getattr__.  Contrary to inplication of
>doc, __getattribute__ is also not called for a+4.  (Not a big loss
>since attempt to access any attribute, including .__dict__, causes
>infinite loop.)  I opened sf bug report 643841.
>
>>I don't think is can be the intention. Or can it?
>
>Beats me.  We'll see.
>
Adding some print statements:

 Python 2.2.2 (#37, Oct 14 2002, 17:02:34) [MSC 32 bit (Intel)] on win32
 Type "help", "copyright", "credits" or "license" for more information.
 >>> class MyClassicInt:
 ...     def __init__(self, val):
 ...         self.val = val
 ...     def __getattr__(self, attr):
 ...         attv = getattr(self.val, attr)
 ...         print '--(old)--\n%s => %s\n%s\n--' % (
 ...             attr, attv, attv.__doc__ )
 ...         return getattr(self.val, attr)
 ...
 >>> class MyInt(object):
 ...     def __init__(self, val):
 ...         self.val = val
 ...     def __getattr__(self, attr):
 ...         attv = getattr(self.val, attr)
 ...         print '--(new)--\n%s => %s\n%s\n--' % (
 ...             attr, attv, attv.__doc__ )
 ...         return getattr(self.val, attr)
 ...
 >>> ci = MyClassicInt(3)
 >>> ci.__add__(4)
 --(old)--
 __add__ => <method-wrapper object at 0x007D7BB0>
 x.__add__(y) <==> x+y
 --
 7
 >>> ci + 4
 --(old)--
 __coerce__ => <method-wrapper object at 0x007D7BB0>
 x.__coerce__(y) <==> coerce(x, y)
 --
 7
 >>> ni = MyInt(3)
 >>> ni.__add__(4)
 --(new)--
 __add__ => <method-wrapper object at 0x007D7570>
 x.__add__(y) <==> x+y
 --
 7
 >>> ni + 4
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: unsupported operand types for +: 'MyInt' and 'int'
 >>>

Either way, the code looks different for the two cases:

 >>> from ut.miscutil import disev
 >>> disev('ci.__add__(4)')
           0 SET_LINENO               0
           3 LOAD_NAME                0 (ci)
           6 LOAD_ATTR                1 (__add__)
           9 LOAD_CONST               0 (4)
          12 CALL_FUNCTION            1
          15 RETURN_VALUE
 >>> disev('ci + 4')
           0 SET_LINENO               0
           3 LOAD_NAME                0 (ci)
           6 LOAD_CONST               0 (4)
           9 BINARY_ADD
          10 RETURN_VALUE

But apparently BINARY_ADD reacts differently to being supplied
with classic vs new style object as an argument together with int.

Apparently, with a classic object it tries to get a __coerce__ method.

Note that what comes back from getting the __add__ attribute from an object
*instance* is a bound method that acts like a callable object, and since
the attribute is gotten from self.val, which is an integer, the associated object
is an integer, even though the __getattr__ was supposedly a method of MyInt.
Hence passing the bound method an integer to add naturally works. An this works
the same for both types, when the attribute access is explicit.

When the __coerce__ method is returned, it is similarly bound to the self.val
value which is an int instance. So the coerce works.

If there is an actual __add__ method in the class, it is found before __getattr__
is tried for the new-style class:

 >>> def add(x,y):
 ...     print 'adding %s + %s' % (`x`,`y`)
 ...     return x.val + y
 ...
 >>> MyInt.__add__ = add
 >>> ni + 4
 adding <__main__.MyInt object at 0x007D7BB0> + 4
 7

(Note that __repr__ is found without resort to __getattr__ above).

But binary add works differently for the old:
 >>> MyClassicInt.__add__ = add
 >>> ci + 5
 --(old)--
 __coerce__ => <method-wrapper object at 0x00839730>
 x.__coerce__(y) <==> coerce(x, y)
 --
 8

The coerce succeeded and MyInt.__add__ never got involved.


Of course, explicitly you get there:

 >>> ci.__add__(6)
 --(old)--
 __repr__ => <method-wrapper object at 0x0083AC90>
 x.__repr__() <==> repr(x)
 --
 adding 3 + 6
 9

And of course,  we mustn't be fooled by the names:

 >>> b = MyInt('bbb')
 >>> b.__add__('x')
 adding <__main__.MyInt object at 0x00839730> + 'x'
 'bbbx'
 >>> b + 'x'
 adding <__main__.MyInt object at 0x00839730> + 'x'
 'bbbx'
 >>> del MyInt.__add__
 >>> b + 'x'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: unsupported operand types for +: 'MyInt' and 'str'
 >>> b.__add__('x')
 --(new)--
 __add__ => <method-wrapper object at 0x00839D80>
 x.__add__(y) <==> x+y
 --
 'bbbx'

Regards,
Bengt Richter



More information about the Python-list mailing list