Title: add implicit conversion for random.choice() on a dict
Type: enhancement Stage: resolved
Components: Versions: Python 3.4
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: Aristide Grange, tim.peters
Priority: normal Keywords:

Created on 2018-03-18 22:00 by Aristide Grange, last changed 2018-03-19 07:48 by Aristide Grange. This issue is now closed.

Messages (3)
msg314062 - (view) Author: Aristide Grange (Aristide Grange) Date: 2018-03-18 22:00
In Python 3, the expression:


where `d` is a `dict`, raises this error:

~/anaconda3/lib/python3.6/ in choice(self, seq)
    256         except ValueError:
    257             raise IndexError('Cannot choose from an empty sequence') from None
--> 258         return seq[i]
    260     def shuffle(self, x, random=None):

KeyError: 2

Converting `d` into a list restores the Python 2's behavior:


I am aware that the keys of a dict have now their own type. But IMHO the error message is rather uninformative, and above all, couldn't this conversion be made implicitely under the hood?
msg314069 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2018-03-18 23:49
This won't be changed.  The dict type doesn't support efficient random choice (neither do sets, by the way), and it's been repeatedly decided that it would do a disservice to users to hide that.  As you know, you can materialize the keys in a list (or tuple) first if you _want_ to pay that cost.  Otherwise you should use a different data structure.

Note that there's really no differnce between Pythons 2 and 3 here.  If you _happen_ to have a dict that uses little integers as keys, then it can _appear_ to work, when a random integer picked from range(len(the_dict)) happens to be one of the keys.  But then you get back the associated dict value, not the key.  For example, here under Python 2.7.11:

>>> import random
>>> random.choice({0: "a", 1: "b"})
>>> random.choice({0: "a", 1: "b"})
>>> random.choice({0: "a", 1: "b"})

But if the keys don't happen to be little integers, it always fails:

>>> random.choice({"a": 1, "b": 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\", line 275, in choice
    return seq[int(self.random() * len(seq))]  # raises IndexError if seq is empty
KeyError: 1
msg314080 - (view) Author: Aristide Grange (Aristide Grange) Date: 2018-03-19 07:48
My bad... For my reference to Python 2, I relied on my memory only, which starts to vanish. Really sorry about that. Yes, `random.choice(d)` (mostly) fails in Python 2 too, with an error message that I better understand after reading your explanation.

So, in Python 2/3, when `random.choice()` is applied to a dictionary, it draws a random integer i in [0, len(d)[ and tries to return the _value_ `d[i]`. It's quite unexpected, for me at last. According to the doc:

Return a random element from the non-empty sequence seq. If seq is empty, raises IndexError.

In Python 3, evaluating `choice(d.keys())` raises "TypeError: 'dict_keys' object does not support indexing". Shouldn't `choice(d)` _always_ fail with the same error message? I am not sure to see any legitimate use for the current behavior.

With regard to the repeated refusal to hide the fact that `choice`-ing among the keys of a dictionary is a linear operation, I can understand this decision. The general interest does not necessary align with that of an algorithmic teacher which only uses Python as a support language for introducing students to basic / transversal datatypes such as lists, arrays, dictionaries, sets, and prefers to avoid speaking of `dict_keys` and other Python's niceties...

Anyway, thanks a lot for your detailed and patient answer.
Date User Action Args
2018-03-19 07:48:23Aristide Grangesetmessages: + msg314080
2018-03-18 23:49:54tim.peterssetstatus: open -> closed

nosy: + tim.peters
messages: + msg314069

resolution: wont fix
stage: resolved
2018-03-18 22:00:28Aristide Grangecreate