1

Consider the following example:

class A:
 def m():
  pass

class B(A):
 pass

And the following terminal output:

>>> b = B()
>>> b.m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> super(b.__class__, b).m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> b.m is super(b.__class__, b).m
False
>>> b.m == super(b.__class__, b).m
True

Why are they equal but not identical? Is a copy of the method made when it is inherited?

Are there better ways to test whether a child class has overridden a parent method?

Robin De Schepper
  • 4,942
  • 4
  • 35
  • 56
  • https://stackoverflow.com/questions/9436681/how-to-detect-method-overloading-in-subclasses-in-python Maybe this could help you – Alexander Lekontsev Jun 10 '20 at 10:52
  • I think there is some magic (probably descriptors) going on behind the scenes when binding methods to instances. That's why `A.m is B.m` is True, but `B.m is b.m` is False. – mportes Jun 10 '20 at 11:03
  • @AlexanderLekontsev most of the ansers there seem to be going for more roundabout solutions that require meddling with the classes. – Robin De Schepper Jun 10 '20 at 11:05
  • @myrmica is right! `super()` returns a bound version of the first method it finds going up the MRO list. Every function is a descriptor and to get the bound version of a function the descriptor is called. Every time the descriptor is called it produces a different bound instance of the function! – Robin De Schepper Jun 10 '20 at 11:07

2 Answers2

2

You can use the __dict__ attribute to check which methods and attributes have been overridden:

>>> class A:
...     def m():
...         pass
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     def m():
...         pass
...
>>> 'm' in A.__dict__
True
>>> 'm' in B.__dict__
False                    # not overridden
>>> 'm' in C.__dict__
True                     # overridden
mportes
  • 1,589
  • 5
  • 13
  • 1
    Well actually you put me on the right path to solving this: `B.m.__get__(b) is B.m.__get__(b)` will produce `False`, so I should test identity between the unbound versions of the classes. – Robin De Schepper Jun 10 '20 at 11:11
  • 1
    So `b.__class__.m is super(b.__class__, b).m.__func__` would be the solution I'm looking for. I hope you don't mind that I'm writing up my answer as well. Traversing the MRO is really key for my use case as I have complex and dynamic inheritance and composition – Robin De Schepper Jun 10 '20 at 11:23
1

Using super(b.__class__, b) produces an object that implements a __getattr__ method that will go up the __mro__ attribute starting at position 1 (skipping the current class) and look for the first class that has the specified attribute. It will then return that bound method. For a better explanation see this answer.

Knowing that all functions are also descriptors the following

class A:
 def m(self):
  pass

creates an object A with the attribute m which will be a function and descriptor. When you initialize an object a of class A, it will basically result in a.m = A.m.__get__(a) which produces the bound method that has a as the first argument self.

Now since super also retrieves bound methods what is being checked is the identity between 2 instances of A.m.__get__(a) producing your terminal output:

>>> A.m.__get__(a)
<bound method A.m of <__main__.A object at 0x...>>
>>> A.m.__get__(a) is A.m.__get__(a)
False

So 2 calls to the class descriptor m produce different bound instances and it is why the identity check fails. Instead you should test the identity of the functions that produced the bound methods. Luckily a bound method contains the __func__ attribute that returns the original function. So to lookup whether any instance's class has overridden an inherited function without knowing more than just the instance and name of the function you can do:

>>> a.__class__.m is super(a.__class__, a).m.__func__
True
Robin De Schepper
  • 4,942
  • 4
  • 35
  • 56