为什么 `key in dict` 和 `key in dict.keys()` 的输出结果相同?

22

我试图在字典中搜索关键字,但是我忘记添加 keys() 函数。尽管如此,我仍然得到了预期的答案。

为什么这两个表达式的结果相同?

key in dict

key in dict.keys()

1
你是否查阅了文档 - wwii
@Shashank <dict>.__iter__ 不同于 <dict>.keys() -- 在 Python 2 中,keys 返回一个列表,在 Python 3 中,keys 返回一个“视图”。在 Python 2 中,<dict>.__iter__ 等同于 <dict>.iterkeys()。此外,在对象中实现 __iter__ 不需要使用元类或内部类。 - jedwards
3
我知道这只是一个例子,但请不要使用"dict"作为变量名,因为它会覆盖内置的"dict"类型。同样适用于"list"、"str"、"set"等。 - PM 2Ring
2个回答

43
为了理解为什么key in dct返回的结果与key in dct.keys()相同,需要回顾一下历史。在Python 2中,人们通常使用dct.has_key(key)来测试字典dct中是否存在一个。这在Python 2.2时发生了改变,推荐的方式变成了key in dct,其基本上做了同样的事情:

在一个次要相关的更改中,in运算符现在可以用于字典,因此key in dict现在等同于dict.has_key(key)

in的行为是通过内部实现的__contains__方法来实现的。其行为在Python语言参考-3数据模型中有记录:

object.__contains__(self, item)

用于实现成员测试操作符。如果item在self中返回true,否则返回false。对于映射对象,这应该考虑映射的键而不是值或键值对。对于没有定义__contains__()的对象,成员测试首先尝试通过__iter__()进行迭代,然后是旧的序列迭代协议通过__getitem__(),请参阅语言参考中的此部分。

(强调我的; Python中的字典是映射对象)
在Python 3中,已经完全删除了has_key方法,现在唯一正确的测试键是否存在的方法是key in dict,如文档所述,保留a标签链接
与前面两个不同,key in dct.keys() 从来都不是测试字典中是否存在键的正确方法。你示例的结果确实相同,但在Python 3上,key in dct.keys() 稍微慢一些,在Python 2上则非常慢。
如果在字典 dct 中找到一个键 keykey in dct 将返回true,这几乎是一个恒定时间操作 - 不管有两个还是一百万个键 - 它的时间复杂度在平均情况下是常数级别(O(1))。
在Python 2中,dct.keys() 创建一个包含所有键的列表;在Python 3中创建一个 视图,这两个对象都可以理解 key in x。在Python 2中,它就像对于任何可迭代对象一样工作;值被迭代,并且只要一个值等于给定值(这里是key),就会返回True
实际上,在Python 2中,key in dct.keys()key in dict要慢得多(key in dct.keys()的时间复杂度与键的数量成线性比例增长,为O(n) - dct.keys()key in key_list都是O(n))。在Python 3中,由于view不会创建键列表,因此key in dct.keys()的速度不会比key in dct慢太多,访问仍然是O(1),但实际上至少会慢一定的常数值,并且它多了7个字符,所以通常没有使用它的实际理由,即使在Python 3上也是如此。

运行 key in dict 与 key in dict.keys() 的时间复杂度是有价值的。 - kashiraja

4

Python数据模型规定,通常情况下成员测试是通过对序列进行迭代来实现的,除非容器对象提供了特殊方法__contains__

正如文档中进一步提到的那样,对于没有实现__contains__特殊方法的对象,成员测试首先尝试通过__iter__()进行迭代,然后再尝试通过__getitem__()进行旧的序列迭代协议。

重要的是要知道,对于字典而言,dict.keys()返回的(Python 3.X)要么是一个迭代器,要么是一个字典视图,而在Python (2.X)中则返回一个序列(更确切地说是一个列表)。对于序列/列表的成员测试,其复杂度为O(n),而对于像哈希映射这样实现的字典对象或支持成员测试和迭代等操作的字典视图,则其复杂度为O(1)。
因此,在Python 2.X中,两者之间存在明显的差异,这可能会影响性能,而在Python 3.X中,则只有额外的函数调用开销。
无论如何,始终应优先使用字典对象上的成员资格,而不是使用dict.keys返回的字典视图或序列上的成员测试。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接