是否有更快的方法从字典中获取多个键?

时间:2018-03-29 12:14:36

标签: python performance dictionary key-value

我有一本字典:

d = {'a':1, 'b':2, 'c':3, 'd':4}

然后我有一个键列表:

l = ['a', 'b', 'z']

我想要的结果是:

[1, 2, None]

我到目前为止所做的是:

[d.get(k) for k in l]

有更快的方法吗?也许没有for

3 个答案:

答案 0 :(得分:13)

您可以使用:

>>> list(map(d.get, l))
[1, 2, None]

它有两个优点:

  • 它只执行一次d.get查询 - 而不是每次迭代
  • 只有CPython:因为dict.get是用C实现的,而map是用C实现的,所以它可以避免函数调用中的Python层(粗略地说,细节有点复杂)。

至于时间(在Jupyter笔记本中的Python 3.6上执行):

d = {'a':1, 'b':2, 'c':3, 'd':4}
l = ['a', 'b', 'z']

%timeit list(map(d.get, l))
594 ns ± 41.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit [d.get(k) for k in l]
508 ns ± 17.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

请注意,在这种情况下,这实际上更慢!这是因为对于短迭代,maplist开销占主导地位。因此,如果您希望在短期迭代中更快地坚持您的方法。

如果l更长,您会看到list(map(...))最终变得更快:

d = {'a':1, 'b':2, 'c':3, 'd':4}
l = [random.choice(string.ascii_lowercase) for _ in range(10000)]

%timeit list(map(d.get, l))
663 µs ± 64.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit [d.get(k) for k in l]
1.13 ms ± 7.55 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

然而,这仍然是"只是"比较快2倍。

答案 1 :(得分:5)

重要的是要注意这里的瓶颈不是字典的大小,而是键列表的大小(当然还有方法查找时间,哈希查找键的时间,等)

请考虑以下事项:

from timeit import Timer

d = {1: 'a'}
keys = [1 for _ in range(1000)]

def _map():
    return list(map(d.get, keys))

def _map_single_lookup():
    g = d.get
    return list(map(g, keys))

def _list_comp():
    return [d.get(key) for key in keys]

def _list_comp_single_lookup():
    g = d.get
    return [g(key) for key in keys]

print(min(Timer(_map).repeat(100, 100)))
print(min(Timer(_map_single_lookup).repeat(100, 100)))
print(min(Timer(_list_comp).repeat(100, 100)))
print(min(Timer(_list_comp_single_lookup).repeat(100, 100)))

#  0.009307396466818774
#  0.009261678214412816
#  0.018456645101335933
#  0.011634828724497837

答案 2 :(得分:2)

更快的替代方法是将itemgetterdefaultdict一起使用(因为如果密钥不存在,itemgetter不支持dict.get之类的默认值)

from collections import defaultdict
from timeit import Timer
from operator import itemgetter

d = defaultdict(lambda: None)
d[1] = 'a'
keys = [1 for _ in range(1000)]

def _map():
    return list(map(d.get, keys))

def _getter():
    return list(itemgetter(*keys)(d))

print(min(Timer(_map).repeat(100, 100)))
print(min(Timer(_getter).repeat(100, 100)))

# 0.0074976040767260055
# 0.0021861597102568187

编辑为不存在的键(整数和字符串)添加了计时。对绩效没有重大影响。

from collections import defaultdict
from timeit import Timer
from operator import itemgetter

d = defaultdict(lambda: None)
d[1] = 'a'
non_existing_keys_int = [2 for _ in range(1000)]
non_existing_keys_string = ['a' for _ in range(1000)]

def get_non_existing_keys_int():
    return list(itemgetter(*non_existing_keys_int)(d))

def get_non_existing_keys_string():
    return list(itemgetter(*non_existing_keys_string)(d))

print(min(Timer(get_non_existing_keys_int).repeat(100, 100)))
print(min(Timer(get_non_existing_keys_string).repeat(100, 100)))

#  0.002647169132724954
#  0.002408539023506795