Oddity in Python's New-Style Classes

I thought I understood the differences between Old-Style Classes™ and New-Style Classes™ in Python. This surprised me, however:

python

example
class OldStyle: def __init__(self): def override_call(): print "I have overriden %s()" % self def override_x(): print "I have overriden %s.x()" % self self.__call__ = override_call self.x = override_x def __call__(self): print "I have not overridden %s()" % self def x(self): print "I have not overridden %s.x()" % self class NewStyle(object): def __init__(self): def override_call(): print "I have overriden %s()" % self def override_x(): print "I have overriden %s.x()" % self self.__call__ = override_call self.x = override_x def __call__(self): print "I have not overridden %s()" % self def x(self): print "I have not overridden %s.x()" % self >>> o = OldStyle() >>> o() I have overriden <__main__.OldStyle instance at 0xb6530>() >>> o.x() I have overriden <__main__.OldStyle instance at 0xb6530>.x() >>> n = NewStyle() >>> n() I have not overridden <__main__.NewStyle object at 0x89a10>() >>> n.x() I have overriden <__main__.NewStyle object at 0x89a10>.x()

(This is in Python 2.4.1, for reference.)

This seems to apply to any double-underscore (“dunder”) method, not just __call__, but no regular methods. The replacement method is stored in n.__call__, but Python’s usual instance-overrides-class semantics seem to be bypassed for dunder methods, in new-style classes only, with it reading n.__class__.__call__ directly.

There might be a very good reason for this, but it’s not documented in the introduction to new-style classes nor the What’s New for the version of Python they were introduced in. Perhaps the PEP explains why this happens, but I’ll have to re-read it after sleep and caffeination…