7

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'
Konstantin
  • 2,937
  • 10
  • 41
  • 58
  • 1
    No, `abc.abstractmethod` does not enforce what arguments must be present. – Martijn Pieters Mar 22 '14 at 18:51
  • Martijn, is there any other way to enforce arguments? May be something different from abc. – Konstantin Mar 22 '14 at 18:53
  • [`zope.interface`](http://docs.zope.org/zope.interface) can [verify method signatures](http://docs.zope.org/zope.interface/verify.html). But you need to do so explicitly (like in a unit test). – Martijn Pieters Mar 22 '14 at 18:59
  • 3
    Is your project complex enough to require this kind of static checking? If yes, Python might not be your best language choice. – Sven Marnach Mar 22 '14 at 19:02
  • 1
    Sven, what I'm implementing is a system for network equipment black-box testing. I use abstract classes to specify the interface in HAL layer of my software for various CLI/SNMP (tests for various devices will look the same besides hardware adaptation layer). Python is quite handy to write the tests. – Konstantin Mar 22 '14 at 19:06

1 Answers1

3

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))
user508402
  • 496
  • 1
  • 4
  • 19