Python 从key==value的列表生成dict的最快方法

Python 从key==value的列表生成dict的最快方法,python,list,performance,dictionary,Python,List,Performance,Dictionary,我有一份清单,比如: NUM = 100 my_list = list(range(NUM)) 我想生成一个dict,其中键等于值,类似于: my_dict = {item: item for item in my_list} 或: 我已经运行了一些微基准测试,看起来它们的速度类似,但我希望第二个会快得多,因为循环应该在C中发生 例如,以下构造: my_dict = {key: SOMETHING for key in keys} 转化为更快的速度: my_dict = dict.from

我有一份清单,比如:

NUM = 100
my_list = list(range(NUM))
我想生成一个
dict
,其中键等于值,类似于:

my_dict = {item: item for item in 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.fromtimes()
这样的方法比这个特定用例有更广泛的应用,因为:

dict.fromitems(keys, values)
原则上可以替代以下两种:

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


使用答案的结果,我们创建了一个新类,该类子类为defaultdict,并覆盖其缺失的属性,以允许将密钥传递给默认工厂:

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
%%timeit
frac = 0.17
my_dict = keydefaultdict(lambda x: x)
for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num
结果:

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
781 ms ± 4.03 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
%%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
结果:

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
781 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
当您最终需要访问大约17%的原始值时,这两个值将具有可比性。如果您需要的更少,则更好:

仅访问部分原始值

子类化
defaultdict

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

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
781 ms ± 4.03 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
%%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
结果:

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
781 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

不,字典没有更快的方法

这是因为性能成本都是在处理迭代器中的每个项、计算其散列并将键插入字典数据散列表结构(包括动态增长这些结构)中。相比之下,执行字典理解字节码实际上是微不足道的

dict(zip(it,it))
{k:k代表it中的k}
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
这是每种技术的每项成本表,从1亿到1000万项。随着哈希表结构增长的额外成本的累积,计时也随之增加

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

zip()
速度较慢,因为它构建了额外的对象(为每个键值对创建一个2项元组不是一个无成本的操作),并且它增加了过程中涉及的迭代器的数量,您可以从一个迭代器理解字典和
dict.fromkeys()
,到3个迭代器(
dict())
通过
zip()
将迭代委托给两个单独的迭代器(用于键和值)

在C语言中,向
dict
类添加一个单独的方法来处理这个问题是没有意义的,因为

  • 无论如何,这不是一个足够常见的用例(创建键和值相等的映射不是常见的需要)
  • 无论如何,C语言的理解速度不会比字典理解速度快很多

  • 这是禅宗的答案。字典理解循环本身很快,这不是瓶颈。正如Martijn Pieters所说,时间是花在:

  • 访问密钥和密码
  • 计算密钥的
    \uuuuuuuuuuuuuuuuu
  • 可能您的
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

  • 不要担心循环。如果建立字典需要很长时间,那是因为这些操作很慢。

    不,没有。我真的不明白为什么有人一开始就需要这样的东西。。。你能给我们一个用例吗?也许我们可以想出一个更好的方法来做你想做的事情dict(zip)版本会进行两个函数调用(尽管它们是C调用,所以比Python调用快)但是
    zip
    必须构建一组元组,虽然这是一个廉价的操作,但dict comp避免了这一点。时间问题是你的代码的瓶颈吗?还有,弗雷德说的。你打算在任何阶段更改值吗?如果不是,为什么不使用一个集合呢?哇,这深入到了内部!当你说
    将默认dict子类化时de>你的答案中的
    my_dict
    是什么类型的?它是普通的dict吗?你能不能
    timeit
    并在所提供的例子上给出一个基准?你能添加如何传递列表吗?@devssh
    my_dict
    将是所有的一个实例:
    dict
    defaultdict
    ,和
    keydefaultdict
    请更具体地说明如何将其用于给定的
    列表
    ?另外,请您生成一些计时?这肯定不会更快,因为您为每个值添加了一个函数调用,而不是让值预先可用。Python函数调用在这里相对昂贵。您甚至不应该使用lambda。只需子类
    dict
    ,并使其
    \uuuuuuuuuuuuuuuuuuuuuuuuuuu
    只需将值设置为键。感谢您的见解。然而,我相信,从两个iterables创建
    dict
    的构造可能是一个足够大的用例,足以证明
    dict.fromtimes(键,值)的合理性
    用C实现的方法。这比理解或使用
    zip()要快得多