为什么dict()中的键比听写(键)在Python3?

2024-04-25 08:23:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我想检查字典里是否有密钥。据我所知,最合适的方法是: if d_.get(s):。但是,在尝试一个关于Leetcode的问题时,当我使用这个方法时出现了一个TLE错误。然而,当我尝试if s in d_时,TLE不见了。我想知道为什么inget()快。你知道吗

我试着通过一些问题,发现this one有一个解释d_.get()v/s d_[s]。没有一个问题涉及d_.get()v/s s in d_。你知道吗

以防万一,一些背景:

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())要慢。你知道吗


Tags: inselfforgetlenreturnifdef
3条回答

使用链接问题的^{}方法:

>>> 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必须处理更多信息:它必须:

  1. 检查是否存在
  2. 如果找到了,则返回值
  3. 否则,返回指定的默认值(如果未指定默认值,则返回None)。你知道吗

另外,通过观察C code for ^{}C code for ^{},我们可以看到它们非常相似。你知道吗

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的开销更大,必须返回一个值。你知道吗

但是,如果哈希已知,d in key似乎会使用faster method,而d.get每次都会重新计算哈希。此外,^{}^{}的开销比执行内置布尔运算之一的^{}要高得多。请注意,COMPARE\u OP将直接跳转到here。你知道吗

这两段代码的作用不同。注意self.memo是如何设置的:

self.memo[s] = res

如果resFalse,则getif语句将失败,而inif语句将成功。你知道吗

时间开销是显式调用一个方法,而不是让语言构造来处理它。我们可以用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

我开始尝试通过分别查看^{}^{}的源代码来找出差异,结果发现它们几乎相同,只是.get()增加了对象的引用计数(这应该或多或少可以忽略不计)。当然,没有足够的差异来解释你所看到的时差。你知道吗

但是,在进行测试时,我们可以看到,实际使用语言构造(in[])而不是显式方法调用(它们将分别变成(__contains__()__getitem__()),速度快了整整50%。你知道吗

一个完整的调查需要花费一段时间和更多的精力,但我假设这是由于一些内置的加速和跳过了解释器应用的步骤-使用语言构造而不是显式调用方法缩小了可以预期的复杂程度,解释器可以直接跳转到C代码中,而不需要首先调用方法。你知道吗


正如@rassar的回答所表明的,事实上,这基本上就是所发生的事情。你知道吗

相关问题 更多 >