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?

È stato utile?

Soluzione

Versione corta

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

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'

  
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top