Search code examples
pythonpolymorphism

How to enforce method signature for child classes?


Languages like C#, Java has method overloads, which means if child class does not implement the method with exact signature will not overwrite the parent method.

How do we enforce the method signature in child classes in python? The following code sample shows that child class overwrites the parent method with different method signature:

>>> class A(object):
...   def m(self, p=None):
...     raise NotImplementedError('Not implemented')
... 
>>> class B(A):
...   def m(self, p2=None):
...     print p2
... 
>>> B().m('123')
123

While this is not super important, or maybe by design of python (eg. *args, **kwargs). I am asking this for the sake of clarity if this is possible.

Please Note:

I have tried @abstractmethod and the ABC already.


Solution

  • Below is a complete running example showing how to use a metaclass to make sure that subclass methods have the same signatures as their base classes. Note the use of the inspect module. The way I'm using it here it makes sure that the signatures are exactly the same, which might not be what you want.

    import inspect
    
    class BadSignatureException(Exception):
        pass
    
    
    class SignatureCheckerMeta(type):
        def __new__(cls, name, baseClasses, d):
            #For each method in d, check to see if any base class already
            #defined a method with that name. If so, make sure the
            #signatures are the same.
            for methodName in d:
                f = d[methodName]
                for baseClass in baseClasses:
                    try:
                        fBase = getattr(baseClass, methodName).__func__
                        if not inspect.getargspec(f) == inspect.getargspec(fBase):
                            raise BadSignatureException(str(methodName))
                    except AttributeError:
                        #This method was not defined in this base class,
                        #So just go to the next base class.
                        continue
    
            return type(name, baseClasses, d)
    
    
    def main():
    
        class A(object):
            def foo(self, x):
                pass
    
        try:
            class B(A):
                __metaclass__ = SignatureCheckerMeta
                def foo(self):
                    """This override shouldn't work because the signature is wrong"""
                    pass
        except BadSignatureException:
            print("Class B can't be constructed because of a bad method signature")
            print("This is as it should be :)")
    
        try:
            class C(A):
                __metaclass__ = SignatureCheckerMeta
                def foo(self, x):
                    """This is ok because the signature matches A.foo"""
                    pass
        except BadSignatureException:
            print("Class C couldn't be constructed. Something went wrong")
    
    
    if __name__ == "__main__":
        main()