Differenza tra classe astratta e interfaccia in Python


Risposte:


618

Quello che vedrai a volte è il seguente:

class Abstract1( object ):
    """Some description that tells you it's abstract,
    often listing the methods you're expected to supply."""
    def aMethod( self ):
        raise NotImplementedError( "Should have implemented this" )

Poiché Python non ha (e non ha bisogno) un contratto formale di Interfaccia, la distinzione in stile Java tra astrazione e interfaccia non esiste. Se qualcuno passa attraverso lo sforzo di definire un'interfaccia formale, sarà anche una classe astratta. Le uniche differenze sarebbero nell'intenzione dichiarata nella dottring.

E la differenza tra astratto e interfaccia è una cosa spaccante quando si digita l'anatra.

Java utilizza le interfacce perché non ha ereditarietà multiple.

Poiché Python ha un'eredità multipla, potresti anche vedere qualcosa del genere

class SomeAbstraction( object ):
    pass # lots of stuff - but missing something

class Mixin1( object ):
    def something( self ):
        pass # one implementation

class Mixin2( object ):
    def something( self ):
        pass # another

class Concrete1( SomeAbstraction, Mixin1 ):
    pass

class Concrete2( SomeAbstraction, Mixin2 ):
    pass

Questo utilizza una sorta di superclasse astratta con mixin per creare sottoclassi concrete che sono disgiunte.


5
S. Lott, vuoi dire che a causa della digitazione di duck la distinzione tra has-a (interfaccia) e is-a (eredità) non è sostanziale?
Lorenzo,

3
La differenza tra abstract e interfaccia è una cosa che fa perdere i capelli quando si digita l'anatra. Non so cosa significhi "sostanziale". È "reale" - ha sostanza - dal punto di vista del design. Ma dal punto di vista linguistico potrebbe non esserci supporto. È possibile adottare convenzioni per distinguere tra una classe astratta e una definizione di classe di interfaccia in Python.
S.Lott

26
@ L.DeLeo - sei sicuro che la tua nozione di has-a vs. is-a sia corretta? In genere vedo la distinzione come has-a = variabile membro vs. is-a = ereditarietà (classe genitore o interfaccia). Pensa comparabile o elenca in Java; quelle sono relazioni is-a, indipendentemente dal fatto che siano interfacce o classi astratte.
dimo414,

43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))è un messaggio di errore più informativo :)
naught101

9
@Lorenzo una relazione has-a non ha nulla a che fare con l'ereditarietà, la tipizzazione anatra, le interfacce e le classi astratte (tutte e quattro si riferiscono alle relazioni is-a).
Karl Richter,

197

Qual è la differenza tra la classe astratta e l'interfaccia in Python?

Un'interfaccia, per un oggetto, è un insieme di metodi e attributi su quell'oggetto.

In Python, possiamo usare una classe base astratta per definire e applicare un'interfaccia.

Utilizzando una classe base astratta

Ad esempio, supponiamo di voler utilizzare una delle classi di base astratte dal collectionsmodulo:

import collections
class MySet(collections.Set):
    pass

Se proviamo a usarlo, otteniamo un TypeErrorperché la classe che abbiamo creato non supporta il comportamento previsto degli insiemi:

>>> MySet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MySet with abstract methods
__contains__, __iter__, __len__

Così ci viene richiesto di attuare, a almeno __contains__ , __iter__e __len__. Usiamo questo esempio di implementazione dalla documentazione :

class ListBasedSet(collections.Set):
    """Alternate set implementation favoring space over speed
    and not requiring the set elements to be hashable. 
    """
    def __init__(self, iterable):
        self.elements = lst = []
        for value in iterable:
            if value not in lst:
                lst.append(value)
    def __iter__(self):
        return iter(self.elements)
    def __contains__(self, value):
        return value in self.elements
    def __len__(self):
        return len(self.elements)

s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2

Implementazione: creazione di una classe base astratta

Possiamo creare la nostra classe base astratta impostando la metaclasse abc.ABCMetae usando il abc.abstractmethoddecoratore su metodi pertinenti. Alla metaclasse verranno aggiunte le funzioni decorate __abstractmethods__all'attributo, impedendo l'istanza fino a quando non vengono definite.

import abc

Ad esempio, "effable" è definito come qualcosa che può essere espresso in parole. Supponiamo di voler definire una classe di base astratta che sia efficace, in Python 2:

