34

I have a class that is a super-class to many other classes. I would like to know (in the __init__() of my super-class) if the subclass has overridden a specific method.

I tried to accomplish this with a class method, but the results were wrong:

class Super:
   def __init__(self):
      if self.method == Super.method:
         print 'same'
      else:
         print 'different'
     
   @classmethod
   def method(cls):
      pass

class Sub1(Super):
   def method(self):
      print 'hi'
  
class Sub2(Super):
   pass

Super() # should be same
Sub1() # should be different
Sub2() # should be same

>>> same
>>> different
>>> different

Is there any way for a super-class to know if a sub-class has overridden a method?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Brian
  • 343
  • 1
  • 3
  • 5
  • 2
    Could you say a few words about *why* you'd want to do that? – NPE Feb 24 '12 at 19:21
  • Nothing comes to mind immediately, but if you include docstrings for your methods, then they get overwritten when the method is overridden. You should therefore be able to track it with `MyClass.methodname.__doc__`. But I find this solution to be very hack, which is why I'm not posting it as an answer – inspectorG4dget Feb 24 '12 at 19:21
  • 1
    Basically I want to have the super-class have these methods defined as just 'pass' and have a separate method (actually __init__) call these functions. I would like to have init put in print statements that say we are starting or ending the function, but if that function is still null, these statements look out of place, and I would like to do away with them. – Brian Feb 24 '12 at 19:52
  • 3
    @Brian Why not make these `print` statements part of the methods _in the subclass_ instead of in the superclass? – Katriel Feb 24 '12 at 20:04
  • 1
    Part of the goal of the super-class is to remove the need to add these common items to the sub-classes. There is one super-class, and many sub-classes. – Brian Feb 24 '12 at 22:19

10 Answers10

20

If you want to check for an overridden instance method in Python 3, you can do this using the type of self:

class Base:
    def __init__(self):
        if type(self).method == Base.method:
            print('same')
        else:
            print('different')

    def method(self):
        print('Hello from Base')


class Sub1(Base):
    def method(self):
        print('Hello from Sub1')


class Sub2(Base):
    pass

Now Base() and Sub2() should both print "same" while Sub1() prints "different". The classmethod decorator causes the first parameter to be bound to the type of self, and since the type of a subclass is by definition different to its base class, the two class methods will compare as not equal. By making the method an instance method and using the type of self, you're comparing a plain function against another plain function, and assuming functions (or unbound methods in this case if you're using Python 2) compare equal to themselves (which they do in the C Python implementation), the desired behavior will be produced.

Paul
  • 201
  • 2
  • 2
  • 1
    This was the clearest way, with `type(self)` and no tricks with `__func__` needed. Thanks. – Andrew Oct 21 '20 at 07:14
18

It seems simplest and sufficient to do this by comparing the common subset of the dictionaries of an instance and the base class itself, e.g.:

def detect_overridden(cls, obj):
  common = cls.__dict__.keys() & obj.__class__.__dict__.keys()
  diff = [m for m in common if cls.__dict__[m] != obj.__class__.__dict__[m]]
  print(diff)

def f1(self):
  pass

class Foo:
  def __init__(self):
    detect_overridden(Foo, self)
  def method1(self):
    print("Hello foo")
  method2=f1

class Bar(Foo):
  def method1(self):
    print("Hello bar")
  method2=f1 # This is pointless but not an override
#  def method2(self):
#    pass

b=Bar()
f=Foo()

Runs and gives:

['method1']
[]
Flexo
  • 87,323
  • 22
  • 191
  • 272
17

You can use your own decorator. But this is a trick and will only work on classes where you control the implementation.

def override(method):
  method.is_overridden = True
  return method

class Super:
   def __init__(self):
      if hasattr(self.method, 'is_overridden'):
         print 'different'
      else:
         print 'same'
   @classmethod
   def method(cls):
      pass

class Sub1(Super):
   @override
   def method(self):
      print 'hi'

class Sub2(Super):
   pass

Super() # should be same
Sub1() # should be different
Sub2() # should be same

