从字典中访问任意元素的Python风格方法

34

我有一个字典,里面装满了项。我想窥视一个单独的任意项:

print("Amongst our dictionary's items are such diverse elements as: %s" % arb(dictionary))

我不在意选择哪个项目,它不需要是随机的

我能想到许多实现此目的的方法,但它们都似乎很浪费。我想知道是否有Python中更好的惯用语,或者(更好的是)我是否遗漏了一种方法。

def arb(dictionary):
# Creates an entire list in memory. Could take a while.
    return list(dictionary.values())[0]

def arb(dictionary):
# Creates an entire iterator. An improvement.
    for item in dictionary.values():
        return item

def arb(dictionary):
# No iterator, but writes to the dictionary! Twice!
    key, value = dictionary.popitem()
    dictionary[key] = value
    return value

我处于这样一个位置:性能还不够重要,所以这并不重要(目前),因此我可能会被指责为过早优化,但我正在努力改进我的Python编码风格,所以如果有一个易于理解的变体,采用它将是好的。


7
dictionary.itervalues().next()是关键。这至少比你的第二个arb函数更好。 - srgerg
1
@srgerg:除非你使用next(dictionary.itervalues());这是推荐的风格,并且具有一些好处(对于Python 3兼容性没有变化-其中next方法变为__next__-以及默认值的可能性)。 - Chris Morgan
1
@carrot-top:没有这个要求。 - Oddthinking
1
当你知道自己不需要做额外的工作时,不去做并不是过早优化,而是高效率。 - Chris Morgan
3
如果你想查看一个“项”(与“值”相对),你应该使用 iteritems 而不是 itervalues。/吹毛求疵/ - moooeeeep
显示剩余2条评论
4个回答

38

与您的第二个解决方案类似,但在我看来稍微更加明显:

return next(iter(dictionary.values()))

这在Python 2和Python 3中都有效,但在Python 2中,更高效的方法是这样的:

return next(dictionary.itervalues())

5
需要注意的是,如果字典为空,则会引发StopIteration异常。 - yak
6
如果您想避免StopIteration错误,可以指定一个默认值,例如 next(dictionary.itervalues(), None) - Chris Morgan
2
我不确定我认为它是“更明显了”,但是将其适合于单个表达式的确切点数。 - Oddthinking
2
@Oddthinking:如果你只想从迭代器中获取单个元素,那么使用next是最好的选择。没有必要使用for a in b: return c或在循环的第一次迭代中使用无条件的break语句。 - Chris Morgan
3
我已经更新了你的回答以适应现代 Python 3,因为这仍然是一个受欢迎的问题。如果你对我所做的更改不满意,可以随意回滚或根据自己的喜好进行编辑。 - Aran-Fey
显示剩余5条评论

11

避免整个values/itervalues/viewvalues混乱问题,这个方法在Python2和Python3中同样有效。

dictionary[next(iter(dictionary))]

如果您喜欢生成器表达式,也可以选择这种方式。

next(dictionary[x] for x in dictionary)

你可以简单地执行next(iter(dictionary.values())) - Ma0
@Ev.Kounis,在Python2中,这会创建一个额外的列表。 - John La Rooy
map 在 Python 2 中也会创建一个列表。 - user2357112
@user2357112,啊是的,你说得对。我会删除那个,因为有些人仍在尝试支持2和3。 - John La Rooy

3

我相信这个问题已经得到了很好的回答,但希望这个比较能够阐明“清洁代码”与时间之间的权衡:

from timeit import timeit
from random import choice
A = {x:[y for y in range(100)] for x in range(1000)}
def test_pop():
    k, v= A.popitem()
    A[k] = v

def test_iter(): k = next(A.iterkeys())

def test_list(): k = choice(A.keys())

def test_insert(): A[0] = 0

if __name__ == '__main__':
    print('pop', timeit("test_pop()", setup="from __main__ import test_pop", number=10000))
    print('iter', timeit("test_iter()", setup="from __main__ import test_iter", number=10000))
    print('list', timeit("test_list()", setup="from __main__ import test_list", number=10000))
    print('insert', timeit("test_insert()", setup="from __main__ import test_insert", number=10000))

以下是结果:
('pop', 0.0021750926971435547)
('iter', 0.002003908157348633)
('list', 0.047267913818359375)
('insert', 0.0010859966278076172)

似乎使用iterkeys仅比弹出并重新插入项快一点,但比创建列表并从中选择随机对象快10倍。

1
为什么不使用random
import random

def arb(dictionary):
    return random.choice(dictionary.values())

这使得结果纯粹是任意的,而不是实现副作用。在性能成为实际问题之前,始终选择清晰度而非速度。

很遗憾dict_values不支持索引,如果能够传递值视图将会很好。

更新:由于每个人都如此着迷于性能,上述函数仅需要<120ms即可从包含100万项的字典中返回随机值。依赖清晰的代码并不会像它被描述的那样对性能造成惊人的影响。


3
如果已经明确指定所选择的元素不需要是随机的,那么这样做就是浪费时间。对于这种情况,文档字符串(和名称!)已经足够了。 - Chris Morgan
1
如果名称是“任意的”,并且操作是迭代键,则该名称对我来说不太清晰,因此需要使用文档字符串。如果必须编写文档字符串来解释代码为什么做某些与其表面看起来不同的事情,那么也许答案是编写更清晰的代码。 - Matthew Trevor
1
120毫秒对于这样的操作来说是完全不能接受的。这个操作应该在一微秒以下完成。如果你把它放在之前使用d[next(iter(d))]的地方,你的应用程序可能会变得慢上百万倍。 - user2357112
在现代Python中,你会得到: TypeError: 'dict_values' object is not subscriptable。这个可以代替它:random.choice(list(dictionary.values())) 但这很方便,因为你可以采样不同的任意元素。 - nealmcb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接