Python:“除了KeyError”比“if key in dict”快吗?

Posted

技术标签:

【中文标题】Python:“除了KeyError”比“if key in dict”快吗?【英文标题】:Python: is "except KeyError" faster than "if key in dict"? 【发布时间】:2013-12-16 23:55:00 【问题描述】:

编辑 2:有人建议这是一个类似问题的副本。我不同意,因为我的问题侧重于速度,而另一个问题则问什么更“可读”或“更好”(没有定义更好)。虽然问题相似,但给出的讨论/答案存在很大差异。

编辑: 我从这些问题中意识到我本来可以更清楚。 对不起,代码拼写错误,是的,它应该使用正确的 python 运算符进行添加。

关于输入数据,我只是选择了一个随机数列表,因为这是一个常见的样本。在我的例子中,我使用的是一个字典,我预计会有很多键错误,可能 95% 的键将不存在,而存在的少数键将包含数据集群。

尽管我对一般性讨论感兴趣,不管输入数据集如何,但带有运行时间的样本当然很有趣。

我的标准方法是像许多其他帖子一样写类似的东西

list =  (100 random numbers)
d = 
for x in list:
    if x in d:
        d[x]+=1
    else:
        d[x]=1

但我只是想到这会更快,因为我们不必检查字典是否包含密钥。我们只是假设它确实如此,如果不是,我们会处理它。 有什么区别还是 Python 比我聪明?

list =  (100 random numbers)
d = 
for x in list:
    try:
        d[x]+=1
    except KeyError:
        d[x] = 1

与数组中的索引、越界、负索引等相同的方法。

【问题讨论】:

你为什么不把你的发现告诉我们?但首先请注意,Python 没有后缀 ++ 运算符 ;-) 请注意,您可以只使用collections.Counter(l),或使用collections.defaultdict(int) 用于低于 2.7 的 Python 版本。 没有后缀或前缀++,所以如果您尝试++x并发现它只是意味着+(+x),请不要感到惊讶。 我们假设您在谈论 CPython:在 IronPython、Jython、PyPi 或其他 Python 实现下,结果可能会有所不同。另外,由于没有人说过:提防过早的优化。无论如何都要对事情的工作原理感到好奇,但不要花大量时间试图预先找到不会真正增加任何东西的小的性能提升。在这种情况下,我会使用defaultdict。我不知道性能如何,但对我来说更重要的是代码易于理解。 user2357112:是的,在这种情况下,我可以使用计数器作为输入数据样本,但对于真实案例中的数据,它就不合适了。也不是如何计算一组数字的问题,问题是异常发生后处理的速度,而不是每次都事先检查每个键。 【参考方案1】:

注意:纯属推测

我认为第一个会慢一些,因为它会在字典中两次定位键,首先是在 if 语句中,然后是在 C 代码中进行字典访问。当许多键不在字典中时,try-except 可能会更慢,因为处理异常需要一些开销。

我将列表设置为range(100) 并将字典留空。第一次使用 if 需要 8.003 秒,第二次使用 try-except 需要 30.976 秒!在这样的情况下,没有什么其他事情要做,开销是相当大的。

【讨论】:

计时 - 我敢打赌你会感到惊讶 ;-)【参考方案2】:

您的声明绝对错误取决于输入。

如果您有一组不同的键,并且经常点击except 块,则性能不佳。如果 try 块占主导地位,则 try/except 成语可以在较小的列表上执行。

这是一个基准,展示了做同样事情的几种方法:

from __future__ import print_function
import timeit
import random
import collections

def f1():
    d=
    for x in tgt:
        if x in d:
            d[x]+=1
        else:
            d[x]=1
    return d

def f2():
    d = 
    for x in tgt:
        try:
            d[x]+=1
        except KeyError:
            d[x] = 1    
    return d

def f3():
    d=.fromkeys(tgt, 0)
    for x in tgt:
        d[x]+=1    
    return d    


def f4():
    d=collections.defaultdict(int)
    for x in tgt:
        d[x]+=1    
    return d    

