Search code examples
pythonclassoopabstractabc

Abstract methods with specific arguments in Python


I implement abstract class with abc package. The program below shows no problems.

Is there any way to make it fail because abstract MyMethod did have an argument a but the implementation of 'MyMethod' in class Derivative didn't? So I would like specify not only methods in the interface class Base but also arguments of these methods.

import abc

#Abstract class
class Base(object):
    __metaclass__  = abc.ABCMeta

    @abc.abstractmethod
    def MyMethod(self, a):
        'MyMethod prints a'


class Derivative(Base)

    def MyMethod(self):
        print 'MyMethod'

Solution

  • The code below is copied from a proxy-class, which works analogous. It checks that all methods are present and that the method signatures are identical. The work is done in _checkImplementation(). Note the two lines starting with ourf and theirf; _getMethodDeclaration() translates the signatures into strings. Here I chose to require both to be exactly identical:

      @classmethod
      def _isDelegatableIdentifier(cls, methodName):
        return not (methodName.startswith('_') or methodName.startswith('proxy'))
    
    
    
      @classmethod
      def _getMethods(cls, aClass):
        names  = sorted(dir(aClass), key=str.lower)
        attrs  = [(n, getattr(aClass, n)) for n in names if cls._isDelegatableIdentifier(n)]
        return dict((n, a) for n, a in attrs if inspect.ismethod(a))
    
    
    
      @classmethod
      def _getMethodDeclaration(cls, aMethod):
        try:
          name = aMethod.__name__
          spec = inspect.getargspec(aMethod)
          args = inspect.formatargspec(spec.args, spec.varargs, spec.keywords, spec.defaults)
          return '%s%s' % (name, args)
        except TypeError, e:
          return '%s(cls, ...)' % (name)
    
    
    
      @classmethod    
      def _checkImplementation(cls, aImplementation):
        """
        the implementation must implement at least all methods of this proxy,
        unless the methods is private ('_xxxx()') or it is marked as a proxy-method
        ('proxyXxxxxx()'); also check signature (must be identical).
        @param aImplementation: implementing object
        """
        missing = {}
    
        ours   = cls._getMethods(cls)
        theirs = cls._getMethods(aImplementation)
    
        for name, method in ours.iteritems():
            if not (theirs.has_key(name)):
              missing[name + "()"] = "not implemented"
              continue
    
    
            ourf   = cls._getMethodDeclaration(method)
            theirf = cls._getMethodDeclaration(theirs[name])
    
            if not (ourf == theirf):
              missing[name + "()"] = "method signature differs"
    
        if not (len(missing) == 0):
          raise Exception('incompatible Implementation-implementation %s: %s' % (aImplementation.__class__.__name__, missing))