Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if `__init_subclass__` has been overridden in a subclass?

Normally in Python, it is possible to detect whether a method has been overridden in a subclass using the following technique:

>>> class Foo:
...     def mymethod(self): pass
...
>>> class Bar(Foo): pass
...
>>> Bar.mymethod is Foo.mymethod 
True

The expression Bar.mymethod is Foo.mymethod will evaluate to True if the method from Foo has not been overridden in Bar, but will evaluate to False if the method has been overridden in Bar. This technique works well with dunder methods inherited from object, as well as non-dunder methods:

>>> Bar.__new__ is Foo.__new__
True
>>> Bar.__eq__ is Foo.__eq__
True

We can formalise this logic in a function like so:

def method_has_been_overridden(superclass, subclass, method_name):
    """
    Return `True` if the method with the name `method_name`
    has been overridden in the subclass
    or an intermediate class in the method resolution order
    """
    if not issubclass(subclass, superclass):
        raise ValueError(
            "This function only makes sense if `subclass` is a subclass of `superclass`"
        )
    subclass_method = getattr(subclass, method_name)
    if not callable(method):
        raise ValueError(f"'{subclass.__name__}.{method_name}' is not a method")
    return subclass_method is not getattr(superclass, method_name, object())

However, this technique fails when it comes to two methods: __init_subclass__ and __subclasshook__:

>>> class Foo: pass
...
>>> class Bar(Foo): pass
...
>>> Bar.__init_subclass__ is Foo.__init_subclass__
False
>>> Bar.__subclasshook__ is Foo.__subclasshook__
False

And, for an even more perplexing example:

>>> type.__init_subclass__ is type.__init_subclass__
False

I have two questions:

  1. Why does this technique fail with these methods, and these methods only? (I have not been able to find any other examples where this technique fails -- but if there are any, I'd love to know about them!)
  2. Is there an alternative technique that can be used to detect if __init_subclass__ or __subclasshook__ have been defined in a subclass after being left undefined in the superclass?
like image 403
Alex Waygood Avatar asked Oct 31 '21 13:10

Alex Waygood


1 Answers

__init_subclass__ is special-cased to be a class method, whether you decorate it with classmethod or not. Just like Foo().mymethod returns a new method instance every time you access the attribute via a class instance, Foo.__init_subclass__ produces a new instance method every time you access the attribute via the class itself.

__subclasshook__, on the other hand, must be declared as a class method to work properly. It is not assumed to be a class method if you define it as a simple function/instance method.

like image 187
chepner Avatar answered Nov 15 '22 01:11

chepner