15.04.2017       Выпуск 173 (10.04.2017 - 16.04.2017)       Статьи

Интерфейсы в Python: протоколы и ABC

Автор рассказывает как использовать идею интерфейсов классов в Python.

Читать>>



Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

The idea of interface is really simple - it is the description of how an object behaves. An interface tells us what an object can do to play it’s role in a system. In object oriented programming, an interface is a set of publicly accessible methods on an object which can be used by other parts of the program to interact with that object. Interfaces set clear boundaries and help us organize our code better. In some langauges like Java, interfaces are part of the language syntax and strictly enforced. However, in Python, things are a little different. In this post, we will explore how interfaces can be implemented in Python.

Informal Interfaces: Protocols / Duck Typing

There’s no interface keyword in Python. The Java / C# way of using interfaces is not available here. In the dynamic language world, things are more implicit. We’re more focused on how an object behaves, rather than it’s type/class.

If it talks and walks like a duck, then it is a duck

So if we have an object that can fly and quack like a duck, we consider it as a duck. This called “Duck Typing”. In runtime, instead of checking the type of an object, we try to invoke a method we expect the object to have. If it behaves the way we expected, we’re fine and move along. But if it doesn’t, things might blow up. To be safe, we often handle the exceptions in a try..except block or use hasattr to check if an object has the specific method.

In the Python world, we often hear “file like object” or “an iterable” - if an object has a read method, it can be treated as a file like object, if it has an __iter__ magic method, it is an iterable. So any object, regardless of it’s class/type, can conform to a certain interface just by implementing the expected behavior (methods). These informal interfaces are termed as protocols. Since they are informal, they can not be formally enforced. They are mostly illustrated in the documentations or defined by convention. All the cool magic methods you have heard about - __len__, __contains__, __iter__ - they all help an object to conform to some sort of protocols.

class Team:
    def __init__(self, members):
        self.__members = members

    def __len__(self):
        return len(self.__members)

    def __contains__(self, member):
        return member in self.__members


justice_league_fav = Team(["batman", "wonder woman", "flash"])

# Sized protocol
print(len(justice_league_fav))

# Container protocol
print("batman" in justice_league_fav)
print("superman" in justice_league_fav)
print("cyborg" not in justice_league_fav)

In our above example, by implementing the __len__ and __contains__ method, we can now directly use the len function on a Team instance and check for membership using the in operator. If we add the __iter__ method to implement the iterable protocol, we would even be able to do something like:


for member in justice_league_fav:
    print(member)

Without implementing the __iter__ method, if we try to iterate over the team, we will get an error like:

TypeError: 'Team' object is not iterable

So we can see that protocols are like informal interfaces. We can implement a protocol by implementing the methods expected by it.

Formal Interfaces: ABCs

While protocols work fine in many cases, there are situations where informal interfaces or duck typing in general can cause confusion. For example, a Bird and Aeroplane both can fly(). But they are not the same thing even if they implement the same interfaces / protocols. Abstract Base Classes or ABCs can help solve this issue.

The concept behind ABCs is simple - we define base classes which are abstract in nature. We define certain methods on the base classes as abstract methods. So any objects deriving from these bases classes are forced to implement those methods. And since we’re using base classes, if we see an object has our class as a base class, we can say that this object implements the interface. That is now we can use types to tell if an object implements a certain interface. Let’s see an example.

import abc

class Bird(abc.ABC):
    @abc.abstractmethod
    def fly(self):
        pass

There’s the abc module which has a metaclass named ABCMeta. ABCs are created from this metaclass. So we can either use it directly as the metaclass of our ABC (something like this - class Bird(metaclass=abc.ABCMeta):) or we can subclass from the abc.ABC class which has the abc.ABCMeta as it’s metaclass already.

Then we have to use the abc.abstractmethod decorator to mark our methods abstract. Now if any class derives from our base Bird class, it must implement the fly method too. The following code would fail:

class Parrot(Bird):
    pass

p = Parrot()

We see the following error:

TypeError: Can't instantiate abstract class Parrot with abstract methods fly

Let’s fix that:


class Parrot(Bird):
    def fly(self):
        print("Flying")


p = Parrot()

Also note:

>>> isinstance(p, Bird)
True

Since our parrot is recognized as an instance of Bird ABC, we can be sure from it’s type that it definitely implements our desired interface.

Now let’s define another ABC named Aeroplane like this:

class Aeroplane(abc.ABC):
    @abc.abstractmethod
    def fly(self):
        pass


class Boeing(Aeroplane):
    def fly(self):
        print("Flying!")

b = Boeing()

Now if we compare:


>>> isinstance(p, Aeroplane)
False
>>> isinstance(b, Bird)
False

We can see even though both objects have the same method fly but we can now differentiate easily which one implements the Bird interface and which implements the Aeroplane interface.

We saw how we can create our own, custom ABCs. But it is often discouraged to create custom ABCs and rather use/subclass the built in ones. The Python standard library has many useful ABCs that we can easily reuse. We can get a list of useful built in ABCs in the collections.abc module - https://docs.python.org/3/library/collections.abc.html#module-collections.abc. Before writing your own, please do check if there’s an ABC for the same purpose in the standard library.

ABCs and Virtual Subclass

We can also register a class as a virtual subclass of an ABC. In that case, even if that class doesn’t subclass our ABC, it will still be treated as a subclass of the ABC (and thus accepted to have implemented the interface). Example codes will be able to demonstrate this better:

@Bird.register
class Robin:
    pass

r = Robin()

And then:

>>> issubclass(Robin, Bird)
True
>>> isinstance(r, Bird)
True
>>>

In this case, even if Robin does not subclass our ABC or define the abstract method, we can register it as a Bird. issubclass and isinstance behavior can be overloaded by adding two relevant magic methods. Read more on that here - https://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass

Further reading



Лучшая Python рассылка

Нас поддерживает


Python Software Foundation



Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus