[英]Why is key in dict() faster than dict.get(key) in Python3?
我想检查字典中是否存在键。 据我所知,最合适的方法是: if d_.get(s):
。 但是,当我尝试在 Leetcode 上提问时,我在使用这种方法时出现了 TLE 错误。 但是,当我尝试if s in d_
时,TLE 消失了。 我想知道为什么in
比get()
快。
我尝试了一些问题,发现这个问题有d_.get()
v/s d_[s]
的解释。 s in d_
没有一个问题涉及d_.get()
v/s s。
以防万一,一些上下文:
if self.memo.get(s):
失败的代码:
from typing import List
class Solution:
def __init__(self):
self.word_dict = {}
self.memo = {}
def word_break(self, s):
if not s:
return True
if self.memo.get(s):
return self.memo[s]
res = False
for word in self.word_dict.keys():
if len(word) <= len(s) and s[:len(word)] == word:
res = res or self.word_break(s[len(word):])
self.memo[s] = res
return res
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
for word in wordDict:
self.word_dict[word] = 1
return(self.word_break(s))
代码比if s in self.memo
接受:
from typing import List
class Solution:
def __init__(self):
self.word_dict = {}
self.memo = {}
def word_break(self, s):
if not s:
return True
if s in self.memo:
return self.memo[s]
res = False
for word in self.word_dict.keys():
if len(word) <= len(s) and s[:len(word)] == word:
res = res or self.word_break(s[len(word):])
self.memo[s] = res
return res
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
for word in wordDict:
self.word_dict[word] = 1
return(self.word_break(s))
我一直认为in
会比获取属性慢(这里是get()
)。
使用链接问题中的dis.dis
方法:
>>> import dis
>>> dis.dis(compile('d.get(key)', '', 'eval'))
1 0 LOAD_NAME 0 (d)
2 LOAD_METHOD 1 (get)
4 LOAD_NAME 2 (key)
6 CALL_METHOD 1
8 RETURN_VALUE
>>> dis.dis(compile('key in d', '', 'eval'))
1 0 LOAD_NAME 0 (key)
2 LOAD_NAME 1 (d)
4 COMPARE_OP 6 (in)
6 RETURN_VALUE
我们可以清楚地看到d.get(key)
必须再运行一个步骤: LOAD_METHOD
步骤。 此外, d.get
必须处理更多信息:它必须:
None
)。 此外,通过查看in
的C 代码和.get
的 C 代码,我们可以看到它们非常相似。
int static PyObject *
PyDict_Contains(PyObject *op, PyObject *key) dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
{ {
Py_hash_t hash; PyObject *val = NULL;
Py_ssize_t ix; Py_hash_t hash;
PyDictObject *mp = (PyDictObject *)op; Py_ssize_t ix;
PyObject *value;
if (!PyUnicode_CheckExact(key) || if (!PyUnicode_CheckExact(key) ||
(hash = ((PyASCIIObject *) key)->hash) == -1) { (hash = ((PyASCIIObject *) key)->hash) == -1) {
hash = PyObject_Hash(key); hash = PyObject_Hash(key);
if (hash == -1) if (hash == -1)
return -1; return NULL;
} }
ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value); ix = (self->ma_keys->dk_lookup) (self, key, hash, &val);
if (ix == DKIX_ERROR) if (ix == DKIX_ERROR)
return -1; return NULL;
return (ix != DKIX_EMPTY && value != NULL); if (ix == DKIX_EMPTY || val == NULL) {
} val = default_value;
}
Py_INCREF(val);
return val;
}
事实上,它们几乎相同,但.get
的开销更大,并且必须返回一个值。
但是,如果 hash 已知, d in key
似乎会使用更快的方法,而d.get
每次都重新计算 hash。 此外, CALL_METHOD
和LOAD_METHOD
的开销比COMPARE_OP
高得多,后者执行内置的 boolean 操作之一。 请注意,COMPARE_OP 将直接跳转到此处。
时间开销在于显式调用方法,而不是让语言结构来处理它。 我们可以用timeit
来证明这一点:
>>> timeit.timeit('"__name__" in x', 'x = globals()')
0.037103720999766665
>>> timeit.timeit('x.__contains__("__name__")', 'x = globals()')
0.07471312899997429
>>> timeit.timeit('x["__name__"]', 'x = globals()')
0.03828814600001351
>>> timeit.timeit('x.__getitem__("__name__")', 'x = globals()')
0.07529343100031838
>>> timeit.timeit('x.get("__name__")', 'x = globals()')
0.08261531900006958
我最初开始试图通过分别查看__contains__()
和.get()
的源代码来找出区别,结果发现它们几乎相同,除了.get()
增加对象的引用计数(应该或多或少可以忽略不计)。 当然,没有足够的差异来解释您将看到的时差。
但是,在进行测试时,我们可以看到实际使用语言结构( in
和[]
)而不是它们会变成的显式方法调用(分别为__contains__()
和__getitem__()
)快了整整 50%。
一个完整的调查将花费一些时间和比我愿意花费更多的精力,但我假设这是由于一些内置的加速和解释器应用的跳过步骤 - 使用语言结构而不是显式调用方法缩小了水平可以预期的复杂性,并且解释器可以直接跳转到 C 代码,而无需首先调用该方法的开销。
正如@rassar 的回答所表明的那样,事实上,这基本上就是发生的事情。
这两段代码不做同样的事情。 注意self.memo
是如何设置的:
self.memo[s] = res
如果res
为False
,则get
的if
语句将失败,而in
的if
语句将成功。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.