52 votes

Conception d'abstractions/interfaces Java en Python

J'ai un certain nombre de classes qui partagent toutes les mêmes méthodes, mais avec des implémentations différentes. En Java, il serait logique que chacune de ces classes implémente une interface ou étende une classe abstraite. Python offre-t-il quelque chose de similaire ou dois-je adopter une autre approche ?

80voto

katrielalex Points 40655

Il y a une petite histoire derrière les interfaces en Python. L'attitude originale, qui a prévalu pendant de nombreuses années, est que vous n'en avez pas besoin : Python fonctionne selon le principe EAFP (plus facile de demander pardon que la permission). C'est-à-dire qu'au lieu de spécifier que vous acceptez un objet, je ne sais pas, ICloseable, vous essayez simplement de close l'objet quand vous en avez besoin, et s'il soulève une exception, alors il soulève une exception.

Ainsi, dans cette mentalité, vous écrivez simplement vos classes séparément, et vous les utilisez comme bon vous semble. Si l'une d'entre elles n'est pas conforme aux exigences, votre programme lèvera une exception ; inversement, si vous écrivez une autre classe avec les bonnes méthodes, elle fonctionnera tout simplement, sans que vous ayez besoin de préciser qu'elle implémente votre interface particulière.

Cela fonctionne assez bien, mais il y a des cas précis d'utilisation d'interfaces, en particulier pour les grands projets logiciels. La décision finale en Python a été de fournir l'interface abc qui vous permet d'écrire classes de base abstraites c'est-à-dire des classes que vous ne pouvez pas instancier à moins de surcharger toutes leurs méthodes. C'est à vous de décider si vous pensez que cela vaut la peine de les utiliser.

En PEP présente l'ABC l'expliquent bien mieux que moi :

Dans le domaine de la programmation orientée objet, les modèles d'utilisation de interagir avec un objet peuvent être divisés en deux catégories de base, qui sont "l'invocation" et "l'inspection".

L'invocation consiste à interagir avec un objet en invoquant ses méthodes. Généralement, elle est associée au polymorphisme, de sorte que l'invocation d'une méthode peut exécuter un code différent selon le type de l'objet.

L'inspection signifie la possibilité pour un code externe (en dehors des méthodes de l'objet) d'examiner le type ou les propriétés de cet objet. méthodes de l'objet) d'examiner le type ou les propriétés de cet objet, et de prendre des décisions sur la façon de traiter cet objet en fonction de cette informations.

Les deux modes d'utilisation servent le même objectif général, qui est de pouvoir le traitement d'objets divers et potentiellement nouveaux d'une manière de manière uniforme, tout en permettant de personnaliser les décisions de traitement personnalisées pour chaque type d'objet.

Dans la théorie classique de la POO, l'invocation est le modèle d'utilisation préféré, et l'inspection est activement découragée, étant considérée comme une relique d'une style de programmation procédural antérieur. Cependant, en pratique, cette vision est tout simplement trop dogmatique et inflexible, et conduit à une sorte de conception une sorte de rigidité de conception qui est en désaccord avec la nature dynamique d'une dynamique d'un langage comme Python.

En particulier, il est souvent nécessaire de traiter les objets de manière à ce que qui n'a pas été prévue par le créateur de la classe d'objets. Il ne s'agit pas toujours la meilleure solution de construire dans chaque objet des méthodes qui satisfaire les besoins de tous les utilisateurs possibles de cet objet. De plus, il y a beaucoup de philosophies de distribution puissantes qui sont en contraste direct directement à l'exigence classique de la POO d'un comportement strictement strictement encapsulé dans un objet, comme par exemple la logique pilotée par les règles ou les par exemple.