def f5():
    return collections.Counter(tgt)        

def f6():
    d=
    for x in tgt:
        d[x]=d.setdefault(x, 0)+1
    return d

def f7():
    d=
    for x in tgt:
        d[x]=d.get(x,0)+1
    return d    

def cmpthese(funcs, c=10000, rate=True, micro=False):
    """Generate a Perl style function benchmark"""                   
    def pprint_table(table):
        """Perl style table output"""
        def format_field(field, fmt=':,.0f'):
            if type(field) is str: return field
            if type(field) is tuple: return field[1].format(field[0])
            return fmt.format(field)     

        def get_max_col_w(table, index):
            return max([len(format_field(row[index])) for row in table])         

        col_paddings=[get_max_col_w(table, i) for i in range(len(table[0]))]
        for i,row in enumerate(table):
            # left col
            row_tab=[row[0].ljust(col_paddings[0])]
            # rest of the cols
            row_tab+=[format_field(row[j]).rjust(col_paddings[j]) for j in range(1,len(row))]
            print(' '.join(row_tab))                

    results=k.__name__:timeit.Timer(k).timeit(c) for k in funcs
    fastest=sorted(results,key=results.get, reverse=True)
    table=[['']]
    if rate: table[0].append('rate/sec')
    if micro: table[0].append('usec/pass')
    table[0].extend(fastest)
    for e in fastest:
        tmp=[e]
        if rate:
            tmp.append(':,'.format(int(round(float(c)/results[e]))))

        if micro:
            tmp.append(':.3f'.format(1000000*results[e]/float(c)))

        for x in fastest:
            if x==e: tmp.append('--')
            else: tmp.append(':.1%'.format((results[x]-results[e])/results[e]))
        table.append(tmp) 

    pprint_table(table)                    

if __name__=='__main__':
    import sys
    print(sys.version)
    for j in [100,1000]:
        for t in [(0,5), (0,50), (0,500)]:
            tgt=[random.randint(*t) for i in range(j)]
            print(' rand ints between :'.format(j,t))
            print('=====')
            cmpthese([f1,f2,f3,f4,f5,f6,f7])
            print()

我已经包含了一个基于timeit 的小型基准函数,它打印从最慢到最快的函数,它们之间存在百分比差异。

这是 Python 3 的结果:

3.4.1 (default, May 19 2014, 13:10:29) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)]
100 rand ints between (0, 5):
=====
   rate/sec    f6    f7     f1     f2     f3     f4     f5
f6   52,756    -- -1.6% -26.2% -27.9% -30.7% -36.7% -46.8%
f7   53,624  1.6%    -- -25.0% -26.7% -29.6% -35.7% -46.0%
f1   71,491 35.5% 33.3%     --  -2.3%  -6.1% -14.2% -28.0%
f2   73,164 38.7% 36.4%   2.3%     --  -3.9% -12.2% -26.3%
f3   76,148 44.3% 42.0%   6.5%   4.1%     --  -8.7% -23.3%
f4   83,368 58.0% 55.5%  16.6%  13.9%   9.5%     -- -16.0%
f5   99,247 88.1% 85.1%  38.8%  35.6%  30.3%  19.0%     --

100 rand ints between (0, 50):
=====
   rate/sec     f2     f6     f7     f4     f3     f1     f5
f2   39,405     -- -17.9% -18.7% -19.1% -41.8% -47.8% -56.3%
f6   47,980  21.8%     --  -1.1%  -1.6% -29.1% -36.5% -46.8%
f7   48,491  23.1%   1.1%     --  -0.5% -28.4% -35.8% -46.2%
f4   48,737  23.7%   1.6%   0.5%     -- -28.0% -35.5% -46.0%
f3   67,678  71.7%  41.1%  39.6%  38.9%     -- -10.4% -24.9%
f1   75,511  91.6%  57.4%  55.7%  54.9%  11.6%     -- -16.3%
f5   90,175 128.8%  87.9%  86.0%  85.0%  33.2%  19.4%     --

