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.