从dicts中删除元素时,del或pop是首选项

时间:2014-07-03 07:10:16

标签: python python-2.7

我对Python比较陌生,想知道从dict中删除元素时是否有理由更喜欢这些方法之一?

A)使用del

# d is a dict, k is a key
if k in d:
   del k

B)使用pop

d.pop(k, None)

我的第一个想法是,方法(A)需要进行两次查找 - 一次在if语句中,再一次在del的实现中,这会使它稍微慢于{ {1}},只需要一次查找。然后一位同事指出pop可能有优势,因为它是一个关键字,因此可能会更好地优化,而del是一种可被最终用户取代的方法(不确定)如果这确实是一个因素,但他在编写Python代码方面确实有更多的经验。)

我写了一些测试片段来比较性能。看起来pop有优势(如果有人关心尝试或评论正确性,我会附上片段。)

所以,这让我回到了这个问题:除了边际绩效收益之外,是否有理由更喜欢一个而不是另一个?

以下是测试效果的片段:

天真的测试

del

此输出

import timeit
print 'in:   ', timeit.Timer(stmt='42 in d', setup='d = dict.fromkeys(range(100000))').timeit()
print 'pop:  ', timeit.Timer(stmt='d.pop(42,None)', setup='d = dict.fromkeys(range(100000))').timeit()
print 'del:  ', timeit.Timer(stmt='if 42 in d:\n    del d[42]', setup='d = dict.fromkeys(range(100000))').timeit()

这是一个奇怪的结果。我原以为in: 0.0521960258484 pop: 0.172810077667 del: 0.0660231113434 pop大致相同,但它的价格却高出三倍。另一个惊喜是in只比del略慢,直到我意识到in类中的setup语句中的字典仍然是同一个实例,所以只有第一个调用才会命中timeit语句,因为所有其他语句都不会传递del语句。

稍微不那么幼稚的测试

所以我写了一个更长的分析代码片段,试图避免这种情况。我使用一些随机密钥选择运行了几个if次运行,并尝试确保我们主要使用timeit语句和if语句(因此我们不使用相同的字典实例一直):

del

进行100次设置,每次设置每次1000次,打印以下内容:

#! /usr/bin/bash

import timeit

# Number of times to repeat fresh setup before doing timeit runs
repeat_num=100
# Number of timeit runs per setup
number=1000
# Size of dictionary for runs (smaller)
small_size=10000
# Size of dictionary for timeit runs (larger)
large_size=1000000
# Switches garbage collection on if True
collect_garbage = False

setup_stmt = """
import random
d = dict.fromkeys(range(%(dict_size)i))
# key, randomly chosen
k = random.randint(0,%(dict_size)i - 1)
%(garbage)s
"""

in_stmt = """
k in d
%(incr_k)s
""" % {'incr_k' : 'k = (k + 1) %% %(dict_size)i' if number > 1 else ''}

pop_stmt = """
d.pop(k, None)
%(incr_k)s
""" % {'incr_k' : 'k = (k + 1) %% %(dict_size)i' if number > 1 else ''}


del_stmt = """
if k in d:
    del d[k]
%(incr_k)s
""" % {'incr_k' : 'k = (k + 1) %% %(dict_size)i' if number > 1 else ''}

# Results for smaller dictionary size
print \
"""SETUP:
   repeats        : %(repeats)s
   runs per repeat: %(number)s
   garbage collect: %(garbage)s""" \
       % {'repeats' : repeat_num,
          'number'  : number,
          'garbage' : 'yes' if collect_garbage else 'no'}
print "SMALL:"
small_setup_stmt = setup_stmt % \
    {'dict_size' : small_size,
     'garbage' : 'gc.enable()' if collect_garbage else ''}
times = timeit.Timer(stmt=in_stmt % {'dict_size' : small_size},
    setup=small_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    in:  ", sum(times)/len(times)
times = timeit.Timer(stmt=pop_stmt % {'dict_size' : small_size},
    setup=small_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    pop: ", sum(times)/len(times)
times = timeit.Timer(stmt=del_stmt % {'dict_size' : small_size},
    setup=small_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    del: ", sum(times)/len(times)

# Results for larger dictionary size
print "LARGE:"
large_setup_stmt = setup_stmt % \
    {'dict_size' : large_size,
     'garbage' : 'gc.enable()' if collect_garbage else ''}
times = timeit.Timer(stmt=in_stmt  % {'dict_size' : large_size},
    setup=large_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    in:  ", sum(times)/len(times)
times = timeit.Timer(stmt=pop_stmt  % {'dict_size' : large_size},
    setup=large_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    pop: ", sum(times)/len(times)
times = timeit.Timer(stmt=del_stmt  % {'dict_size' : large_size},
    setup=large_setup_stmt).repeat(repeat=repeat_num,number=number)
print "    del: ", sum(times)/len(times)

我刚开始使用SETUP: repeats : 100 runs per repeat: 1000 garbage collect: no SMALL: in: 0.00020430803299 pop: 0.000313355922699 del: 0.000262062549591 LARGE: in: 0.000201721191406 pop: 0.000328607559204 del: 0.00027587890625 ,所以这可能是一个有缺陷的测试,但它似乎表明timeit在性能方面有一个小优势。

我从这个练习中学到的一件事很困难,就是Python字典是哈希映射,因此字典的大小不会影响查找时间,就像C ++ del一样。例如(常数时间vs O(log(n)) - ish)。那好吧。生活和学习。

1 个答案:

答案 0 :(得分:9)

我不担心性能差异,除非您有特别的理由相信它们会导致您的程序出现明显的减速,这是不太可能的。

您可能选择使用del vs pop的真正原因是因为他们有不同的行为。 pop会返回弹出键的值,因此如果要在删除它的同时对该值执行某些操作,则可以使用pop。如果您不需要对该值执行任何操作,但只想删除该项,请使用del