从列表生成字典的最快方法,其中键==值

时间:2018-10-04 15:35:54

标签: python list performance dictionary

我有一个清单,说:

NUM = 100
my_list = list(range(NUM))

我想生成一个dict,其中键等于值,例如:

my_dict = {item: item for item in my_list}

或:

my_dict = dict(zip(my_list, my_list))

我已经运行了一些微基准测试,看起来它们具有相似的速度,但是我希望第二个基准测试可以更快,因为循环应该在C语言中进行。

例如,以下构造:

my_dict = {key: SOMETHING for key in keys}

翻译速度更快:

my_dict = dict.fromkeys(k, SOMETHING)

所以,我的问题是:{x: x for x in my_list}是否有类似的构造?


编辑

我已经检查过dir(dict),但似乎没有朝这个方向前进(我希望它被称为dict.fromitems()之类的东西)。


编辑2

dict.fromitems()这样的方法将比此特定用例具有更广泛的应用,因为:

dict.fromitems(keys, values)

原则上可以同时替代:

{k, v for k, v in zip(keys, values)}

和:

dict(zip(keys, values))

3 个答案:

答案 0 :(得分:4)

不,没有更快的词典可用方法。

这是因为性能成本全部集中在处理迭代器中的每个项目,计算其哈希值并将密钥放入字典数据哈希表结构(包括动态增长这些结构)中。相比之下,执行字典理解字节码实在是微不足道。

dict(zip(it, it)){k: k for k in it} dict.fromkeys(it) 的速度均接近:

>>> from timeit import Timer
>>> tests = {
...     'dictcomp': '{k: k for k in it}',
...     'dictzip': 'dict(zip(it, it))',
...     'fromkeys': 'dict.fromkeys(it)',
... }
>>> timings = {n: [] for n in tests}
>>> for magnitude in range(2, 8):
...     it = range(10 ** magnitude)
...     for name, test in tests.items():
...         peritemtimes = []
...         for repetition in range(3):
...             count, total = Timer(test, 'from __main__ import it').autorange()
...             peritemtimes.append(total / count / (10 ** magnitude))
...         timings[name].append(min(peritemtimes))  # best of 3
...
>>> for name, times in timings.items():
...     print(f'{name:>8}', *(f'{t * 10 ** 9:5.1f} ns' for t in times), sep=' | ')
...
dictcomp |  46.5 ns |  47.5 ns |  50.0 ns |  79.0 ns | 101.1 ns | 111.7 ns
 dictzip |  49.3 ns |  56.3 ns |  71.6 ns | 109.7 ns | 132.9 ns | 145.8 ns
fromkeys |  33.9 ns |  37.2 ns |  37.4 ns |  62.7 ns |  87.6 ns |  95.7 ns

这是每种技术从100到1000万件的每件成本的表。随着散列表结构增长的额外成本的累积,时间增加了。

当然,dict.fromkeys()可以更快地处理项目,但并不比其他过程快一个数量级。它的(小)速度优势并非来自这里可以在C中进行迭代;区别仅在于不必每次迭代都更新值指针;所有键都指向单个值引用。

zip()较慢,因为它会构建其他对象(每个键值对创建2项元组不是免费的操作),会增加该过程中涉及的迭代器的数量,从用于字典理解和dict.fromkeys()的单个迭代器,到3个迭代器(通过dict()委托的zip()迭代,到两个单独的迭代器(用于键和值)。

没有必要在dict类中添加单独的方法来在C语言中进行处理,因为

    无论如何,
  1. 并不是一个足够普遍的用例(创建键和值相等的映射不是常见的需求)
  2. 在C语言中不会比使用字典理解 显着快。

答案 1 :(得分:0)

这是禅宗的答案。字典理解循环本身很快,这不是瓶颈。正如马丁·彼得斯(Martijn Pieters)所说的,时间是浪费的:

  1. 访问密钥和;
  2. 计算密钥的__hash__
  3. 您的__hash__可能很糟糕,并且发生了很多冲突,导致字典插入内容非常昂贵。

不用担心循环。如果构建词典需要花费很长时间,那是因为这些操作很慢。

答案 2 :(得分:-4)

使用答案here的结果,我们创建一个新类,该类将defaultdict子类化,并覆盖其 missing 属性,以允许将密钥传递给default_factory:

from collections import defaultdict
class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        else:
            ret = self[key] = self.default_factory(key)
            return ret

现在,您可以通过执行以下操作来创建所需的字典:

my_dict = keydefaultdict(lambda x: x)

然后,每当需要为不映射到自身的键进行映射时,只需更新那些值。

时间。

子类化defaultdict

%%timeit
my_dict = keydefaultdict(lambda x: x)
for num in some_numbers: my_dict[num] == num

结果:

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

词典理解

%%timeit
my_dict = {x: x for x in some_numbers}
for num in some_numbers: my_dict[num] == num

结果:

1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

当您需要访问原始值的大约17%时,这两个值变得可比。如果需要的更少,更好:

仅访问一部分原始值

子类化defaultdict

%%timeit
frac = 0.17
my_dict = keydefaultdict(lambda x: x)
for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num

结果:

770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

词典理解

%%timeit
frac = 0.175
my_dict = {x: x for x in some_numbers}
for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num

结果:

781 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)