100 rand ints between (0, 500):
=====
   rate/sec     f2     f4     f6     f7     f3     f1     f5
f2   25,748     -- -22.0% -41.4% -42.6% -57.5% -66.2% -67.8%
f4   32,996  28.1%     -- -24.9% -26.4% -45.6% -56.7% -58.8%
f6   43,930  70.6%  33.1%     --  -2.0% -27.5% -42.4% -45.1%
f7   44,823  74.1%  35.8%   2.0%     -- -26.1% -41.2% -44.0%
f3   60,624 135.5%  83.7%  38.0%  35.3%     -- -20.5% -24.2%
f1   76,244 196.1% 131.1%  73.6%  70.1%  25.8%     --  -4.7%
f5   80,026 210.8% 142.5%  82.2%  78.5%  32.0%   5.0%     --

1000 rand ints between (0, 5):
=====
   rate/sec     f7     f6     f1     f3     f2     f4     f5
f7    4,993     --  -6.7% -34.6% -39.4% -44.4% -50.1% -71.1%
f6    5,353   7.2%     -- -29.9% -35.0% -40.4% -46.5% -69.0%
f1    7,640  53.0%  42.7%     --  -7.3% -14.9% -23.6% -55.8%
f3    8,242  65.1%  54.0%   7.9%     --  -8.2% -17.6% -52.3%
f2    8,982  79.9%  67.8%  17.6%   9.0%     -- -10.2% -48.1%
f4   10,004 100.4%  86.9%  30.9%  21.4%  11.4%     -- -42.1%
f5   17,293 246.4% 223.0% 126.3% 109.8%  92.5%  72.9%     --

1000 rand ints between (0, 50):
=====
   rate/sec     f7     f6     f1     f2     f3     f4     f5
f7    5,051     --  -7.1% -26.5% -29.0% -34.1% -45.7% -71.2%
f6    5,435   7.6%     -- -20.9% -23.6% -29.1% -41.5% -69.0%
f1    6,873  36.1%  26.5%     --  -3.4% -10.3% -26.1% -60.8%
f2    7,118  40.9%  31.0%   3.6%     --  -7.1% -23.4% -59.4%
f3    7,661  51.7%  41.0%  11.5%   7.6%     -- -17.6% -56.3%
f4    9,297  84.0%  71.1%  35.3%  30.6%  21.3%     -- -47.0%
f5   17,531 247.1% 222.6% 155.1% 146.3% 128.8%  88.6%     --

1000 rand ints between (0, 500):
=====
   rate/sec     f2     f4     f6     f7     f3     f1     f5
f2    3,985     -- -11.0% -13.6% -14.8% -25.7% -40.4% -66.9%
f4    4,479  12.4%     --  -2.9%  -4.3% -16.5% -33.0% -62.8%
f6    4,613  15.8%   3.0%     --  -1.4% -14.0% -31.0% -61.6%
f7    4,680  17.4%   4.5%   1.4%     -- -12.7% -30.0% -61.1%
f3    5,361  34.5%  19.7%  16.2%  14.6%     -- -19.8% -55.4%
f1    6,683  67.7%  49.2%  44.9%  42.8%  24.6%     -- -44.4%
f5   12,028 201.8% 168.6% 160.7% 157.0% 124.3%  80.0%     --

还有 Python 2:

2.7.6 (default, Dec  1 2013, 13:26:15) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)]
100 rand ints between (0, 5):
=====
   rate/sec     f5     f7     f6     f2     f1     f3     f4
f5   24,955     -- -41.8% -42.5% -51.3% -55.7% -61.6% -65.2%
f7   42,867  71.8%     --  -1.2% -16.4% -23.9% -34.0% -40.2%
f6   43,382  73.8%   1.2%     -- -15.4% -23.0% -33.2% -39.5%
f2   51,293 105.5%  19.7%  18.2%     --  -9.0% -21.0% -28.5%
f1   56,357 125.8%  31.5%  29.9%   9.9%     -- -13.2% -21.4%
f3   64,924 160.2%  51.5%  49.7%  26.6%  15.2%     --  -9.5%
f4   71,709 187.3%  67.3%  65.3%  39.8%  27.2%  10.5%     --

