如何在dictionary.keys()中键入比字典中的键慢?

CIs*_*ies 2 python performance dictionary python-3.x

在阅读Michael Driscoll的Python 101时,我得到了关于检查字典中是否存在密钥的解释.我在我的机器上检查了它,其中包含一个字典,其中包含值为其顺序的键,以及一个测量检索键时间的函数(随机选取)'a''z''t'

这是我的功能:

def f(d, flag):
start_time = time.time()
if flag:
    print("t" in d)
else:
    print("t" in d.keys())
print("--- %s seconds ---" % (time.time() - start_time))
Run Code Online (Sandbox Code Playgroud)

以下是结果:

>>> f(dict,True)
True
--- 0.03937530517578125 seconds ---

>>> f(dict,False)
True
--- 0.05114388465881348 seconds ---
Run Code Online (Sandbox Code Playgroud)

但是,我仍然没有得到它.我认为这key in dict.keys()将导致迭代更小的集合,这将更快.是否有一些特殊的实施inkeys()导致这种情况?

Mar*_*ers 8

使用dictionary.keys()速度较慢,因为它做的工作更多:

  • 它添加了一个属性查找; dictionary.keys
  • 它添加了一个方法call(keys()),要求将当前调用帧压入堆栈,然后弹出.
  • 必须为返回值(字典视图)创建另一个对象.它是一个轻量级的对象,但你仍然需要在堆上为它分配内存.

这些都不是必需的,因为针对字典的包含测试和键上的字典视图测试完全相同的事情.直接在字典上进行包含测试不包括值,在两种情况下都只测试密钥.

dict()对象文档:

key in d
True如果d有钥匙钥匙,则 返回False.

请注意,使用步行时间不是测试性能差异的好方法.使用该timeit模块,选择性能最佳的定时器,禁用GC以消除偏斜源,并多次重复测试以最小化系统偏斜.

您可以通过单独测试上述附加步骤(将调用和对象创建合并为一个)来重现时差.默认情况下,timeit.timet()重复测试100万次并返回所用的总时间:

>>> import timeit
>>> from string import ascii_lowercase
>>> d = {l: i for i, l in enumerate(ascii_lowercase)}
>>> 't' in d
True
>>> timeit.timeit('d.keys', globals={'d': d})
0.0439452639548108
>>> timeit.timeit('keys()', globals={'keys': d.keys})
0.06267352704890072
Run Code Online (Sandbox Code Playgroud)

因此,只查看.keys属性100万次已经需要44毫秒,而调用方法(没有属性查找)又增加了63毫秒.这两种方法都有一些查询全局名称的开销:

>>> timeit.timeit('d', globals={'d': d})
0.027833244064822793
Run Code Online (Sandbox Code Playgroud)

因此可以预期两种方法之间会有107 - 28 == 79ms(大致)的差异.

事实上,使用't' in d和之间的时差't' in d.keys()差不多:

>>> timeit.timeit('"t" in d.keys()', globals={'d': d})
0.11647015693597496
>>> timeit.timeit('"t" in d', globals={'d': d})
0.0370339349610731
Run Code Online (Sandbox Code Playgroud)

正如预测的那样,116-37是79毫秒.