Il defaultdict di Python viola l'LSP?

5

Credo che in Python defaultdict ereditato da dict violi il Principio di sostituzione di Liskov. defaultdict non aumenta KeyError mentre x in d è ancora False , per esempio.

È così? Se è così, perché lo sviluppatore ha deciso di fare un tale sacrificio?

    
posta Vadim Pushtaev 27.09.2016 - 23:47
fonte

3 risposte

10

A prima vista, sì

A prima vista sembra violare il Principio di sostituzione di Liskov :

If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program

Se hai un programma scritto per un dizionario dict , questo dovrebbe cercare le parole lette in un flusso e fare qualcosa se le parole non vengono trovate (cioè KeyError ), lo stesso programma non funzionerebbe con a defaultdict perché ciò non causerebbe mai un KeyError a verificarsi (che è un invariante previsto per dict ).

Qui potremmo aprire un dibattito, che solo i designer di Python potrebbero chiudere:

  • Per essere pienamente compatibili con il LSP, dovrebbero aver creato un abstract_dict che definisce l'interfaccia condivisa, ma non fa alcun reclamo sul comportamento in caso di una chiave mancante. Quindi avrebbero dovuto impostare dict, defaultdict e ordereddict ereditari da abstract_dict . Dovresti quindi utilizzare i tre tipi di dictionnary nello stesso modo, ma sapendo che sono "sibbligns", non ti aspetteresti che essi garantiscano lo stesso comportamento. Ma lo sai già dalla documentazione attuale, vero?
  • Poiché abstract_dict non sarebbe molto utile nella libreria, e poiché molti dei metodi hanno sicuramente la stessa implementazione, i progettisti hanno scelto l'ereditarietà. L'idea è di riutilizzare un'implementazione. Ovviamente questo tipo di riutilizzo viola l'LSP. Suppongo che abbiano attentamente valutato i rischi. Ma qual è il rischio per te se scegli consapevolmente defaultdict , perché preferisci lavorare con valori vuoti invece di KeyError ?
  • Suppongo che abbiano anche confrontato i potenziali rischi con i benefici: se in futuro venissero aggiunte nuove funzionalità a dict, questo modello di ereditarietà garantirebbe che defaultdict rimarrebbe coerente a un costo inferiore.

Ma guardando in profondità, defaultdict è completamente compatibile con LSP

Le prime attrazioni possono ingannare. Infatti semanticamente parlando un defaultdict è-a dict . I parametri e i valori di ritorno sono gli stessi. Il comportamento è esattamente lo stesso per tutti i programmi che verificano l'esistenza della chiave (alias key in d ) prima di provare ad affrontarlo.

Ancora di più, se osservi attentamente la documentazione , per dict , è detto:

d[key]

Return the item of d with key key. Raises a KeyError if key is not in the map.

If a subclass of dict defines a method __missing__() and key is not present, the d[key] operation calls that method with the key key as argument. The d[key] operation then returns or raises whatever is returned or raised by the __missing__(key) call. No other operations or methods invoke __missing__(). If __missing__() is not defined, KeyError is raised. __missing__() must be a method; it cannot be an instance variable:

Questo significa che non puoi assumere KeyError quando usi dict: devi presupporre che possa essere restituito un valore diverso o un errore. L'invariante rivendicata è più complessa ed è definita in modo tale da coprire realmente il comportamento di defaultdict .

A proposito, defaultdict sembra avere le sue proprietà usando il metodo __missing__() . E a proposito, se non viene fornita alcuna factory nel suo costruttore, aumenterà un KeyError come dict (vedere documentazione ).

Quindi mi sembra che sia pienamente conforme.

    
risposta data 28.09.2016 - 01:23
fonte
5

LSP non significa che una sottoclasse dovrebbe comportarsi esattamente come se fosse una superclasse in qualsiasi parte di codice che accetta la superclasse (e quindi anche la sottoclasse). Ciò renderebbe il polimorfismo molto meno utile - dopo tutto, sottoclassi perché vuoi cambiare il comportamento. Piuttosto, LSP significa che una sottoclasse può sostituire la sua superclasse senza alterare le proprietà desiderate del codice che la accetta. In altre parole, se qualcuno ha scritto un codice che accetta la superclasse, deve anche funzionare come previsto quando riceve la sottoclasse.

Ora, il pragmatismo è più importante che seguire ciecamente le migliori pratiche. Ecco una funzione che può funzionare su dict ma fallisce su defaultdict con gli stessi valori:

def foo(dct):
    try:
        _ = dct[12]
        assert 12 in dct
    except KeyError:
        pass

Quindi in teoria l'LSP è rotto, ma pragmaticamente - chi scriverà tale codice? Scrivere una funzione che funzioni sulla superclasse ma non sulle sue sottoclassi è sempre possibile ( assert type(arg) == superclass ) ma questo non è il tipico codice che le persone scrivono. Con i dizionari, potresti:

  • Accedi al membro e, se non esiste, prendi un KeyError e fai qualche altra cosa. Con defaultdict , fai semplicemente la cosa originale con il valore predefinito.
  • Controlla se il membro è nel dict. In tal caso, defaultdict si comporterà come un ordinario dict
  • Utilizza .get per selezionare il tuo valore predefinito. In realtà, questo è rotto in Python2 (userà il valore predefinito di defaultdict invece di quello specificato), ma in Python3 funziona di nuovo come un normale dict .

Di solito non usi questi metodi per convalidare l'un l'altro: scegli quello che meglio si adatta al tuo pezzo di codice e usalo. Quindi se il tuo codice ha funzionato con dict e non stai intenzionalmente cercando di romperlo per defaultdict , dovrebbe funzionare anche come previsto con defaultdict .

    
risposta data 28.09.2016 - 03:43
fonte
-2

Sì, viola LSP. Questo è uno dei motivi per cui l'ereditarietà (e la sub-tipizzazione nominale in generale) è pericolosa: gli sviluppatori di solito sbagliano. Per quanto riguarda il motivo per cui è stato creato in questo modo: dovresti chiedere agli sviluppatori.

    
risposta data 27.09.2016 - 23:51
fonte

Leggi altre domande sui tag