100 rand ints between (0, 50):
=====
   rate/sec     f2     f5     f7     f6     f4     f3     f1
f2   22,439     --  -4.7% -45.1% -45.5% -50.7% -63.3% -64.5%
f5   23,553   5.0%     -- -42.4% -42.8% -48.3% -61.5% -62.8%
f7   40,878  82.2%  73.6%     --  -0.7% -10.2% -33.2% -35.4%
f6   41,164  83.4%  74.8%   0.7%     --  -9.6% -32.7% -34.9%
f4   45,525 102.9%  93.3%  11.4%  10.6%     -- -25.6% -28.0%
f3   61,167 172.6% 159.7%  49.6%  48.6%  34.4%     --  -3.3%
f1   63,261 181.9% 168.6%  54.8%  53.7%  39.0%   3.4%     --

100 rand ints between (0, 500):
=====
   rate/sec     f2     f5     f4     f6     f7     f3     f1
f2   13,122     -- -39.9% -56.2% -63.2% -63.8% -75.8% -80.0%
f5   21,837  66.4%     -- -27.1% -38.7% -39.8% -59.6% -66.7%
f4   29,945 128.2%  37.1%     -- -16.0% -17.4% -44.7% -54.3%
f6   35,633 171.6%  63.2%  19.0%     --  -1.7% -34.2% -45.7%
f7   36,257 176.3%  66.0%  21.1%   1.8%     -- -33.0% -44.7%
f3   54,113 312.4% 147.8%  80.7%  51.9%  49.2%     -- -17.5%
f1   65,570 399.7% 200.3% 119.0%  84.0%  80.8%  21.2%     --

1000 rand ints between (0, 5):
=====
   rate/sec     f5     f7     f6     f1     f2     f3     f4
f5    2,787     -- -37.7% -38.4% -53.3% -59.9% -60.4% -67.0%
f7    4,477  60.6%     --  -1.1% -25.0% -35.6% -36.3% -47.0%
f6    4,524  62.3%   1.1%     -- -24.2% -34.9% -35.6% -46.5%
f1    5,972 114.3%  33.4%  32.0%     -- -14.1% -15.0% -29.3%
f2    6,953 149.5%  55.3%  53.7%  16.4%     --  -1.1% -17.7%
f3    7,030 152.2%  57.0%  55.4%  17.7%   1.1%     -- -16.8%
f4    8,452 203.3%  88.8%  86.8%  41.5%  21.6%  20.2%     --

1000 rand ints between (0, 50):
=====
   rate/sec     f5     f7     f6     f2     f1     f3     f4
f5    2,667     -- -37.8% -38.7% -53.0% -55.9% -61.1% -65.3%
f7    4,286  60.7%     --  -1.5% -24.5% -29.1% -37.5% -44.2%
f6    4,351  63.1%   1.5%     -- -23.4% -28.0% -36.6% -43.4%
f2    5,677 112.8%  32.4%  30.5%     --  -6.1% -17.3% -26.1%
f1    6,045 126.6%  41.0%  39.0%   6.5%     -- -11.9% -21.4%
f3    6,862 157.3%  60.1%  57.7%  20.9%  13.5%     -- -10.7%
f4    7,687 188.2%  79.3%  76.7%  35.4%  27.2%  12.0%     --

1000 rand ints between (0, 500):
=====
   rate/sec     f2     f5     f7     f6     f4     f3     f1
f2    2,018     -- -16.1% -44.1% -46.2% -53.4% -61.8% -63.0%
f5    2,405  19.1%     -- -33.4% -35.9% -44.5% -54.4% -55.9%
f7    3,609  78.8%  50.1%     --  -3.8% -16.7% -31.6% -33.8%
f6    3,753  85.9%  56.1%   4.0%     -- -13.4% -28.9% -31.2%
f4    4,334 114.7%  80.2%  20.1%  15.5%     -- -17.9% -20.5%
f3    5,277 161.5% 119.5%  46.2%  40.6%  21.8%     --  -3.2%
f1    5,454 170.2% 126.8%  51.1%  45.3%  25.8%   3.3%     --

