This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: defaultdict.fromkeys should accept a callable factory
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: SilentGhost, allenap, justanr, rhettinger, serhiy.storchaka, veky
Priority: normal Keywords:

Created on 2015-02-01 16:19 by justanr, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (9)
msg235180 - (view) Author: Alec Nikolas Reiter (justanr) Date: 2015-02-01 16:19
Not something I've noticed until today, but defaultdict.fromkeys only accepts an iterable of keys and an initial value. To set the default_factory for the newly created collection, you need to prod at the attribute itself.

This isn't a huge issue for me, however adding an optional default_factory to fromkeys would be a nice convenience.
msg235181 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2015-02-01 16:47
Correct me if I'm wrong but this seem as a very unlikely use case. Why would you need to ensure content of the defaultdict (i.e., why would you ever use its fromkeys method)? And, perhaps, having to implicitly assign default factory is not such a bad tradeoff, considering implementation overhead.
msg235187 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2015-02-01 18:00
-0  I could see some use cases for this, dict.fromkeys(contestants, factory=list), but it adds complexity to a core container API and it starts to overlap the use cases for collections.defaultdict().

Perhaps set comprehensions should remain the one-obvious-way-to-do-it:
   {k : [] for k in contestants}
msg235191 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2015-02-01 18:27
Raymond, but Alec talks about 

  defaultdict.fromkeys(constants, factory=list)

as opposed to current solution

  d = defaultdict.fromkeys(constants)
  d.default_factory = list
  for i in d:
      d[i] = []

I wouldn't think that the dict.fromkeys should be affected by this issue.
msg235193 - (view) Author: Alec Nikolas Reiter (justanr) Date: 2015-02-01 18:38
@SilentGhost I was playing with a voting algorithm (Instant Runoff, rather than traditional majority). Ultimately, I ended up using Counter in place of a defaultdict, but that's how I ended up noticing it.

Not so much ensuring the content so much as setting an initial default state to mutate.

@rhettinger I had considered a comprehension and passing it into a defaultdict (or Counter) to get that nice missing key handling.

Implicitly assigning the factory is a pretty good compromise, like I said fromkeys accepting the factory would be a nice convenience rather than correcting a major oversight.
msg235196 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-02-01 18:53
It may be written simpler:

    d = defaultdict(factory)
    for i in constants:
        d[i]

or

    d = defaultdict(factory, {i: factory() for i in constants})

I'm inclined to reject this proposition. It serves very special use case.
msg276981 - (view) Author: Gavin Panella (allenap) Date: 2016-09-19 15:48
It's inconsistent that defaultdict([]) should be rejected:

  >>> defaultdict([])
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: first argument must be callable or None

but defaultdict.fromkeys([]) is okay:

  >>> defaultdict.fromkeys([])
  defaultdict(None, {})

The constructor signature differs between defaultdict and dict, and defaultdict.fromkeys is an alternate constructor, so it seems reasonable to also change its signature.

Also confusing is that I can call fromkeys on an instance of defaultdict:

  >>> dd = defaultdict(list)
  >>> dd.fromkeys([1])
  defaultdict(None, {1: None})

Instinctively I expect the default_factory to be carried over, even though I realise that would be odd behaviour for Python. If defaultdict.fromkeys were to expect a mandatory default_factory argument there wouldn't be this moment of confusion.
msg276997 - (view) Author: Vedran Čačić (veky) * Date: 2016-09-20 01:42
That's usual behavior for any class method. You can call them on an instance, but they don't have the access to it, only to its class. So transferring of factory would in fact not be possible. Of course,it's possible to make fromkeys an instance method, but please don't do that.
msg277000 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2016-09-20 03:39
[Serhiy]
I'm inclined to reject this proposition. It serves very special use case.

[Silent Ghost]
Correct me if I'm wrong but this seem as a very unlikely use case

[Alec Nikolas Reiter]
Implicitly assigning the factory is a pretty good compromise, like I said fromkeys accepting the factory would be a nice convenience rather than correcting a major oversight.

-----

After thinking about the above, I'm disinclined to make the API modification.

The current way to do it is straight-forward and reads nicely:

    >>> d = defaultdict.fromkeys('abcdefg', somedefault)
    >>> d.default_factory = somefunc

Combining those into a single step is less readable and may incorrectly imply some sort of interaction between the factory function and initial population of keys with a constant default value.   Likewise, the possibility of implicitly guessing and assigning the factory function is contraindicated by the zen-of-python in at least two places.

Given the paucity of use cases, the potential for API confusion, and the ready availability of reasonable alternatives, I'm closing the feature request.  Thank for the suggestion though, it certainly seemed like something that deserved a little more thought.
History
Date User Action Args
2022-04-11 14:58:12adminsetgithub: 67561
2016-09-20 03:39:26rhettingersetstatus: open -> closed
resolution: rejected
messages: + msg277000
2016-09-20 01:42:08vekysetnosy: + veky
messages: + msg276997
2016-09-19 15:48:05allenapsetnosy: + allenap
messages: + msg276981
2015-02-01 18:53:22serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg235196
2015-02-01 18:38:31justanrsetmessages: + msg235193
2015-02-01 18:27:01SilentGhostsetmessages: + msg235191
2015-02-01 18:00:06rhettingersetassignee: rhettinger
messages: + msg235187
2015-02-01 17:33:48serhiy.storchakasetnosy: + rhettinger
2015-02-01 16:47:15SilentGhostsetversions: - Python 3.4
nosy: + SilentGhost

messages: + msg235181

components: + Library (Lib)
2015-02-01 16:19:44justanrcreate