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 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.
6 réponses
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é, et aussi ducktyping, ce qui signifie que les endroits où vous devez avoir des interfaces en Java, vous n'avez pas besoin de les avoir en Python.
Cela dit, il y a encore plusieurs utilisations pour les interfaces. Certains d'entre eux sont couverts par des Classes de base abstraites Pythons, introduites dans Python 2.6. Ils sont utiles, si vous voulez faire des classes de base qui ne peuvent pas être instancié, mais fournit 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 sous-classant à partir d'eux. Une autre façon est zope.interface, un module qui fait partie de L'Architecture des 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 un interface. Cela peut également être utilisé pour rechercher des composants à partir d'un composant de registre. Supercool!
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()
Il existe des implémentations tierces d'interfaces pour Python (le plus populaire est de Zope, également utilisé dans Twisted), mais plus généralement les codeurs Python préfèrent utiliser le concept plus riche connu sous le nom de "classe de Base abstraite" (ABC), qui combine une interface avec la possibilité d'avoir certains aspects d'implémentation là aussi. 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 " le way to go " - il suffit de définir une classe dont certaines méthodes soulèvent NotImplementedError
afin que les sous-classes soient à l'avis qu'elles feraient mieux de remplacer ces méthodes!-)
Quelque chose comme ça (pourrait ne pas fonctionner car je n'ai pas Python autour):
class IInterface:
def show(self): raise NotImplementedError
class MyClass(IInterface):
def show(self): print "Hello World!"
Ma compréhension est que les interfaces ne sont pas si 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'effectuer un ensemble de tâches.
Par exemple, si vous avez observer et observable, observable est intéressé par l'abonnement aux objets qui prennent 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 en a pas chose comme compile time
et les recherches de méthode 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, n'importe quel objet pouvant renvoyer callable lors de l'accès à l'attribut notify
.
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
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éer l'interface/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ée une sous-classe normale et remplace 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 explicitement avec super()
dans la sous-classe comme ci-dessus.
Instanciation d'un la 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-classe la classe de base, vous avez un accès direct à eux via __subclasses__()
et vous pouvez assurez-vous que le contrat d'interface est appliqué pour tous lors de l'instanciation.
Voici l'implémentation de chargement du plugin pour l'exemple AccountingSystem
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 système de comptabilité via la classe AccountingSystem
:
>>> accountingsystem = AccountingSystem.instance()
(inspiré par ce poste PyMOTW-3 .)