Python abstract classes and interfaces

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

    Python is a very flexible language. One of the facets of this flexibility is the possibilities provided by metaprogramming. Although abstract classes and interfaces are not represented in the core of the language, the former were implemented in the standard abc module, the latter 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 (abс)



    Starting with the language version 2.6, the abc module is included in the standard library, which adds abstract base classes (hereinafter ABK) to the language.

    ABA allow you to define a class, while indicating which methods or properties must be redefined in the derived 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 with the ability to move and a certain speed in the code, 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 ABA 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 ABA fits well into the hierarchy of class inheritance, it is easy to use them, and implementation, if you look at the source code of the abc module, is very simple. Abstract classes are used in the standard collections and number modules, defining methods for user
    - defined inheritor classes that are necessary for defining .

    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 work on Zope3 decided to focus on component architecture; the framework has turned into a set of practically independent components. Glue connecting components - interfaces and adapters based on them.

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

    In the simplest case, using interfaces resembles an ABA example:

    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 which attributes and methods the object should have. Moreover, the class implements the interface, and the class object provides. You should pay attention to the difference between these concepts!

    An “implementation” of an interface means that only an “produced” entity will possess the necessary properties; and the “provision” of an interface indicates the specific capabilities of the entity being evaluated. Accordingly, in Python, classes, by the way, can both implement and provide an interface.

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

    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, the interfaces only complicate the code, as, incidentally, the ABK.

    The component architecture of Zope includes another important concept - adapters. Generally speaking, this is a simple design pattern that corrects 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 a 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 account for the 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 across interfaces. Thanks to it, you can get an adapter by passing an adaptable object to the interface class call. If the adapter is not registered, the second argument to the interface 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>
    


    It is convenient to use such an infrastructure for dividing 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 fair amount of the architecture relies on the interfaces from zope.interfaces.

    5 Conclusion



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

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

    The minus is the semantics of issubclass, isinstance checks that intersect with ordinary classes (their inheritance hierarchy). No additional abstractions are built on ABA.

    Interfaces are declarative essence, they do not set any framework; it is simply stated that the class implements, and its object provides an interface. Semantically, the statements implementedBy, providedBy are more correct. On such a simple base, it is convenient to build component architecture using adapters and other derived entities, which is what the large Zope and Twisted frameworks do.

    It should be understood that the use of both tools makes sense only 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.

    Also popular now: