Search code examples
pythonmethodsabstract-classdecorator

How to get in a superclass full names of all methods in subclasses that start with the same two letters in python


I have number of subclasses all of which have number of methods whose names start with "on.." eg.:

def on_index(self):
    raise NotImplementedError()

def on_spiking_intent(self):
    raise NotImplementedError()

def on_no_option(self):
    raise NotImplementedError()

I would like to write a method in the superclass which prints a list of all the "on..." methods of its subclasses and specifies to which subclass the method belongs.

I guess this get_all_on_methods method need not be in the superclass, but it made sense to me to put it there as only the superclass unites the subclasses.

How should I go about this? I was thinking about somehow using decorators like putting

@onmethod

in front of every "on..." method or also somehow using an abstract class. But I actually don't know.


Solution

  • You can iterate over the subclasses of some class using the __subclasses__() method. You can check the namespace of the subclasses for attributes that start with "on_", so something to the effect of:

    class Ur:
        @classmethod
        def get_all_on_methods(cls):
            results = []
            for klass in cls.__subclasses__():
                for name, attribute in vars(klass).items():
                    if name.startswith("on_"):
                        results.append((attribute, klass))
            return results
    
    class Foo(Ur):
        def on_index(self):
            pass
        def some_method(self):
            pass
    
    class Bar(Ur):
        def on_spiking_intent(self):
            pass
        def another_method(self):
            pass
    

    This populates a list for illustration purposes, you could easily populate a dictionary or whatever you want instead.

    Also, this gets any class attribute, so you might want to check if it is a method by using:

    if callable(attribute) and name.startswith("on_"):
        ...
    

    But that is up to you.

    Here's the output you get in the above example:

    In [2]: Ur.get_on_methods()
    Out[2]:
    [(<function __main__.Foo.on_index(self)>, __main__.Foo),
     (<function __main__.Bar.on_spiking_intent(self)>, __main__.Bar)]