At first sight, yes
At first sight it seems to violate the Liskov Substitution Principle:
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
If you have a program written for a
dict dictionary, that should look for words read in a stream, and do something if the words are not found (aka
KeyError occurs), the same program would not work with a
defaultdict because this would never cause a
KeyError to happen (which is an expected invariant for
Here we could open a debate, that only the Python designers could close:
- To be fully compliant with the LSP, they should have created an
abstract_dict that defines the shared interface, but makes no claim on the behavior in case of a missing key. Then they should have made dict,
ordereddict inherit from
abstract_dict. You would then use the three types of dictionnary in the same manner, but knowing that they are "sibbligns", you would not expect that they guarantee the same behavior. But you already know that from the current documentation, don't you ?
- As the
abstract_dict would not be very useful in the library, and as many of the methods certainly have the same implementation, the designers chose the inheritance. The idea is to reuse an implementation. Of course this kind of reuse violates the LSP. I suppose they have carefully evaluated the risks. But what's the risk for you if you knowingly choose
defaultdict, because you prefer working with empty values instead of
- I suppose they have also compared the potential risks with the benefits: if in future, new features would be added to dict, this inheritance model would ensure that
defaultdict would remain consistent at a lesser cost.
But in looking in depth, defaultdict is fully LSP compliant
First sights can mislead. In fact semantically speaking a
dict. Parameters and return values are the same. Behavior is exactly the same for all programs that verify existence of the key (aka
key in d) before trying to address it.
Even more, if you look closely at the documentation, for
dict, it is said:
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__() is not defined,
KeyError is raised.
__missing__() must be a method; it cannot be an
This means that you can't assume
KeyError when you use dict: you have to assume that a different value could be returned or error raised. The claimed invariant is more complex, and it is defined in such a manner that it really covers the behavior of
By the way,
defaultdict seems to have its properties by using the
__missing__() method. And by the way, if no factory is provided in its constructor, it will raise a
dict would (see documentation).
So it seems to me that it's fully compliant.