1

I've read https://bugs.python.org/issue33098, which is about the following behavior of Python 3

a = {'1': 42, '2': 84}  # keys are str on purpose, c.f. the issue

import random
random.choice(a.keys())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/.../lib/python3.7/random.py", line 262, in choice
    return seq[i]
TypeError: 'dict_keys' object does not support indexing

I've discovered that this functionally equivalent call succeeds just fine

random.sample(a.keys(), 1)[0]
'1'

Given the justification on the issue, is there any deeper principled reason why the two functions should behave differently, are there practical considerations for making sample() more flexible in its arguments, or is it all just an implementation accident?

2

The support for random.sample(a.keys(), 1)[0] is an implementation accident and can break down in future Python3 versions. There is good reason to recommend that neither choice() nor sample() supports a dictionary or a 'dict_keys' object as an argument. As Tim Peters say:

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.

This answer is for both choice() and sample()! If speed matter, don't use standard dictionary!

If you really don't care about speed, at least, you should try a better approach (memory-free + save half-time in average):

import random, itertools

def randkey(dic):
    pos = random.randrange(0, len(dic))
    res, = itertools.islice(dic, pos, pos + 1)
    return res

randkey({"a": 1, "b": 1, "c": 1})
'b'

If I don't convince you:


Due to implementation detail:

random.sample(a.keys(), 1)[0]

Is equivalent to:

random.sample(tuple(a.keys()), 1)[0]

So the .keys() call is pointless and should be replaced by a list() or tuple() call:

random.sample(list(a), 1)[0]

The sample() function warn you when you try to use the dictionary directly as parameter:

random.sample({}, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/random.py", line 317, in sample
    raise TypeError("Population must be a sequence or set.  For dicts, use list(d).")
TypeError: Population must be a sequence or set.  For dicts, use list(d).

Due to implementation detail, choice() function expect a object that support __len__ and __getitem__ methods:

class MyDuckTypingObject:
    def __len__(self):
        return 1000000
    def __getitem__(self, key):
        return key

random.choice(MyDuckTypingObject())
212426

But 'dict_keys' object only support __len__:

{}.keys().__len__
<method-wrapper '__len__' of dict object at 0x7fecf0820630>
{"a": "a"}.keys().__len__()
1
{}.keys().__getitem__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict_keys' object has no attribute '__getitem__

Again, a solution is to call list() or tuple() instead of .keys():

random.choice(list(a))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.