获取键列表的值列表

251
有没有一种内置/快速的方法可以使用一个键列表来获取对应项的列表?
例如,我有以下内容:
>>> mydict = {'one': 1, 'two': 2, 'three': 3}
>>> mykeys = ['three', 'one']

如何使用mykeys来获取字典中对应的值并以列表形式返回?
>>> mydict.WHAT_GOES_HERE(mykeys)
[3, 1]
13个回答

283

列表推导式似乎是实现这个的好方法:

>>> [mydict[x] for x in mykeys]
[3, 1]

2
如果mydict是一个函数调用(返回一个字典),那么这会多次调用该函数,对吗? - endolith
1
@endolith 是的,它会。 - Eric Romrell
2
好的回答,谢谢!你是如何在2013年8月26日21:45提问并在同一时间回答的? - MJimitater
7
@MJimitater,他比Python编译器更快。 - SumanKalyan
@MJimitater 他们[回答了自己的问题] (/help/self-answer)。 - wjandrea
同志们,别再添加检查以避免 KeyError 了。除非你期望并希望那种行为,否则没有必要这样做。 - juanpa.arrivillaga

127

除了列表推导式之外,还有几种方法:

  • 构建列表,并在未找到键时引发异常:map(mydict.__getitem__, mykeys)
  • 使用get方法构建列表并在未找到键时用None填充:map(mydict.get, mykeys)

另外,可以使用operator.itemgetter返回一个元组:

from operator import itemgetter
myvalues = itemgetter(*mykeys)(mydict)
# use `list(...)` if list is required

注意:在 Python3 中,map 返回一个迭代器而不是列表。使用 list(map(...)) 转换为列表。


不要直接调用 mydict.__getitem__(),而是使用生成器表达式:(mydict[key] for key in mykeys)。或者对于 list(map(...)),使用列表推导式:[mydict[key] for key in mykeys] - wjandrea

70

一点速度比较:

Python 2.7.11 |Anaconda 2.4.1 (64-bit)| (default, Dec  7 2015, 14:10:42) [MSC v.1500 64 bit (AMD64)] on win32
In[1]: l = [0,1,2,3,2,3,1,2,0]
In[2]: m = {0:10, 1:11, 2:12, 3:13}
In[3]: %timeit [m[_] for _ in l]  # list comprehension
1000000 loops, best of 3: 762 ns per loop
In[4]: %timeit map(lambda _: m[_], l)  # using 'map'
1000000 loops, best of 3: 1.66 µs per loop
In[5]: %timeit list(m[_] for _ in l)  # a generator expression passed to a list constructor.
1000000 loops, best of 3: 1.65 µs per loop
In[6]: %timeit map(m.__getitem__, l)
The slowest run took 4.01 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 853 ns per loop
In[7]: %timeit map(m.get, l)
1000000 loops, best of 3: 908 ns per loop
In[33]: from operator import itemgetter
In[34]: %timeit list(itemgetter(*l)(m))
The slowest run took 9.26 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 739 ns per loop

所以列表推导式和itemgetter是最快的方法来做到这一点。

更新

对于大型随机列表和映射,我的结果有些不同:

Python 2.7.11 |Anaconda 2.4.1 (64-bit)| (default, Dec  7 2015, 14:10:42) [MSC v.1500 64 bit (AMD64)] on win32
In[2]: import numpy.random as nprnd
l = nprnd.randint(1000, size=10000)
m = dict([(_, nprnd.rand()) for _ in range(1000)])
from operator import itemgetter
import operator
f = operator.itemgetter(*l)

%timeit f(m)
1000 loops, best of 3: 1.14 ms per loop

%timeit list(itemgetter(*l)(m))
1000 loops, best of 3: 1.68 ms per loop

%timeit [m[_] for _ in l]  # list comprehension
100 loops, best of 3: 2 ms per loop

%timeit map(m.__getitem__, l)
100 loops, best of 3: 2.05 ms per loop

%timeit list(m[_] for _ in l)  # a generator expression passed to a list constructor.
100 loops, best of 3: 2.19 ms per loop

%timeit map(m.get, l)
100 loops, best of 3: 2.53 ms per loop

%timeit map(lambda _: m[_], l)
100 loops, best of 3: 2.9 ms per loop

因此,在这种情况下,明显的优胜者是f = operator.itemgetter(*l); f(m),而鲜明的局外人是:map(lambda _: m[_], l)

更新 Python 3.6.4

import numpy.random as nprnd
l = nprnd.randint(1000, size=10000)
m = dict([(_, nprnd.rand()) for _ in range(1000)])
from operator import itemgetter
import operator
f = operator.itemgetter(*l)