所以——这取决于。

结论:

    Counter 方法几乎总是最慢的 Counter 方法在 Python 2 上是最慢的,但在 Python 3.4 上是迄今为止最快的 try/except 版本通常是最慢的 if key in dict 版本无疑是最好/最快的版本之一,无论大小或密钥数量如何 .fromkeys(tgt, 0) 非常可预测 defaultdict 版本在较大的列表中速度最快。列表越小,设置时间越长,因为元素太少而摊销。

【讨论】:

在这个测试中,几乎所有的迭代都命中了except 块。如果大多数访问不触发except 块,使用except 更具竞争力,甚至可以更快。这在很大程度上取决于输入。【参考方案3】:

更新: 不确定我是否在测试正确的东西,但仍然发现结果很有趣。

Python 2:

0% missing keys, Standard access: 0.419198036194
0% missing keys, try/except access: 0.309811115265
50% missing keys, Standard access: 0.417014837265
50% missing keys, try/except access: 0.309100866318
100% missing keys, Standard access: 0.416236877441
100% missing keys, try/except access: 0.310797929764

我使用 normal 和 try/except 方法测试了 3 个具有不同数量键的字典。 try/except 方法对我来说每次都更快。

我的代码:

from timeit import timeit

size = 2**10
allkeys = "0% missing keys", dict([(i, 0) for i in range(size)])
somekeys= "50% missing keys", dict([(i*2, 0) for i in range(size//2)])
nokeys = "100% missing keys", dict([])

def test_normal():
    """Standard access"""
    for i in xrange(size):
        if i in d:
            d[i] += 1
        else:
            d[i] = 1

def test_try():
    """try/except access"""
    for i in xrange(size):
        try:
            d[i] += 1
        except KeyError:
            d[i] = 1

for trial in (allkeys, somekeys, nokeys):
    d = trial[1]
    for test in (test_normal, test_try):
        trial_time = timeit("test()",
                            setup="from __main__ import test",
                            number=2**10)
        print "0, 1: 2".format(trial[0], test.__doc__, trial_time)

代码现在使用 timeit,这可能更准确。

【讨论】:

Python 3.6 测试:“try-catch”更快。【参考方案4】:

关于编码风格还有一点。由于使用 EAFP请求宽恕比许可更容易)是一种常见的 Python 编码风格,它假定存在有效键并在假设被证明为错误时捕获异常。 p>

由于这种常见的编码风格,我一直使用 try/except 方法,并且确信这比 LBYL 风格更快(三思而后行)。正如我从这里的答案中了解到的,这绝对取决于。只要您可以期待现有的密钥,我就会选择 try/except 方法。

【讨论】:

【参考方案5】:
import random
from pip._vendor.distlib.compat import raw_input

x=random.randint(1,99)
guess = int(raw_input("Enter a integer from 1 to 99:"))
    while x !="guess":
        print
        if guess<x:
            print ("guess is low")
            guess= int(raw_input("Enter a integer from 1 to 99:"))
        elif guess >x:
            print ("guess is high")
            guess = int(raw_input("Enter a integer from 1 to 99:"))
        else:
            print (" you guessed it !")
        break
        print

【讨论】:

不仅仅是发布答案,您还可以添加一些解释,以便更好地理解 OP 和未来的读者的解决方案。 这与问题完全无关。

以上是关于Python:“除了KeyError”比“if key in dict”快吗?的主要内容,如果未能解决你的问题,请参考以下文章

小技巧集合

如何撤消对 Python 2D 类实例数组所做的更改,例如在 C++ 中

Excel 公式:=IF(K$10>Period_Ending_Master_Date,0,I15/K$44)是啥

python 基础 for else

最小表示

字符串最小表示法