>>> same
>>> different
>>> same
wberry
  • 18,519
  • 8
  • 53
  • 85
  • 11
    I went in just a slightly different direction, decorating my method with @native, and assuming the sub-classes will not. I think I'll do it this way since I don't have much control over the sub-classes. It works well, thank you for your help! – Brian Feb 24 '12 at 20:46
  • 3
    Or even simpler put `Super.method._original = True` is the body of `Super()` class, and then check whether `hasattr(self.method, '_original')`. – Ben Usman Sep 06 '17 at 22:52
9

In reply to answer https://stackoverflow.com/a/9437273/1258307, since I don't have enough credits yet to comment on it, it will not work under python 3 unless you replace im_func with __func__ and will also not work in python 3.4(and most likely onward) since functions no longer have the __func__ attribute, only bound methods.

EDIT: Here's the solution to the original question(which worked on 2.7 and 3.4, and I assume all other version in between):

    class Super:
        def __init__(self):
            if self.method.__code__ is Super.method.__code__:
                print('same')
            else:
                print('different')

        @classmethod
        def method(cls):
            pass

    class Sub1(Super):
        def method(self):
            print('hi')

    class Sub2(Super):
        pass

    Super() # should be same
    Sub1() # should be different
    Sub2() # should be same

And here's the output:

same
different
same
Community
  • 1
  • 1
s0undt3ch
  • 755
  • 6
  • 12
  • Just for note: in cython (at least in python 3.4), `self.method.__code__` is absent, but works in clean Python. By some reason, `__code__` is missing in `dir(self.method)` – socketpair Oct 15 '15 at 20:54
  • `self.method` is a bound method, meaning it'sn't the actual method. The actual function is stored in `__func__`. It's described in more detail here (https://docs.python.org/3/tutorial/classes.html#odds-and-ends). – GeeTransit Aug 22 '19 at 14:16
  • 1
    You can use `type(self).method` and access `__code__` from there too. – GeeTransit Aug 22 '19 at 14:17
  • FYI `__code__` can change just due to the docstring (even if the actual code implementation is identical). To avoid this, you'd want `__code__.co_code` – Addison Klinke Nov 15 '22 at 19:47
4

I'm using the following method to determine if a given bound method is overridden or originates from the parent class

class A():
    def bla(self):
        print("Original")


class B(A):
    def bla(self):
        print("Overridden")

class C(A):
    pass


def isOverriddenFunc(func):
    obj = func.__self__
    prntM = getattr(super(type(obj), obj), func.__name__)

    return func.__func__ != prntM.__func__


b = B()
c = C()

b.bla()
c.bla()

print(isOverriddenFunc(b.bla))
print(isOverriddenFunc(c.bla))

Result:

Overridden
Original
True
False

Of course, for this to work, the method must be defined in the base class.

4

You can compare whatever is in the class's __dict__ with the function inside the method you can retrieve from the object - the "detect_overriden" functionbellow does that - the trick is to pass the "parent class" for its name, just as one does in a call to "super" - else it is not easy to retrieve attributes from the parentclass itself instead of those of the subclass:

# -*- coding: utf-8 -*-
from types import FunctionType

def detect_overriden(cls, obj):
    res = []
    for key, value in cls.__dict__.items():
        if isinstance(value, classmethod):
            value = getattr(cls, key).im_func
        if isinstance(value, (FunctionType, classmethod)):
            meth = getattr(obj, key)
            if not meth.im_func is  value:
                res.append(key)
    return res


# Test and example
class A(object):
    def  __init__(self):
        print detect_overriden(A, self)

    def a(self): pass
    @classmethod
    def b(self): pass
    def c(self): pass

class B(A):
    def a(self): pass
    #@classmethod
    def b(self): pass

edit changed code to work fine with classmethods as well: if it detects a classmethod on the parent class, extracts the underlying function before proceeding.

-- Another way of doing this, without having to hard code the class name, would be to follow the instance's class ( self.__class__) method resolution order (given by the __mro__ attribute) and search for duplicates of the methods and attributes defined in each class along the inheritance chain.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
3

You can also check if something is overridden from its parents, without knowing any of the classes involved using super:

class A:
    def fuzz(self):
        pass

class B(A):
    def fuzz(self):
        super().fuzz()

class C(A):
    pass
>>> b = B(); c = C()
>>> b.__class__.fuzz is super(b.__class__, b).fuzz.__func__
False
>>> c.__class__.fuzz is super(c.__class__, c).fuzz.__func__
True

See this question for some more nuggets of information.

A general function:

