Java abstract/interface design in Python

Question:

I have a number of classes which all share the same methods, only with different implementations. In Java, it would make sense to have each of these classes implement an interface or extend an abstract class. Does Python have anything similar to this, or should I be taking an alternative approach?

Asked By: Matt

||

Answers:

May be you can use something like this. This will act as an abstract class. Every subclass is thus forced to implement func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
Answered By: M S

I’m not that familiar with Python, but I would hazard a guess that it doesn’t.

The reason why interfaces exist in Java is that they specify a contract. Something that implements java.util.List, for example, is guaranteed to have an add() method to conforms to the general behaviour as defined on the interface. You could drop in any (sane) implementation of List without knowing its specific class, call a sequence of methods defined on the interface and get the same general behaviour.

Moreover, both the developer and compiler can know that such a method exists and is callable on the object in question, even if they don’t know its exact class. It’s a form of polymorphism that’s needed with static typing to allow different implementation classes yet still know that they’re all legal.

This doesn’t really make sense in Python, because it’s not statically typed. You don’t need to declare the class of an object, nor convince the compiler that methods you’re calling on it definitely exist. “Interfaces” in a duck-typing world are as simple as invoking the method and trusting that the object can handle that message appropriately.

Note – edits from more knowledgeable Pythonistas are welcome.

Answered By: Andrzej Doyle

There’s a bit of a story behind interfaces in Python. The original attitude, which held sway for many years, is that you don’t need them: Python works on the EAFP (easier to ask forgiveness than permission) principle. That is, instead of specifying that you accept an, I don’t know, ICloseable object, you simply try to close the object when you need to, and if it raises an exception then it raises an exception.

So in this mentality you would just write your classes separately, and use them as you will. If one of them doesn’t conform to the requirements, your program will raise an exception; conversely, if you write another class with the right methods then it will just work, without your needing to specify that it implements your particular interface.

This works pretty well, but there are definite use cases for interfaces, especially with larger software projects. The final decision in Python was to provide the abc module, which allows you to write abstract base classes i.e. classes that you can’t instantiate unless you override all their methods. It’s your decision as to whether you think using them is worth it.

The PEP introducing ABCs explain much better than I can:

In the domain of object-oriented programming, the usage patterns for
interacting with an object can be divided into two basic categories,
which are ‘invocation’ and ‘inspection’.

Invocation means interacting with an object by invoking its methods.
Usually this is combined with polymorphism, so that invoking a given
method may run different code depending on the type of an object.

Inspection means the ability for external code (outside of the
object’s methods) to examine the type or properties of that object,
and make decisions on how to treat that object based on that
information.

Both usage patterns serve the same general end, which is to be able to
support the processing of diverse and potentially novel objects in a
uniform way, but at the same time allowing processing decisions to be
customized for each different type of object.

In classical OOP theory, invocation is the preferred usage pattern,
and inspection is actively discouraged, being considered a relic of an
earlier, procedural programming style. However, in practice this view
is simply too dogmatic and inflexible, and leads to a kind of design
rigidity that is very much at odds with the dynamic nature of a
language like Python.

In particular, there is often a need to process objects in a way that
wasn’t anticipated by the creator of the object class. It is not
always the best solution to build in to every object methods that
satisfy the needs of every possible user of that object. Moreover,
there are many powerful dispatch philosophies that are in direct
contrast to the classic OOP requirement of behavior being strictly
encapsulated within an object, examples being rule or pattern-match
driven logic.

On the other hand, one of the criticisms of inspection by classic OOP
theorists is the lack of formalisms and the ad hoc nature of what is
being inspected. In a language such as Python, in which almost any
aspect of an object can be reflected and directly accessed by external
code, there are many different ways to test whether an object conforms
to a particular protocol or not. For example, if asking ‘is this
object a mutable sequence container?’, one can look for a base class
of ‘list’, or one can look for a method named ‘_getitem_’. But note
that although these tests may seem obvious, neither of them are
correct, as one generates false negatives, and the other false
positives.

The generally agreed-upon remedy is to standardize the tests, and
group them into a formal arrangement. This is most easily done by
associating with each class a set of standard testable properties,
either via the inheritance mechanism or some other means. Each test
carries with it a set of promises: it contains a promise about the
general behavior of the class, and a promise as to what other class
methods will be available.

This PEP proposes a particular strategy for organizing these tests
known as Abstract Base Classes, or ABC. ABCs are simply Python classes
that are added into an object’s inheritance tree to signal certain
features of that object to an external inspector. Tests are done using
isinstance(), and the presence of a particular ABC means that the test
has passed.

In addition, the ABCs define a minimal set of methods that establish
the characteristic behavior of the type. Code that discriminates
objects based on their ABC type can trust that those methods will
always be present. Each of these methods are accompanied by an
generalized abstract semantic definition that is described in the
documentation for the ABC. These standard semantic definitions are not
enforced, but are strongly recommended.

Like all other things in Python, these promises are in the nature of a
gentlemen’s agreement, which in this case means that while the
language does enforce some of the promises made in the ABC, it is up
to the implementer of the concrete class to insure that the remaining
ones are kept.

Answered By: Katriel

I wrote a library in 3.5+ the allows for writing interfaces in Python.

The gist is to write a class decorator with the help of inspect.

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

You can then write classes like this:

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

Below would give you an error though:

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'
Answered By: Kamil Sindi

according this articule https://realpython.com/python-interface/ , this is how you can do formal interface:

import abc

class CRUDInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def read(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def update(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def delete(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError



class ModelTest1(CRUDInterface):
    def create(self, *args, **kwargs):
        pass
    def read(self, *args, **kwargs):
        pass
    def update(self, *args, **kwargs):
        pass
    def delete(self, *args, **kwargs):
        pass
    
class ModelTest2(CRUDInterface):
        pass
    

formal interface that will raise errors when the abstract methods aren’t overridden.

>>> t1 = ModelTest1()
>>> t2 = ModelTest2()
Traceback (most recent call last):
  File "../abstractmodel.py", line 46, in <module>
    t2 = ModelTest2()
TypeError: Can't instantiate abstract class ModelTest2 with abstract methods create, delete, read, update
Answered By: Adán Escobar