6

I have the following simplified scheme:

class NetworkAnalyzer(object):
    def __init__(self):
       print('is _score_funct implemented?')

    @staticmethod
    def _score_funct(network):
        raise NotImplementedError

class LS(NetworkAnalyzer):
    @staticmethod
    def _score_funct(network):
        return network

and I am looking for what I should use instead of print('is _score_funct implemented?') in order to figure out if a subclass has already implemented _score_funct(network) or not.

Note: If there is a more pythonic/conventional way of structuring the code, I would also appreciate its mention. The reason I defined it this way is, some NetworkAnalyzer subclasses have _score_funct in their definition, and the ones that dont have it will have different initialization of variables although they will have the same structure

ozgeneral
  • 6,079
  • 2
  • 30
  • 45
  • 5
    abstract methods? https://stackoverflow.com/questions/4382945/abstract-methods-in-python – Jean-François Fabre Apr 18 '18 at 15:09
  • Well you could take a look at the AST and look if there is only a single raise statement and the only constant is `NotImplementedError`. – Wombatz Apr 18 '18 at 15:13
  • 1
    I would suggest you figure out the minimal behavior for each subclass and turn that into an abstract base class. See [the abc docs](https://docs.python.org/3/library/abc.html) . Research [Liskov Substitution Principle](https://stackoverflow.com/q/56860/2958070) . – Ben Apr 18 '18 at 15:16
  • Why not just _not_ implement the function in the base class and in `__init__` use `getattr()` and `callable()` to check if it exists and can be called? – Attie Apr 18 '18 at 15:16
  • I havent used abstract methods as I had little understanding of metaclasses. I will read the documentation to have it more conventional. By then, I have tried @Jean-FrançoisFabre's answer and it works – ozgeneral Apr 18 '18 at 15:18
  • I've answered with something unrelated to abstract methods. – Jean-François Fabre Apr 18 '18 at 15:18
  • @Attie because almost all subclasses of NetworkAnalyzer have this function(except very few) and I want it to be clear in the class definition, in case someone adds another subclass and forgets to specify this critical score function – ozgeneral Apr 18 '18 at 15:21

2 Answers2

4

Use an abstract base class and you won't be able to instantiate the class unless it implements all of the abstract methods:

import abc

class NetworkAnalyzerInterface(abc.ABC):
    @staticmethod
    @abc.abstractmethod
    def _score_funct(network):
        pass

class NetworkAnalyzer(NetworkAnalyzerInterface):
    def __init__(self):
        pass

class LS(NetworkAnalyzer):
    @staticmethod
    def _score_funct(network):
        return network

class Bad(NetworkAnalyzer):
    pass

ls = LS()   # Ok
b = Bad()   # raises TypeError: Can't instantiate abstract class Bad with abstract methods _score_funct
Duncan
  • 92,073
  • 11
  • 122
  • 156
  • Do I need the NetworkAnalyzerInterface in this example by any conventional reason or can I just bypass it by `NetworkAnalyzer(abc.ABC)` – ozgeneral Apr 18 '18 at 15:31
  • 1
    You can probably just make NetworkAnalyzer itself the abstract class in this case. I would just prefer to keep interface and implementation code separate although in the example I ended up with there's no implementation code. – Duncan Apr 18 '18 at 15:32
2

I'm not a metaclass/class specialist but here's a method that works in your simple case (not sure it works as-is in a complex/nested class namespace):

To check if the method was overridden, you could try a getattr on the function name, then check the qualified name (class part is enough using string partitionning):

class NetworkAnalyzer(object):
    def __init__(self):
        funcname = "_score_funct"
        d = getattr(self,funcname)
        print(d.__qualname__.partition(".")[0] == self.__class__.__name__)

if _score_funct is defined in LS, d.__qualname__ is LS._score_funct, else it's NetworkAnalyzer._score_funct.

That works if the method is implemented at LS class level. Else you could replace by:

d.__qualname__.partition(".")[0] != "NetworkAnalyzer"

Of course if the method is overridden with some code which raises an NotImplementedError, that won't work... This method doesn't inspect methods code (which is hazardous anyway)

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219