Comment implémenter des interfaces en python?

182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

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

    #endregion
}

Comment 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.

Pratik Deoghare
la source
Quel serait le but d'utiliser une interface dans votre cas?
Bandi-T
23
Franchement pas du tout! Je veux juste savoir quoi faire lorsque vous avez besoin d'interfaces en python?
Pratik Deoghare
18
raise NotImplementedErrorest ce que showdevrait être le corps - cela n'a aucun sens d'élever un complètement générique Exceptionquand Python en définit un intégré parfaitement spécifique! -)
Alex Martelli
2
Est-ce que init ne devrait pas appeler show () dans IInterface (ou lever l'exception elle-même) pour que vous ne puissiez pas instancier une interface abstraite?
Katastic Voyage
1
Je peux voir une certaine utilité pour cela ... disons que vous avez un objet dont vous voulez vous assurer qu'il a une signature spécifique. Avec la saisie de canard, vous ne pouvez pas garantir que l'objet aura la signature que vous attendez. Parfois, il peut être utile d'appliquer une certaine saisie sur des propriétés typées dynamiquement.
Prime By Design

Réponses:

151

Comme mentionné par d'autres ici:

Les interfaces ne sont pas nécessaires en Python. C'est parce que Python a un héritage multiple approprié, ainsi que du typage duck, ce qui signifie que les endroits où vous devez avoir des interfaces en Java, vous n'avez pas à les avoir en Python.

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

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

Lennart Regebro
la source
11
Pouvez-vous développer? 1. Comment implémenter une telle interface? 2. Comment peut-il être utilisé pour rechercher des composants?
géoidesique
43
"Les interfaces ne sont pas nécessaires en Python. Sauf lorsqu'elles le sont."
Baptiste Candellier
8
Les interfaces sont principalement utilisées pour avoir un résultat prévisible / appliquer l'exactitude des membres lors du passage d'objets. ce serait formidable si python le supportait en option. cela permettrait également aux outils de développement d'avoir une meilleure intelligence
Sonic Soul
1
Un exemple améliorerait grandement cette réponse.
bob
5
"C'est parce que Python a un héritage multiple approprié", qui a dit que les interfaces sont destinées à l'héritage multiple?
adnanmuttaleb
76

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()
Peter Torpman
la source
11
Yugghh, ayant besoin d'un module pour ce qui devrait faire partie du langage lui-même, ou pas du tout utilisé, IMO.
Mike de Klerk
voulez-vous dire if not server.version() == '1.0': raise ...:? Je ne comprends pas vraiment cette ligne. Une explication serait la bienvenue.
Skandix
1
@MikedeKlerk Je suis entièrement d'accord. Tout comme la réponse Python à la saisie; Je ne devrais pas avoir à importer un module pour déclarer que je veux qu'un type soit un type. La réponse à cela est typiquement "bien Python est typé dynamiquement", mais ce n'est pas une excuse. Java + Groovy résolvent ce problème. Java pour les trucs statiques, Groovy pour les trucs dynamiques.
ubiquibacon
6
@MikedeKlerk, le module abc est en effet intégré à python. Il est un peu plus difficile de mettre en place certains de ces modèles, car ils sont largement inutiles en Python en raison de modèles alternatifs considérés comme «plus pythoniques». Pour la grande majorité des développeurs, ce serait, comme vous l'avez dit, «pas du tout utilisé». Cependant, reconnaissant qu'il existe des cas très spécifiques qui nécessitent vraiment ces capacités d'interfaçage, les créateurs de Python ont fourni une API facile à utiliser pour rendre cela possible.
David Culbreth
39

L'interface prend en charge Python 2.7 et Python 3.4+.

Pour installer l' interface, 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
Blue Ray
la source
7
Un avantage majeur de cette bibliothèque, à mon humble avis, est l'échec précoce qu'elle vous donne: si votre classe n'implémente pas correctement une interface spécifiée, vous obtenez une exception dès que la classe est lue - vous n'avez même pas à l'utiliser . Avec la propre classe de base abstraite de Python, vous obtenez l'exception lorsque vous instanciez votre classe pour la première fois, ce qui pourrait être beaucoup plus tard.
Hans
C'est inutile, ABC offre une fonctionnalité intégrée similaire.
Daniel le
@DanielCasares ABC propose-t-il une interface réelle ou voulez-vous dire que les classes abstraites sans état ni implémentation sont la solution qu'offre ABC?
asaf92
36

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-in.

Créez 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 remplacez 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 éventuellement avoir une implémentation commune dans les méthodes abstraites comme dans create_sale_invoice(), en l'appelant 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 avoir des propriétés abstraites, des méthodes statiques et de classe en combinant les annotations correspondantes avec @abstractmethod .

Les classes de base abstraites sont idéales pour implémenter des systèmes basés sur des plugins. Toutes les sous-classes importées d'une classe sont accessibles via __subclasses__(), donc si vous chargez toutes les classes à partir d'un répertoire de plugin avec importlib.import_module()et si elles sous-classent la classe de base, vous avez un accès direct à elles via__subclasses__() et vous pouvez être sûr que le contrat d'interface est appliqué pour tous eux lors de l'instanciation.

Voici l'implémentation du chargement du plugin pour l' AccountingSystemexemple 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 plug-in du système comptable via la AccountingSystemclasse:

>>> accountingsystem = AccountingSystem.instance()

(Inspiré par ce post PyMOTW-3 .)

mrts
la source
Question: Que signifie le nom de module "ABC"?
Sebastian Nielsen le
"ABC" signifie "Abstract Base Classes", voir la documentation officielle
mrts
31

Il existe des implémentations tierces d'interfaces pour Python (le plus populaire est celui de Zope , également utilisé dans Twisted ), mais le plus souvent les codeurs Python préfèrent utiliser le concept plus riche connu sous le nom de "Abstract Base Class" (ABC), qui combine une interface avec la possibilité d'y avoir aussi quelques aspects de mise en œuvre. Les ABC sont particulièrement bien pris en charge dans Python 2.6 et versions ultérieures, voir le PEP , mais même dans les versions antérieures de Python, ils sont normalement considérés comme "la voie à suivre" - définissez simplement une classe dont certaines méthodes soulèvent NotImplementedErrorpour que les sous-classes soient en notant qu'ils feraient mieux de remplacer ces méthodes! -)

Alex Martelli
la source
3
Il existe des implémentations tierces d'interfaces pour Python Qu'est-ce que cela signifie? Pouvez-vous expliquer ABC?
Pratik Deoghare
2
Eh bien, je vais contester le fait qu'ABC soit "plus riche". ;) Il y a des choses que zope.interface peut faire que ABC ne peut pas faire aussi bien que l'inverse. Mais sinon, vous avez raison comme d'habitude. +1
Lennart Regebro
1
@Alfred: Cela signifie que des modules comme zope.interface ne sont pas inclus dans la bibliothèque standard, mais disponibles sur pypi.
Lennart Regebro
J'ai encore du mal à comprendre le concept d'ABC. Serait-il possible pour quelqu'un de réécrire twistedmatrix.com/documents/current/core/howto/components.html (IMHO, une excellente explication du concept des interfaces) en termes d'ABC. Cela a-t-il un sens?
mcepl
21

Quelque chose comme ça (peut ne pas fonctionner car je n'ai pas de Python):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"
Bandit
la source
2
Que dois-je faire avec __init__(self)le constructeur?
Pratik Deoghare
1
Dépend de vous. Puisqu'il n'y a aucune vérification à la compilation contre la construction d'un objet à partir d'une classe abstraite, vous n'obtiendrez aucune protection pendant le codage / compilation. Il y aura un constructeur hérité, donc l'objet serait créé, juste "vide". C'est à vous de décider si vous feriez mieux de permettre que cela se produise et de détecter les échecs plus tard, ou d'arrêter explicitement le programme sur-le-champ en implémentant un constructeur similaire en lançant une exception.
Bandi-T
1
C'est pourquoi abc.ABCc'est bien mieux que de déclencher NotImplementedError- l'instanciation d'une abc.ABCsous-classe qui n'implémente pas toutes les méthodes abstraites échoue tôt, vous êtes donc protégé contre les erreurs. Voir ma réponse ci-dessous pour savoir à quoi ressemble l'erreur.
mrts
8

Je crois comprendre que les interfaces ne sont pas nécessaires dans les langages dynamiques comme Python. En Java (ou C ++ avec sa classe de base abstraite), les interfaces sont des moyens pour s'assurer que, par exemple, vous passez le bon paramètre, capable d'exécuter un ensemble de tâches.

Par exemple, si vous avez observer et observable, observable est intéressé par l'abonnement d'objets prenant en charge l'interface IObserver, qui à son tour a notify action. Ceci est vérifié au moment de la compilation.

En Python, il n'y a rien de tel compile timeet les recherches de méthodes sont effectuées au moment de l'exécution. De plus, on peut remplacer la recherche avec les méthodes magiques __getattr __ () ou __getattribute __ (). En d'autres termes, vous pouvez passer, en tant qu'observateur, tout objet qui peut retourner appelable en accédantnotify attribut.

Cela m'amène à la conclusion que les interfaces en Python existent - c'est juste que leur application est reportée au moment où elles sont réellement utilisées

Tomasz Zieliński
la source