303 votes

Comment implémenter des interfaces en python ?

public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

Comment puis-je implémenter l'équivalent Python de ce code C# ?

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")

class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

Est-ce une bonne idée ? Veuillez donner des exemples dans vos réponses.

217voto

Lennart Regebro Points 52510

Comme mentionné par d'autres ici :

Les interfaces ne sont pas nécessaires en Python. Cela est dû au fait que Python dispose d'un héritage multiple approprié, ainsi que du ducktyping, ce qui signifie que les endroits où vous doit ont des interfaces en Java, vous n'êtes pas obligé d'en avoir en Python.

Cela dit, il existe encore plusieurs utilisations des interfaces. Certaines d'entre elles sont couvertes par les classes de base abstraites de Python, introduites dans Python 2.6. Elles sont utiles si vous souhaitez créer des classes de base qui ne peuvent pas être instanciées, mais qui fournissent une interface spécifique ou une partie d'une implémentation.

Une autre utilisation est celle qui consiste à spécifier qu'un objet implémente une interface spécifique, et vous pouvez utiliser les ABC pour cela aussi en les sous-classant. Un autre moyen est zope.interface, un module qui fait partie de l'architecture de composants Zope, un cadre de composants vraiment génial. Ici, vous ne sous-classez pas à partir des interfaces, mais vous marquez les classes (ou même les instances) comme implémentant une interface. Cela peut également être utilisé pour rechercher des composants dans un registre de composants. Super cool !

109voto

Peter Torpman Points 11

L'utilisation du module abc pour les classes de base abstraites semble faire l'affaire.

from abc import ABCMeta, abstractmethod

class IInterface:
    __metaclass__ = ABCMeta

    @classmethod
    def version(self): return "1.0"
    @abstractmethod
    def show(self): raise NotImplementedError

class MyServer(IInterface):
    def show(self):
        print 'Hello, World 2!'

class MyBadServer(object):
    def show(self):
        print 'Damn you, world!'

class MyClient(object):

    def __init__(self, server):
        if not isinstance(server, IInterface): raise Exception('Bad interface')
        if not IInterface.version() == '1.0': raise Exception('Bad revision')

        self._server = server

    def client_show(self):
        self._server.show()

# This call will fail with an exception
try:
    x = MyClient(MyBadServer)
except Exception as exc:
    print 'Failed as it should!'

# This will pass with glory
MyClient(MyServer()).client_show()

98voto

mrts Points 472

L'implémentation d'interfaces avec des classes de base abstraites est beaucoup plus simple dans Python 3 moderne et elles servent de contrat d'interface pour les extensions de plug-ins.

Créer l'interface/la classe de base abstraite :

from abc import ABC, abstractmethod

class AccountingSystem(ABC):

    @abstractmethod
    def create_purchase_invoice(self, purchase):
        pass

    @abstractmethod
    def create_sale_invoice(self, sale):
        log.debug('Creating sale invoice', sale)

Créez une sous-classe normale et surchargez toutes les méthodes abstraites :

class GizmoAccountingSystem(AccountingSystem):

    def create_purchase_invoice(self, purchase):
        submit_to_gizmo_purchase_service(purchase)

    def create_sale_invoice(self, sale):
        super().create_sale_invoice(sale)
        submit_to_gizmo_sale_service(sale)

Vous pouvez optionnellement avoir une implémentation commune dans les méthodes abstraites comme dans create_sale_invoice() et l'appelle avec super() explicitement dans la sous-classe comme ci-dessus.

L'instanciation d'une sous-classe qui n'implémente pas toutes les méthodes abstraites échoue :

class IncompleteAccountingSystem(AccountingSystem):
    pass

>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice

Vous pouvez également disposer de propriétés abstraites, de méthodes statiques et de méthodes de classe en combinant les annotations correspondantes avec les éléments suivants @abstractmethod .

Les classes de base abstraites sont idéales pour mettre en œuvre des systèmes basés sur des plugins. Toutes les sous-classes importées d'une classe sont accessibles par l'intermédiaire de la fonction __subclasses__() Ainsi, si vous chargez toutes les classes à partir d'un répertoire de plugins avec l'option importlib.import_module() et si elles sous-classent la classe de base, vous y avez un accès direct via __subclasses__() et vous pouvez être sûr que le contrat d'interface est appliqué pour chacun d'entre eux lors de l'instanciation.

Voici l'implémentation du chargement des plugins pour la fonction AccountingSystem exemple ci-dessus :

...
from importlib import import_module

class AccountingSystem(ABC):

    ...
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module(module_name)
            subclasses = cls.__subclasses__()
            if len(subclasses) > 1:
                raise InvalidAccountingSystemError('More than one '
                        f'accounting module: {subclasses}')
            if not subclasses or module_name not in str(subclasses[0]):
                raise InvalidAccountingSystemError('Accounting module '
                        f'{module_name} does not exist or does not '
                        'subclass AccountingSystem')
            cls._instance = subclasses[0]()
        return cls._instance

Ensuite, vous pouvez accéder à l'objet plugin du système de comptabilité par le biais de la fonction AccountingSystem classe :

>>> accountingsystem = AccountingSystem.instance()

(Inspiré par ce billet de PyMOTW-3 .)

61voto

Ahmmad Ismail Points 383

interface supporte Python 2.7 et Python 3.4+.

A installer vous devez

pip install python-interface

Exemple de code :

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass

class MyClass(implements(MyInterface)):

    def method1(self, x):
        return x * 2

    def method2(self, x, y):
        return x + y

46voto

Je vous invite à explorer ce que Python 3.8 a à offrir pour le sujet en question sous forme de Structural subtyping (static duck typing) (PEP 544)

Voir la brève description https://docs.python.org/3/library/typing.html#typing.Protocol

Pour l'exemple simple que voici, cela se passe comme suit :

from typing import Protocol

class MyShowProto(Protocol):
    def show(self):
        ...

class MyClass:
    def show(self):
        print('Hello World!')

class MyOtherClass:
    pass

def foo(o: MyShowProto):
    return o.show()

foo(MyClass())  # ok
foo(MyOtherClass())  # fails

foo(MyOtherClass()) échoueront aux contrôles de type statique :

$ mypy proto-experiment.py 
proto-experiment.py:21: error: Argument 1 to "foo" has incompatible type "MyOtherClass"; expected "MyShowProto"
Found 1 error in 1 file (checked 1 source file)

En outre vous pouvez spécifier explicitement la classe de base, par exemple :

class MyOtherClass(MyShowProto):

mais notez que cela rend les méthodes de la classe de base disponibles sur la sous-classe, et donc le vérificateur statique ne signalera pas qu'une définition de méthode est manquante sur la classe de base. MyOtherClass . Ainsi, dans ce cas, pour obtenir un contrôle de type utile, toutes les méthodes que nous souhaitons voir explicitement implémentées doivent être décorées avec la balise @abstractmethod :

from typing import Protocol
from abc import abstractmethod

class MyShowProto(Protocol):
    @abstractmethod
    def show(self): raise NotImplementedError

class MyOtherClass(MyShowProto):
    pass

MyOtherClass()  # error in type checker

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X