Populating a defaultdict at init time
Asked Answered
G

2

6

How can I get a callable factory for a defaultdict to allow populating it with a comprehension? I think it's probably not possible, but I can't think of a good reason why?

>>> def foo(*args):
...     # TODO
...
>>> from collections import defaultdict
>>> thing = foo(defaultdict, int)
>>> d = thing((i, i*i) for i in range(3))
>>> d[2]
# should return 4
>>> d[-1]
# should return 0
Guiana answered 14/1, 2014 at 0:13 Comment(3)
I don't understand what you are trying to do. What are thing and foo and why should thing return 0?Mersey
To clarify: thing((i, i*i) for i in range(3)) should return a defaultdict instance. I will edit the question to make that clearerGuiana
Oh ok, the -1 made me think it was a list rather than a missing index. If you wanted a default_factory that would do this, I don't think it could be done, because (from the docs) "The default factory is called without arguments to produce a new value when a key is not present, in getitem only."Mersey
R
10

Any arguments to defaultdict after the default_factory are treated just like arguments to dict:

>>> defaultdict(int, [(i, i*i) for i in range(5)])
defaultdict(<type 'int'>, {0: 0, 1: 1, 2: 4, 3: 9, 4: 16})

Just pass the comprehension to defaultdict and let it do the work:

def defaultdict_factory_factory(default_factory):
    def defaultdict_factory(*args, **kwargs):
        return defaultdict(default_factory, *args, **kwargs)
    return defaultdict_factory

Or use functools.partial:

def defaultdict_factory_factory(default_factory):
    return partial(defaultdict, default_factory)
Relent answered 14/1, 2014 at 0:39 Comment(5)
Very nice! This is noted in the docs (docs.python.org/2/library/…) but not in help(defaultdict) (at least in my Python 2.7 install)Demagnetize
Yep surprisingly missing from the docstring of defaultdict. I wonder how many other little python secrets are hiding out there ...Guiana
If this is still missing from the docstring in 3.4b2, someone should submit a bug report. (I'm assuming defaultdict is being affected by the argument clinic derby, so you can't just check the latest 3.3 release.)Doralia
It's still missing in the latest trunk, so I filed bugs.python.org/issue20250Doralia
As a side note, the OP is using a genexpr, which you've converted to a listcomp, and there's no obvious reason to do so.Doralia
D
5

Are you just looking for defaultdict.update?

>>> from collections import defaultdict
>>> thing = defaultdict(int)
>>> thing.update((i, i*i) for i in range(3))
>>> thing
defaultdict(<type 'int'>, {0: 0, 1: 1, 2: 4})

You could put this into a function.

>>> def initdefaultdict(type_, *args, **kwargs):
...     d = defaultdict(type_)
...     d.update(*args, **kwargs)
...     return d
... 
>>> thing = initdefaultdict(int, ((i, i+10) for i in range(3)))
>>> thing
defaultdict(<type 'int'>, {0: 10, 1: 11, 2: 12})
>>> thing[3]
0

Or to satisfy your original requirements, return a function:

>>> def defaultdictinitfactory(type_): # this is your "foo"
...     def createupdate(*args, **kwargs):
...             d = defaultdict(type_)
...             d.update(*args, **kwargs)
...             return d
...     return createupdate
... 
>>> f = defaultdictinitfactory(int) # f is your "thing"
>>> d = f((i, i*i) for i in range(3))
>>> d
defaultdict(<type 'int'>, {0: 0, 1: 1, 2: 4})
>>> 
Demagnetize answered 14/1, 2014 at 0:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.