Python 字典键。 “在”复杂性
Posted
技术标签:
【中文标题】Python 字典键。 “在”复杂性【英文标题】:Python dictionary keys. "In" complexity 【发布时间】:2013-07-06 12:53:00 【问题描述】:快速提问,主要满足我对该主题的好奇心。
我正在编写一些带有 SQlite 数据库后端的大型 python 程序,并且将来会处理大量记录,因此我需要尽可能优化。
对于一些功能,我正在通过字典中的键进行搜索。我一直在使用“in”关键字进行原型设计,并计划稍后返回并优化这些搜索,因为我知道“in”关键字通常是 O(n)(因为这只是转换为 python 遍历整个列表并进行比较每个元素)。但是,作为一个 python dict 基本上只是一个哈希映射,python 解释器是否足够聪明来解释:
if(key in dict.keys()):
...code...
到:
if(dict[key] != None):
...code...
这基本上是相同的操作,但顶部是 O(n),底部是 O(1)。
在我的代码中使用底层版本对我来说很容易,但后来我只是好奇并想我会问。
【问题讨论】:
我说做最简单的事情,然后再进行分析。 其实底部的代码是行不通的。你必须做一些类似于try: dict[key]; except KeyError: pass; else: #...code...
的事情。
@TravisGD 这是个好点,我忘了
附注:不要将if
条件括在不必要的括号中。阅读和编写大量 Python 的人希望括号表示某种含义——元组、基因表达式或覆盖运算符优先级——他们必须停下来阅读每一行两次,以确保你的括号实际上没有任何意义。
另一个旁注:不要命名字典dict
——它隐藏了同名的类型和构造函数,您可能希望稍后使用。
【参考方案1】:
首先,key in d.keys()
保证为您提供与 key in d
相同的值,用于任何字典 d
。
而对dict
的in
操作,或通过调用keys()
返回的dict_keys
对象(在3.x 中)是不是 O(N ),它是 O(1)。
没有真正的“优化”;只是使用哈希是在哈希表上实现__contains__
的明显方式,就像它是实现__getitem__
的明显方式一样。
你可能会问这是在哪里保证的。
好吧,事实并非如此。 Mapping Types 基本上将dict
定义为collections.abc.Mapping
的哈希表实现。没有什么能阻止某人创建映射的哈希表实现,但仍提供 O(N) 搜索。但做出如此糟糕的实现将是额外的工作,那他们为什么会这样做呢?
如果您真的需要向自己证明这一点,您可以测试您关心的每个实现(使用分析器,或者使用带有自定义 __hash__
和 __eq__
记录调用的某种类型,或者......),或者阅读源代码。
在 2.x 中,您不想调用 keys
,因为这会生成一个 list
的键,而不是 KeysView
。您可以使用iterkeys
,但这可能会生成一个迭代器或其他不是 O(1) 的东西。因此,只需将 dict 本身用作序列即可。
即使在 3.x 中,您也不想调用 keys
,因为没有必要。迭代一个dict
,检查它的__contains__
,一般来说把它当作一个序列来处理总是等同于对它的键做同样的事情,那何必呢? (当然,构建微不足道的 KeyView
并通过它进行访问,会增加几纳秒的运行时间和几下程序的击键次数。)
(不清楚在 2.x 中使用序列操作对于 d.keys()
/d.iterkeys()
和 d
是等价的。除了性能问题之外,它们在每个 CPython 中是等价的, Jython、IronPython 和 PyPy 实现,但在 3.x 中的任何地方似乎都没有说明它。这没关系;只需使用 key in d
。)
当我们这样做时,请注意:
if(dict[key] != None):
... 是行不通的。如果key
不在dict
中,这将引发KeyError
,而不是返回None
。
另外,你不应该检查None
和==
或!=
;始终使用is
。
您可以使用try
执行此操作,或者更简单地说,使用if dict.get(key, None) is not None
。但同样,没有理由这样做。此外,这不会处理 None
是完全有效的项目的情况。如果是这种情况,您需要执行sentinel = object(); if dict.get(key, sentinel) is not sentinel:
之类的操作。
所以,正确的写法是:
if key in d:
更一般地说,这是不正确的:
我知道“in”关键字通常是 O(n)(因为这只是转换为 python 遍历整个列表并比较每个元素
in
运算符与大多数其他运算符一样,只是对 __contains__
方法(或 C/Java/.NET/RPython 内置函数的等效方法)的调用。 list
通过迭代列表并比较每个元素来实现它; dict
通过散列值并查找散列来实现它; blist.blist
通过遍历 B+Tree 来实现;等等。所以,它可能是 O(n)、O(1)、O(log n) 或完全不同的东西。
【讨论】:
这就是我的想法,这是否记录在任何地方?我不确定只是因为我认为 dict.keys() 可能只是返回一个列表。使“in” O(n) @tknickman:一般来说,Python 不会记录其函数的性能特征。 (部分原因是你总是可以做一些荒谬的事情,比如定义一个取决于元素数量的hash
函数。)所以,this 就是你所得到的。但它记录 dicts 是哈希表的事实非常强烈地暗示 key in d
、d[key]
和 d.get(key)
都将是 O(1)。
平均情况是O(1),最坏情况是O(n)。
@AshwiniChaudhary:我相信他的意思是语义上的,而不是算法上的。
@AshwiniChaudhary:保证它们在语义上是等价的。在 Python 3.x 中,它们在性能方面也相当。在 Python 2.x 中,keys
显然会更慢。我已经编辑了答案以提供更多详细信息。但真正的重点是,从来没有任何理由使用key in d.keys()
,所以你不必记住细节。【参考方案2】:
在 Python 2 中,dict.keys()
首先创建整个键列表,这就是为什么它是 O(N)
操作,而 key in dict
是 O(1)
操作。
if(dict[key] != None)
如果在 dict 中没有找到 key,则会引发 KeyError
,所以它不等同于第一个代码。
Python 2 结果:
>>> dic = dict.fromkeys(range(10**5))
>>> %timeit 10000 in dic
1000000 loops, best of 3: 170 ns per loop
>>> %timeit 10000 in dic.keys()
100 loops, best of 3: 4.98 ms per loop
>>> %timeit 10000 in dic.iterkeys()
1000 loops, best of 3: 402 us per loop
>>> %timeit 10000 in dic.viewkeys()
1000000 loops, best of 3: 457 ns per loop
在 Python 3 中,dict.keys()
返回一个视图对象,它比 Python 2 的 keys()
快得多,但仍然较慢简单普通 key in dict
:
Python 3 结果:
>>> dic = dict.fromkeys(range(10**5))
>>> %timeit 10000 in dic
1000000 loops, best of 3: 295 ns per loop
>>> %timeit 10000 in dic.keys()
1000000 loops, best of 3: 475 ns per loop
只使用:
if key in dict:
#code
【讨论】:
这是特定于 2.x 的。 (另外,请注意,在 CPython 2.7.3 或 PyPy 2.0b1 中,iterkeys
可能比 keys
快得多——Python 2.x 允许 iterkeys
比 iter(d.keys())
更智能,它们实际上确实需要一些优势。但它仍然远不及直接使用d
快。在我的计算机上,它是 94ns vs. 338us vs. 2.03ms。)【参考方案3】:
这样做的正确方法是
if key in dict:
do stuff
in 运算符对于 python 中的字典和集合来说是 O(1)。
【讨论】:
应该修改最后一句以将其限制在字典(和集合)中,因为x in a_list
是 O(n)。【参考方案4】:
dict 的 in 运算符的平均案例时间复杂度为 O(1)。有关其他 dict() 方法的时间复杂度的详细信息,请访问此link。
【讨论】:
虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。【参考方案5】:兄弟你可以这样写...所以你也不需要检查异常,我的方法时间复杂度是 O(1)。
if (dict.get(key) != None):
....code.....enter code here
【讨论】:
您的语法错误。您应该使用括号而不是方括号以上是关于Python 字典键。 “在”复杂性的主要内容,如果未能解决你的问题,请参考以下文章
无需借助 csv 文件即可保存和加载复杂的 python 字典