📜 ⬆️ ⬇️

Abstract classes and interfaces in Python

Abstract base classes and interfaces are similar in purpose and meaning to the entity. Both the first and the second are a peculiar way of documenting code and help limit (decouple) the interaction of individual abstractions in the program (classes).

Python is a very flexible language. One facet of this flexibility is the possibilities provided by metaprogramming. And although in the core of the language abstract classes and interfaces are not represented, the first were implemented in the standard abc module, the second in the Zope project (zope.interfaces module).

It makes no sense to use both at the same time, and therefore each programmer must determine for himself which tool to use when designing applications.
')


2 Abstract base classes (abc)



Starting with the 2.6 version of the language, the abc module is added to the standard library, which adds abstract base classes to the language (hereinafter referred to as ABC).

ABCs allow you to define a class, while specifying which methods or properties must be redefined in successor classes:

 from abc import ABCMeta, abstractmethod, abstractproperty
 class Movable ():
     __metaclass __ = ABCMeta

     @abstractmethod
     def move ():
     "" "Move object" ""
    
     @abstractproperty
     def speed ():
     "" "Object speed" ""


Thus, if we want to use an object in the code that has the ability to move and a certain speed, then we should use the Movable class as one of the base classes.

The presence of the necessary methods and attributes of the object is now guaranteed by the presence of ABK among the ancestors of the class:

 class Car (Movable):
     def __init__:
         self.speed = 10
         self.x = 0

     def move (self):
         self.c + = self.speed
         def speed (self):
         return self.speed
    
 assert issubclass (Car, Movable)
 assert ininstance (Car (), Movable)


It can be seen that the concept of ABK fits well with the class inheritance hierarchy, it is easy to use them, and the implementation, if you look at the source code of the abc module, is very simple. Abstract classes are used in standard collections and number modules, specifying methods that are necessary for defining custom
heir classes.

Details and considerations regarding the use of ABA can be found in PEP 3119
( http://www.python.org/dev/peps/pep-3119/ ).

3 Interfaces (zope.interfaces)



The implementation of the Zope project in working on Zope3 decided to focus on the component architecture; The framework has become a set of almost independent components. The glue that joins the components is the interfaces and the adapters based on them.

The zope.interfaces module is the result of this work.

In the simplest case, the use of interfaces resembles an ABA try-in:

 import zope.interface

 class IVehicle (zope.interface.Interface):
     "" "Any moving thing" ""
     speed = zope.interface.Attribute ("" "Movement speed" "")
     def move ():
         "" "Make a single step" ""
    
 class Car (object):
     zope.interface.implements (IVehicle)

     def __init__:
         self.speed = 1
         self.location = 1

     def move (self):
         self.location = self.speed * 1
         print "moved!"
    
 assert IVehicle.implementedBy (Car)
 assert IVehicle.providedBy (Car ())


The interface declaratively shows what attributes and methods the object should have. Moreover, the class implements (implements) the interface, and the class object provides (provides). Pay attention to the difference between these concepts!

“Implementing” an interface with something means that only an “produced” entity will have the necessary properties; and the “provision” of an interface speaks of the specific capabilities of the entity being evaluated. Accordingly, in Python, by the way, classes can both implement and provide an interface.

In fact, the declaration implement (IVehicle) is a convention; just a promise that this class and its objects behave in this way. No real checks will be conducted.

 class IVehicle (zope.interface.Interface):
     "" "Any moving thing" ""
     speed = zope.interface.Attribute ("" "Movement speed" "")

     def move ():
         "" "Make a single step" ""

 class Car (object):
     zope.interface.implements (IVehicle)

 assert IVehicle.implementedBy (Car)
 assert IVehicle.providedBy (Car ())


It can be seen that in the simplest cases, interfaces only complicate the code, as, incidentally, ABK

Zope component architecture includes another important concept - adapters. Generally speaking, it is a simple design pattern, correcting one class for use somewhere where a different set of methods and attributes is required. So,

4 Adapters



Consider, greatly simplifying, an example from the Comprehensive Guide to Zope Component Architecture.

Suppose there are a couple of classes, Guest and Desk. Define the interfaces to them, plus the class that implements the Guest interface:

 import zope.interface
 from zope.interface import implements

 from zope.component import adapts, getGlobalSiteManager

 class IDesk (zope.interface.Interface):
     def register ():
         "Register a person"

 class IGuest (zope.interface.Interface):
     name = zope.interface.Attribute ("" "Person`s name" "")

 class Guest (object):
     implements (IGuest)

     def __init __ (self, name):
         self.name = name


The adapter must consider an anonymous guest by registering in the list of names:

 class GuestToDeskAdapter (object):
     adapts (IGuest)
     implements (IDesk)
    
     def __init __ (self, guest):
         self.guest = guest
    
     def register (self):
         guest_name_db.append (self.guest.name)


There is a registry that keeps track of adapters by interfaces. Thanks to him, you can get the adapter by passing an adaptable object to the interface class call. If the adapter is not registered, the second interface argument will be returned:

 guest = Guest ("Ivan")
 adapter = IDesk (guest, alternate = None)
 print adapter
 >>>> None found

 gsm = getGlobalSiteManager ()
 gsm.registerAdapter (GuestToDeskAdapter)

 adapter = IDesk (guest, alternate = "None found")
 print adapter

 >>>> __ main __. GuestToDeskAdapter object at 0xb7beb64c>


This infrastructure is convenient to use to separate the code into components and their binding.

One of the most striking examples of using this approach besides Zope itself is the Twisted network framework, where a good part of the architecture relies on interfaces from zope.interfaces.

5 Conclusion



Upon closer inspection, it turns out that interfaces and abstract base classes are different things.

Abstract classes basically rigidly set the required interface part. Checking an object for compliance with an abstract class interface is checked using the built-in isinstance function; class - issubclass. An abstract base class should be included in the hierarchy as a base class or mixin ʻa.

The minus can be considered the semantics of checks issubclass, isinstance, which intersect with ordinary classes (their inheritance hierarchy). No additional abstractions are built on ABK.

Interfaces - the essence of a declarative, they do not put any framework; it is simply stated that the class implements, and its object provides the interface. Semantic statements implementedBy, providedBy are more correct. It is convenient to build component architecture using such adapters and other derived entities on such a simple base, which is done by large Zope and Twisted frameworks.

It should be understood that the use of both tools only makes sense when building and using relatively large OOP systems — frameworks and libraries; in small programs, they can only confuse and complicate the code with unnecessary abstractions.

Source: https://habr.com/ru/post/72757/


All Articles