def overrides(instance, function_name):
    return getattr(instance.__class__, function_name) is not getattr(super(instance.__class__, instance), function_name).__func__

>>> overrides(b, "fuzz")
True
>>> overrides(c, "fuzz")
False
Robin De Schepper
  • 4,942
  • 4
  • 35
  • 56
  • sorry sir, I think you flipped the output when you turned your code into a general function. I am getting the opposite outputs when I copy-paste your code: `overrides(b, "fuzz")` returns False (should be True) and `overrides(c, "fuzz")` returns True (should be False) – Pablo Aug 15 '20 at 16:54
  • In python 2.7 the generic function should be like this: `getattr(instance.__class__, function_name).__func__ is not getattr(super(instance.__class__, instance), function_name).__func__` otherwise you are comparing two different things and they are always not equal – pawel Aug 21 '20 at 01:42
  • @pawel I don't immediately see the difference with my function? – Robin De Schepper Aug 22 '20 at 09:18
  • @RobinDeSchepper you are missing `__func__` on the left side of the comparison. – pawel Aug 24 '20 at 00:43
  • But the object on the left is already an unbound version of the function, it would throw `AttributeError: 'function' object has no attribute '__func__'`. I don't have Python 2 (nobody should anymore, it's been killed by its makers) so I can't assess the situation there. – Robin De Schepper Aug 24 '20 at 08:00
2

You can check to see if the function has been overridden by seeing if the function handle points to the Super class function or not. The function handler in the subclass object points either to the Super class function or to an overridden function in the Subclass. For example:

class Test:
    def myfunc1(self):
        pass
    def myfunc2(self):
        pass

class TestSub(Test):
    def myfunc1(self):
        print('Hello World')

>>> test = TestSub()
>>> test.myfunc1.__func__ is Test.myfunc1
False
>>> test.myfunc2.__func__ is Test.myfunc2
True

If the function handle does not point to the function in the Super class, then it has been overridden.

andrewmkeller
  • 91
  • 1
  • 8
0

Not sure if this is what you're looking for but it helped me when I was looking for a similar solution.

class A:
    def fuzz(self):
        pass

class B(A):
    def fuzz(self):
        super().fuzz()

assert 'super' in B.__dict__['fuzz'].__code__.co_names
Paul
  • 1,192
  • 1
  • 11
  • 23
-1

The top-trending answer and several others use some form of Sub.method == Base.method. However, this comparison can return a false negative if Sub and Base do not share the same import syntax. For example, see discussion here explaining a scenario where issubclass(Sub, Base) -> False.

This subtlety is not apparent when running many of the minimal examples here, but can show up in a more complex code base. The more reliable approach is to compare the method defined in the Sub.__bases__ entry corresponding to Base because __bases__ is guaranteed to use the same import path as Sub

import inspect 

def method_overridden(cls, base, method):
   """Determine if class overriddes the implementation of specific base class method

   :param type cls: Subclass inheriting (and potentially overriding) the method
   :param type base: Base class where the method is inherited from
   :param str method: Name of the inherited method
   :return bool: Whether ``cls.method != base.method`` regardless of import
       syntax used to create the two classes
   :raises NameError: If ``base`` is not in the MRO of ``cls``
   :raises AttributeError: If ``base.method`` is undefined
   """

   # Figure out which base class from the MRO to compare against
   base_cls = None
   for parent in inspect.getmro(cls):
       if parent.__name__ == base.__name__:
           base_cls = parent
           break
   if base_cls is None:
       raise NameError(f'{base.__name__} is not in the MRO for {cls}')

   # Compare the method implementations
   return getattr(cls, method) != getattr(base_cls, method)
Addison Klinke
  • 1,024
  • 2
  • 14
  • 23
  • The above answer in general is not correct. The issue described in the link is for a situation when the two classes have the same code but are loaded twice - these are **different** classes. Also in the above example you are using the name to identify the parent class but it's possible to have two different classes with the same name so this isn't bulletproof. – joshlk Feb 02 '23 at 10:59
  • @joshlk doesn't it depend how you define "different" classes? I think a lot of people would consider them the same if the code is the same – Addison Klinke Feb 03 '23 at 21:10
  • They are different classes by how Python defines a class. However I understand thats its a gotcha and is annoying to deal with – joshlk Feb 07 '23 at 15:29