Perché usare le classi astratte Base in Python?
-
01-10-2019 - |
Domanda
Perché io sono abituato a vecchi modi di battitura anatra in Python, non riesco a capire la necessità di ABC (classi base astratte). Il aiuto è buona su come usarli.
Ho provato a leggere la logica nel PEP , ma è andata sopra la mia testa. Se ero alla ricerca di un contenitore di sequenza mutabile, vorrei verificare la presenza di __setitem__
, o più probabilmente provare a usarlo ( EAFP ). Non ho incontrato un vero e proprio uso di vita per i numeri modulo, che fa uso di ABC, ma che è il più vicino che ho alla comprensione.
Qualcuno può spiegare le ragioni a me, per favore?
Soluzione
Versione corta ??h3>
ABC offrono un più elevato livello di contratto di semantica tra i clienti e le classi implementate.
Versione lunga
C'è un contratto tra una classe e le sue chiamate. Le promesse di classe a fare certe cose e hanno determinate proprietà.
Ci sono diversi livelli del contratto.
A un livello molto basso, il contratto potrebbe includere il nome di un metodo o il suo numero di parametri.
In un linguaggio staticamente tipizzato, che il contratto sarebbe in realtà essere applicata dal compilatore. In Python, è possibile utilizzare EAFP o introspezione per confermare che l'oggetto sconosciuto risponde presente contratto previsto.
Ma ci sono anche di più alto livello, le promesse semantiche nel contratto.
Ad esempio, se v'è un metodo __str__()
, si prevede di restituire una rappresentazione stringa dell'oggetto. E ' potrebbe eliminare tutti i contenuti dell'oggetto, il commit della transazione e sputare una pagina vuota dalla stampante ... ma c'è una comune comprensione di quello che dovrebbe fare, descritto nel manuale Python.
Questo è un caso speciale, in cui il contratto semantica è descritta nel manuale. Quale dovrebbe essere il metodo print()
fare? Nel caso in cui scrivere l'oggetto ad una stampante o una linea sullo schermo, o qualcos'altro? Dipende - è necessario leggere i commenti per capire il contratto completo qui. Un pezzo di codice client che semplicemente controlla che il metodo print()
esiste ha confermato parte del contratto -. Che una chiamata di metodo può essere fatta, ma non che ci sia un accordo sulla semantica di livello più alto della chiamata ??p>
La definizione di una classe base astratta (ABC) è un modo di produrre un contratto tra gli esecutori di classe e dei chiamanti. Non è solo una lista di nomi di metodo, ma una condivisa comprensione di ciò che questi metodi dovrebbero fare. Se si eredita da questo ABC, si sta promettendo di seguire tutte le regole descritte nei commenti, tra cui la semantica del metodo print()
.
anatra tipizzazione di Python ha molti vantaggi in termini di flessibilità oltre statico-tipizzazione, ma non risolve tutti i problemi. ABC offrono una soluzione intermedia tra la forma libera di Python e il bondage-and-disciplina di un linguaggio staticamente tipizzato.
Altri suggerimenti
@ di Oddthinking risposta non è sbagliata, ma penso che manca la reale , pratica motivo Python ha ABCs in un mondo di anatra-digitazione.
I metodi astratti sono pulite, ma a mio parere in realtà non riempire eventuali casi d'uso non siano già coperti da digitazione anatra. risiede il potere reale classi astratte di base a il modo in cui consentono di personalizzare il comportamento di isinstance
e issubclass
. (__subclasshook__
è fondamentalmente un'API amichevole sulla cima del pitone __instancecheck__
e __subclasscheck__
ganci.) adattando built-in costrutti per lavorare su tipi personalizzati è parte integrante della filosofia di Python.
Il codice sorgente di Python è esemplare. qui è come collections.Container
è definita nella libreria standard ( al momento della scrittura):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Questa definizione di __subclasshook__
dice che ogni classe con un attributo __contains__
è considerato come una sottoclasse di container, anche se non sottoclasse direttamente. Quindi posso scrivere questo:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
In altre parole, se si implementa l'interfaccia giusta, sei una sottoclasse! ABC forniscono un modo formale per definire le interfacce in Python, pur rimanendo fedele allo spirito di anatra tipizzazione. Inoltre, questo funziona in un modo che onori il aperto-chiuso Principle .
sguardi del modello a oggetti di Python superficialmente simile a quello di un altro sistema di OO "tradizionale" (e con questo intendo Java *) - abbiamo ottenuto yer classi, yer oggetti, yer metodi - ma quando si gratta la superficie troverete qualcosa molto più ricco e più flessibile. Allo stesso modo, la nozione di Python di classi base astratte può essere riconoscibile per uno sviluppatore Java, ma in pratica sono destinati ad uno scopo molto diverso.
A volte mi ritrovo a scrivere funzioni polimorfiche che possono agire su un singolo elemento o un insieme di elementi, e trovo isinstance(x, collections.Iterable)
di essere molto più leggibile di hasattr(x, '__iter__')
o un blocco try...except
equivalente. (Se non si conosce Python, che di questi tre renderebbe l'intenzione del codice più chiaro?)
Detto questo, trovo che raramente ho bisogno di scrivere il mio ABC e io di solito scopro la necessità di uno attraverso il refactoring. Se vedo una funzione polimorfica facendo un sacco di controlli di attributo, o un sacco di funzioni che fanno gli stessi controlli di attributo, quell'odore suggerisce l'esistenza di un ABC in attesa di essere estratto.
* senza entrare nel dibattito sulla possibilità che Java è un "tradizionale" sistema OO ...
Addendum : Anche se una classe base astratta può ignorare il comportamento di isinstance
e issubclass
, ancora non entrare nel MRO della sottoclasse virtuale. Si tratta di un potenziale problema per i clienti:. Non tutti gli oggetti per i quali isinstance(x, MyABC) == True
ha i metodi definiti sulla MyABC
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Purtroppo questo uno di quelli "semplicemente non farlo" trappole (di cui Python ha relativamente pochi!): Evitare di definire ABCs sia con un __subclasshook__
e metodi non astratti. Inoltre, si dovrebbe fare la tua definizione di __subclasshook__
coerente con l'insieme dei metodi astratti tuoi definisce ABC.
Una caratteristica utile di ABC è che se non implementare tutti i metodi necessari (e proprietà) si ottiene un errore su di esemplificazione, piuttosto che un AttributeError
, potenzialmente molto più tardi, quando in realtà tenta di utilizzare il metodo mancante.
from abc import ABCMeta, abstractmethod
# python2
class Base(object):
__metaclass__ = ABCMeta
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
# python3
class Base(object, metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
class Concrete(Base):
def foo(self):
pass
# We forget to declare `bar`
c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"
Esempio da https://dbader.org/blog/abstract-base- lezioni-in-python
Modifica: per includere la sintassi python3, @PandasRocks grazie
Si farà determinare se un oggetto supporta un determinato protocollo senza dover controllare la presenza di tutti i metodi del protocollo o senza innescare un'eccezione in profondità nel territorio "nemico" a causa del mancato supporto molto più facile.
metodo astratto assicurarsi che ciò che mai il metodo che si sta chiamando nella classe genitore deve essere appare in classe figlio. Qui di seguito sono modo noraml di chiamare e utilizzare astratto. Il programma scritto in python3
modo Normale di chiamare
class Parent:
def methodone(self):
raise NotImplemented()
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
c.methodone()
'methodone () viene chiamato'
c.methodtwo()
NotImplementedError
Con metodo astratto
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
TypeError:. Can classe non instantiate astratta Figlio con metodi astratti methodtwo
Dato methodtwo non è rimessa in classe figlia abbiamo ottenuto l'errore. La corretta esecuzione è inferiore a
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
def methodtwo(self):
return 'methodtwo() is called'
c = Son()
c.methodone()
'methodone () viene chiamato'