2011-11-18 16 views
28

Ho un numero di classi che condividono tutti gli stessi metodi, solo con diverse implementazioni. In Java, avrebbe senso che ognuna di queste classi implementasse un'interfaccia o estendesse una classe astratta. Python ha qualcosa di simile a questo, o dovrei assumere un approccio alternativo?Disegno astratto/interfaccia Java in Python

+0

Vedi http://stackoverflow.com/questions/372042/difference -tra-abstract-class-and-interface-in-python Si noti il ​​collegamento al modulo di interfaccia Zope come implementazione di interfacce java-like. –

+0

c'è una risposta diretta a questo ci sono interfacce o no? –

risposta

51

C'è un po 'di storia dietro le interfacce in Python. L'atteggiamento originale, che ha dominato per molti anni, è che non ne hai bisogno: Python lavora sul principio EAFP (più facile chiedere perdono che permesso). Cioè, invece di specificare che tu accetti un oggetto ICloseable, non lo so, devi semplicemente provare a close l'oggetto quando ne hai bisogno, e se solleva un'eccezione allora solleva un'eccezione.

Quindi in questa mentalità si dovrebbero semplicemente scrivere le classi separatamente e usarle come preferisci. Se uno di essi non è conforme ai requisiti, il tuo programma solleverà un'eccezione; al contrario, se scrivi un'altra classe con i giusti metodi, funzionerà, senza che tu debba specificare che implementa la tua particolare interfaccia.

Questo funziona abbastanza bene, ma ci sono casi d'uso definiti per le interfacce, in particolare con progetti software più grandi. La decisione finale in Python era di fornire il modulo abc, che consente di scrivere classi di base astratte, ovvero classi che non è possibile creare un'istanza a meno che non si ignorino tutti i loro metodi. È una tua decisione se pensi che utilizzarli valga la pena.

Il PEP introducing ABCs spiegare molto meglio di me:

Nel campo della programmazione orientata agli oggetti, i modelli di utilizzo per che interagiscono con un oggetto possono essere suddivisi in due categorie di base, che sono 'invocazione 'e' ispezione '.

L'invocazione significa interagire con un oggetto invocando i suoi metodi. Di solito questo viene combinato con il polimorfismo, in modo che il richiamo di un determinato metodo possa eseguire codice diverso a seconda del tipo di un oggetto.

