I get some strange behaviour of colletions.defaultdict
:
import collections
c1 = collections.defaultdict(str)
c1['new'] # Works!
c2 = collections.defaultdict(default_factory=str)
c2['new'] # Raises KeyError...
Why raises c2
a KeyError?
Sometimes I like naming parameter because I think it increases readability.
First I thought maybe python does not allow me to pass the parameter by naming it and puts my default_factory
parameter to the kwargs, so I checked:
def func(first, **kwargs):
print(first)
print(kwargs)
func(first='one', second='two')
This outputs:
one
{'second': 'two'}
So this is not the case.
The default_factory
parameter of the defaultdict
constructor is positional-only and doesn't really have a name. If you try to pass it by name, you are just passing a completely unrelated keyword argument. Since keyword arguments to the defaultdict
constructor are interpreted as its initial contents, your dict starts out having a single key "default_factory"
whose value is the str
type object.
To understand how this works, imagine a function like this:
def func(*args, **kwds):
(default_factory,) = args
for k, v in kwds.items():
print(k, v) # do something with keys and values
If the documentation of this function were to name the positional argument default_factory
, that might be a correct description of its meaning, but it would be misleading if it implied that one could pass it as a keyword argument.
Some built-in functions are like that because it is very easy to define positional-only arguments in CPython C code. With defaultdict
, it is by design, to allow literally any string key to be used as the part of initial content, without having an exception for a key that happens to be named default_factory
.