D'autre part, l'une des critiques de l'inspection par la POO classique est le manque de formalisme et la nature ad hoc de ce qui est inspecté. ce qui est inspecté. Dans un langage tel que Python, dans lequel presque tous les objets aspect d'un objet peut être reflété et accédé directement par du code externe, il existe de nombreuses manières différentes de tester si un objet est conforme à un protocole particulier ou non. Par exemple, si l'on demande "Cet objet est-il objet est-il un conteneur de séquence mutable ? de "list", ou encore une méthode nommée "_". getitem _'. Mais notez que, bien que ces tests puissent sembler évidents, aucun d'entre eux n'est correctes, car l'une génère des faux négatifs, et l'autre de faux positifs.

Le remède généralement accepté est de standardiser les tests, et les regrouper dans un arrangement formel. Cela est plus facile à faire en associant à chaque classe un ensemble de propriétés standard testables, soit via le mécanisme d'héritage, soit par d'autres moyens. Chaque test porte avec lui un ensemble de promesses : il contient une promesse sur le comportement général de la classe, et une promesse quant aux autres méthodes de la classe qui seront disponibles. seront disponibles.

Ce PEP propose une stratégie particulière pour organiser ces tests connue sous le nom de classes de base abstraites, ou ABC. Les ABC sont simplement des classes Python qui sont ajoutées dans l'arbre d'héritage d'un objet afin de signaler certaines caractéristiques de cet objet à un inspecteur externe. Les tests sont effectués à l'aide de isinstance(), et la présence d'une ABC particulière signifie que le test a été a réussi.

En outre, les ABC définissent un ensemble minimal de méthodes qui établissent le comportement caractéristique du type. Un code qui discrimine les objets en fonction de leur type ABC peut être sûr que ces méthodes seront seront toujours présentes. Chacune de ces méthodes est accompagnée d'un définition sémantique abstraite généralisée qui est décrite dans la documentation de l'ABC. Ces définitions sémantiques standard ne sont pas imposées, mais sont fortement recommandées.

Comme toutes les autres choses en Python, ces promesses sont de la nature d'une gentlemen's agreement, ce qui, dans ce cas, signifie que si le langage langage fait respecter certaines des promesses faites dans l'ABC, c'est à la l'implémenteur de la classe concrète de s'assurer que les promesses restantes sont restantes soient tenues.

6voto

Andrzej Doyle Points 52541

Je ne suis pas très familier avec Python, mais je pense que ce n'est pas le cas.

La raison pour laquelle les interfaces existent en Java est qu'elles spécifient une contrat . Quelque chose qui met en œuvre java.util.List par exemple, est assuré d'avoir un add() pour se conformer au comportement général défini dans l'interface. Vous pourriez introduire n'importe quelle implémentation (saine) de List sans connaître sa classe spécifique, appeler une séquence de méthodes définies dans l'interface et obtenir le même comportement général.

De plus, le développeur et le compilateur peuvent savoir qu'une telle méthode existe et est appelable sur l'objet en question, même s'ils ne connaissent pas sa classe exacte. C'est une forme de polymorphisme qui est nécessaire avec le typage statique pour permettre différentes classes d'implémentation tout en sachant qu'elles sont toutes légales.

Cela n'a pas vraiment de sens en Python, car il n'est pas typé statiquement. Vous n'avez pas besoin de déclarer la classe d'un objet, ni de convaincre le compilateur que les méthodes que vous appelez sur cet objet existent bel et bien. Les "interfaces" dans un monde typage canard sont aussi simples que d'invoquer la méthode et de croire que l'objet peut gérer ce message de manière appropriée.

Note - les modifications apportées par des pythonistes plus expérimentés sont les bienvenues.

5voto

M S Points 1150

Vous pouvez peut-être utiliser quelque chose comme ça. Cela agira comme une classe abstraite. Chaque sous-classe est donc obligée d'implémenter func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")

3voto

tuva Points 885

J'ai écrit un bibliothèque dans la version 3.5+, il est possible d'écrire des interfaces en Python.

L'essentiel est d'écrire un décorateur de classe avec l'aide de inspect .

import inspect

def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator

def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )

def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )

def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )

def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

Vous pouvez alors écrire des classes comme celle-ci :

class Quackable:
    def quack(self) -> bool:
        pass

@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

En dessous, vous obtiendrez cependant une erreur :

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'

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