class Effable(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

O in Python 3, con il leggero cambiamento nella dichiarazione della metaclasse:

class Effable(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Ora se proviamo a creare un oggetto effable senza implementare l'interfaccia:

class MyEffable(Effable): 
    pass

e tenta di istanziarlo:

>>> MyEffable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyEffable with abstract methods __str__

Ci viene detto che non abbiamo finito il lavoro.

Ora, se rispettiamo fornendo l'interfaccia prevista:

class MyEffable(Effable): 
    def __str__(self):
        return 'expressable!'

siamo quindi in grado di utilizzare la versione concreta della classe derivata da quella astratta:

>>> me = MyEffable()
>>> print(me)
expressable!

Ci sono altre cose che possiamo fare con questo, come la registrazione di sottoclassi virtuali che già implementano queste interfacce, ma penso che esuli dallo scopo di questa domanda. Gli altri metodi qui dimostrati dovrebbero adattare questo metodo usando il abcmodulo per farlo, tuttavia.

Conclusione

Abbiamo dimostrato che la creazione di una classe base astratta definisce le interfacce per oggetti personalizzati in Python.


101

Python> = 2.6 ha Classi di base astratte .

Le classi di base astratte (ABC abbreviate) completano la tipizzazione anatra fornendo un modo per definire le interfacce quando altre tecniche come hasattr () sarebbero goffe. Python viene fornito con molti ABC integrati per strutture dati (nel modulo collezioni), numeri (nel modulo numeri) e flussi (nel modulo io). Puoi creare la tua ABC con il modulo abc.

C'è anche il modulo Zope Interface , che viene utilizzato da progetti al di fuori di zope, come twisted. Io non sono davvero familiarità con essa, ma c'è una pagina wiki qui che aiuto potrebbe.

In generale, non è necessario il concetto di classi astratte o interfacce in Python (modificato - vedere la risposta di S.Lott per i dettagli).


2
Cosa guadagni usando ABCs in Python?
CpILL,

38

Python non ha davvero alcun concetto.

Utilizza la tipizzazione duck, che ha rimosso la necessità di interfacce (almeno per il computer :-))

Python <= 2.5: esistono ovviamente classi di base, ma non esiste un modo esplicito di contrassegnare un metodo come "puro virtuale", quindi la classe non è veramente astratta.

Python> = 2.6: esistono classi base astratte ( http://docs.python.org/library/abc.html ). E ti consente di specificare i metodi che devono essere implementati nelle sottoclassi. Non mi piace molto la sintassi, ma la funzione è lì. Il più delle volte è probabilmente meglio usare la tipizzazione duck dal lato client "using".


3
Python 3.0 aggiunge classi base astratte reali. Sono utilizzati nel modulo collezioni e in altri luoghi. docs.python.org/3.0/library/abc.html
Lara Dougan,

Sarebbe utile un riferimento al motivo per cui la digitazione di anatre rimuove la necessità di interfacce. Non mi sembra ovvio che la tipizzazione anatra, che intendo come la capacità di "colpire" qualsiasi metodo o attributo su qualsiasi oggetto, significherebbe che non è necessario specificare i comportamenti richiesti (e far ricordare al compilatore per implementarli), ecco come intendo le classi di base astratte.
Reb.Cabin

È meno l'anatra che digita quindi il supporto per l'ereditarietà multipla che rimuove la linea artificiale tra l'interfaccia e la classe astratta che ad esempio Java ha disegnato.
masi,

35

In un modo più semplice per spiegare: un'interfaccia è un po 'come una teglia per muffin vuota. È un file di classe con un insieme di definizioni di metodi che non hanno codice.

Una classe astratta è la stessa cosa, ma non tutte le funzioni devono essere vuote. Alcuni possono avere il codice. Non è rigorosamente vuoto.

Perché differenziare: non c'è molta differenza pratica in Python, ma a livello di pianificazione per un grande progetto, potrebbe essere più comune parlare di interfacce, poiché non c'è codice. Soprattutto se stai lavorando con programmatori Java che sono abituati al termine.


+1 per la distinzione in cui l'ABC può avere implementazioni proprie - che sembra un modo molto bello di superare in
astuzia

17

In generale, le interfacce vengono utilizzate solo nei linguaggi che utilizzano il modello di classe a ereditarietà singola. In questi linguaggi a eredità singola, le interfacce vengono in genere utilizzate se una classe può utilizzare un metodo o un insieme di metodi particolari. Anche in questi linguaggi a eredità singola, le classi astratte vengono utilizzate per avere variabili di classe definite oltre a nessuno o più metodi o per sfruttare il modello a ereditarietà singola per limitare l'intervallo di classi che potrebbero utilizzare un insieme di metodi.

I linguaggi che supportano il modello di ereditarietà multipla tendono a utilizzare solo classi o classi di base astratte e non interfacce. Poiché Python supporta l'ereditarietà multipla, non utilizza interfacce e si desidera utilizzare le classi di base o le classi di base astratte.

http://docs.python.org/library/abc.html


2

Le classi astratte sono classi che contengono uno o più metodi astratti. Insieme ai metodi astratti, le classi astratte possono avere metodi statici, di classe e di istanza. Ma in caso di interfaccia, avrà solo metodi astratti, non altri. Quindi non è obbligatorio ereditare la classe astratta ma è obbligatorio ereditare l'interfaccia.


1

Per completezza, dovremmo menzionare PEP3119 in cui la ABC è stata introdotta e confrontata con le interfacce e il commento originale di Talin .

La classe astratta non è un'interfaccia perfetta:

  • appartiene alla gerarchia ereditaria
  • è mutevole

Ma se pensi di scriverlo a modo tuo:

def some_function(self):
     raise NotImplementedError()

interface = type(
    'your_interface', (object,),
    {'extra_func': some_function,
     '__slots__': ['extra_func', ...]
     ...
     '__instancecheck__': your_instance_checker,
     '__subclasscheck__': your_subclass_checker
     ...
    }
)

ok, rather as a class
or as a metaclass
and fighting with python to achieve the immutable object
and doing refactoring
...

ti accorgerai abbastanza rapidamente che stai inventando la ruota per raggiungere alla fine abc.ABCMeta

abc.ABCMeta è stato proposto come utile aggiunta della funzionalità dell'interfaccia mancante, ed è abbastanza giusto in un linguaggio come Python.

Certamente, è stato in grado di essere migliorato meglio durante la scrittura della versione 3 e l'aggiunta di una nuova sintassi e il concetto di interfaccia immutabile ...

Conclusione:

The abc.ABCMeta IS "pythonic" interface in python
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.