controllo si intende la capacità di codice esterno (al di fuori di metodi dell'oggetto ) per esaminare il tipo o proprietà di tale oggetto, e prendere decisioni su come trattare l'oggetto in base a tale informazioni.

Entrambi i modelli di utilizzo servono allo stesso fine generale, che deve essere in grado di sostegno trasformazione di oggetti diversi e potenzialmente innovativi in ​​modo uniforme , ma allo stesso tempo permettendo decisioni di lavorazione da personalizzato per ogni differente tipo di oggetto.

Nella teoria OOP classica, l'invocazione è il modello di utilizzo preferito, e l'ispezione è attivamente scoraggiata, essendo considerata una reliquia di uno stile di programmazione procedurale precedente. Tuttavia, in pratica questa vista è semplicemente troppo dogmatica e inflessibile e conduce a una sorta di rigidità del design che è molto in disaccordo con la natura dinamica di un linguaggio come Python.

In particolare, è spesso necessario elaborare gli oggetti in modo che non sia stato previsto dal creatore della classe dell'oggetto. Non è la la soluzione migliore per integrare tutti i metodi di oggetti che soddisfano le esigenze di ogni possibile utente di quell'oggetto. Inoltre, ci sono molte potenti filosofie di dispatch che sono in diretto contrasto al classico requisito OOP di comportamento rigorosamente incapsulato all'interno di un oggetto, esempi di regola o logica guidata modello match .

D'altra parte, una delle critiche all'ispezione da parte dei teorici classici OOP è la mancanza di formalismi e la natura ad hoc di ciò che è ispezionato. In un linguaggio come Python, in cui quasi ogni aspetto di di un oggetto può essere riflesso e direttamente accessibile dal codice esterno, esistono diversi modi per verificare se un oggetto è conforme a a un particolare protocollo o meno. Ad esempio, se si chiede 'questo oggetto è un contenitore di sequenza mutabile?', Si può cercare una classe base di 'elenco', oppure si può cercare un metodo denominato 'getitem'. Ma nota che sebbene questi test possano sembrare ovvi, nessuno di questi è corretto , poiché uno genera falsi negativi e l'altro falso positivo .

Il rimedio generalmente concordato è quello di standardizzare i test e raggrupparli in un accordo formale. Questo è più semplice con associando ad ogni classe un insieme di proprietà testabili standard, tramite il meccanismo di ereditarietà o altri mezzi. Ogni test porta con sé una serie di promesse: contiene una promessa sul comportamento generale della classe e una promessa su quali altri metodi della classe saranno disponibili.

Questo PEP propone una strategia particolare per l'organizzazione di questi test noti come classi base astratte o ABC.Gli ABC sono semplicemente classi Python che vengono aggiunte nell'albero di ereditarietà di un oggetto per segnalare determinate funzioni di quell'oggetto a un'ispettore esterno. I test vengono eseguiti utilizzando isinstance() e la presenza di un particolare ABC significa che il test ha superato.

Inoltre, gli ABC definiscono un insieme minimo di metodi che stabiliscono il comportamento caratteristico del tipo. Il codice che discrimina gli oggetti in base al loro tipo ABC può essere sicuro che tali metodi saranno sempre presenti . Ciascuno di questi metodi è accompagnato da una definizione semantica astratta generalizzata descritta nella documentazione per l'ABC. Queste definizioni semantiche standard non sono applicate a , ma sono fortemente raccomandate.

Come tutte le altre cose in Python, queste promesse sono nella natura di un patto gentiluomini, che in questo caso significa che mentre la lingua non rispettare alcune delle promesse fatte nel ABC, spetta al implementatore della classe concreta per assicurare che i restanti siano mantenuti.

+0

dai, è troppo lungo, ci sono interfacce o no? –

+0

Sì, e sono chiamate classi base astratte – Matt

3

Potrebbe essere possibile utilizzare qualcosa come questo. Questo fungerà da classe astratta. Ogni sottoclasse è così costretto a implementare FUNC1()

class Abstract: 

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

Questo è già presente nello stdlib come 'abc' (http://docs.python.org/library/abc.html). – katrielalex

+1

Bene, 'abc' è molto meglio dell'esempio precedente, perché' abc' genera un errore quando la classe viene creata mentre l'esempio precedente solleva solo quando viene chiamato il metodo. – madjar

4

Io non sono che la familiarità con Python, ma mi azzarderei a dire che non è così.

Il motivo per cui le interfacce sono presenti in Java è che esse specificano un contratto . Qualcosa che implementa java.util.List, ad esempio, garantisce un metodo add() conforme al comportamento generale definito nell'interfaccia. È possibile eseguire l'implementazione (sane) di List senza conoscere la sua classe specifica, chiamare una sequenza di metodi definiti sull'interfaccia e ottenere lo stesso comportamento generale.

Inoltre, sia lo sviluppatore che il compilatore possono sapere che tale metodo esiste ed è richiamabile sull'oggetto in questione, anche se non conoscono la sua classe esatta. È una forma di polimorfismo necessaria con la tipizzazione statica per consentire diverse classi di implementazione, pur sapendo che sono tutte legali.

Questo in realtà non ha senso in Python, perché non è tipizzato staticamente. Non è necessario dichiarare la classe di un oggetto, né convincere il compilatore che i metodi su cui si sta chiamando esista definitivamente. Le "interfacce" in un mondo che digita le anatre sono semplici come invocare il metodo e confidando che l'oggetto possa gestirlo in modo appropriato.

Nota: le modifiche da Pythonistas più esperti sono benvenute.

+0

Oppure perché non esiste un'eredità multipla in Java. –

0

Ho scritto un library in 3.5+ il permesso per la scrittura di interfacce in Python.

L'essenza è di scrivere un decoratore di classe con l'aiuto di 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])) 

È quindi possibile scrivere classi come questo:

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


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

seguito darebbe un errore però:

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

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