%timeit f(m)
1.66 ms ± 74.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit list(itemgetter(*l)(m))
2.1 ms ± 93.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit [m[_] for _ in l]  # list comprehension
2.58 ms ± 88.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit list(map(m.__getitem__, l))
2.36 ms ± 60.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit list(m[_] for _ in l)  # a generator expression passed to a list constructor.
2.98 ms ± 142 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit list(map(m.get, l))
2.7 ms ± 284 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit list(map(lambda _: m[_], l)
3.14 ms ± 62.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

因此,Python 3.6.4的结果几乎相同。


18

以下是三种方法。

当未找到键时引发KeyError

result = [mapping[k] for k in iterable]

缺失键的默认值。

result = [mapping.get(k, default_value) for k in iterable]

跳过缺失的键。

result = [mapping[k] for k in iterable if k in mapping]

在Python 2.7中,found_keys = mapping.keys() & iterable会导致TypeError: unsupported operand type(s) for &: 'list' and 'list'错误。而found_keys = [key for key in mapping.keys() if key in iterable]是更好的选择。 - NotGaeL

10

试一下:

mydict = {'one': 1, 'two': 2, 'three': 3}
mykeys = ['three', 'one','ten']
newList=[mydict[k] for k in mykeys if k in mydict]
print newList
[3, 1]

1
"if k in mydict"这部分会使其过于宽松——如果列表比字典中的键更广泛,但正确,则会悄悄地失败(比较狭窄,但不正确)。 - mirekphd

8

试试这个:

mydict = {'one': 1, 'two': 2, 'three': 3}
mykeys = ['three', 'one'] # if there are many keys, use a set

[mydict[k] for k in mykeys]
=> [3, 1]

@PeterDeGlopper 你有点混淆了。items() 是首选,它不需要进行额外的查找,这里没有 len(mydict)*len(mykeys) 的操作!(请注意我正在使用一个集合) - Óscar López
@ÓscarLópez,是的,有一个问题,你正在检查字典的每个元素。iteritems不会在您需要它们之前生成它们,因此它避免了构建中间列表,但仍然对mydict中的每个k运行'k in mykeys'(顺序为len(mykeys),因为它是一个列表)。与简单的列表推导相比,完全没有必要只运行mykeys。 - Peter DeGlopper
@inspectorG4dget @PeterDeGlopper 我在使用一个集合(set),而不是列表(list),因此mykeys上的成员操作是摊销常数时间。 - Óscar López
2
将OP的列表转换为集合至少使其成为线性结构,但它仍然是错误数据结构上的线性结构,并且会失去顺序。考虑一个包含10k个字典和2个mykeys键的情况。您的解决方案需要进行10k次集合成员测试,而简单列表推导式只需要两个字典查找。通常可以安全地假设键的数量将小于字典元素的数量 - 如果不是,则您的方法将省略重复的元素。 - Peter DeGlopper

6
new_dict = {x: v for x, v in mydict.items() if x in mykeys}

这似乎是相反的。那么这样怎么样?new_dict = {x: mydict[x] for x in mykeys if x in mydict} - wjandrea
无论如何,OP想要一个列表,而不是字典。 - wjandrea

0

Pandas非常优雅地完成了这个任务,虽然列表推导式始终更符合Python的技术规范。我现在没有时间进行速度比较(稍后我会回来并加入):

import pandas as pd
mydict = {'one': 1, 'two': 2, 'three': 3}
mykeys = ['three', 'one']
temp_df = pd.DataFrame().append(mydict)
# You can export DataFrames to a number of formats, using a list here. 
temp_df[mykeys].values[0]
# Returns: array([ 3.,  1.])

# If you want a dict then use this instead:
# temp_df[mykeys].to_dict(orient='records')[0]
# Returns: {'one': 1.0, 'three': 3.0}

0
如果你想在尝试访问字典中的键之前确保它们存在,你可以使用set类型。将字典的键和你请求的键转换为集合。然后使用issubset()方法。
mydict = {'one': 1, 'two': 2, 'three': 3}
mykeys = ['three', 'one']
assert set(mykeys).issubset(set(mydict.keys()))
result = [mydict[key] for key in mykeys]

0
一对新的答案。
(1)如果您的类似字典的对象需要构建,我发现这种方式很有用,这样它只会被构建一次:
train_data, train_labels, test_data, test_labels = [
    d[k] for d in [numpy.load('classify.npz')]
    for k in 'train_data train_labels test_data test_labels'.split()]

(2) Python 3.10具有字典模式匹配,可以实现以下类型的模式匹配。
match numpy.load('classify.npz'):
    case {
        'train_data': train_data,
        'train_labels': train_labels,
        'test_data': test_data,
        'test_labels': test_labels}: pass
    case _: raise KeyError()

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