如果字典中没有该值,如何设置值?
我正在使用一个字典来存储一些计数器,每个计数器用来统计某种文件类型的出现次数,比如 .wav、.mp3 等。
filetypecounter = {}
当我遇到某种文件类型时,我想用一种更符合 Python 风格的方法来增加计数器的值。所以我在考虑...
filetypecounter[filetype] +=1
不过,如果这个文件类型不在字典里,我想把它的计数器初始化为 1。我的逻辑是,如果这个文件类型的计数器已经存在,就把它的值加 1;如果不存在,就把它设为 1。
if filetype not in filetypecounter:
filetypecounter[filetype] = 1
else:
filetypecounter[filetype] +=1
有没有更符合 Python 风格的方法呢?
7 个回答
2
看起来你想要的是 collections.defaultdict,或者是 collections.Counter,这两个都是适用于Python 2.7及以上版本的工具。
2
在这个问题的回答中,大家都提到了使用 collections.Counter
,但这可能不是最快的选择。
还有一种比较老的方法是这样的:
>>> d={}
>>> for ext in ('.mp3','.mp3','.m4a','.mp3','.wav','.m4a'):
... d[ext]=d.setdefault(ext,0)+1
...
>>> d
{'.mp3': 3, '.wav': 1, '.m4a': 2}
这方法也不是最快的,但比 collections.Counter
要快。
有一些关于这些方法的 基准测试,结果显示使用 defaultdict、try/except 或者你最初的方法是最快的。
我在这里复现并扩展了这个基准测试:
import urllib2
import timeit
response = urllib2.urlopen('http://pastebin.com/raw.php?i=7p3uycAz')
hamlet = response.read().replace('\r\n','\n')
LETTERS = [w for w in hamlet]
WORDS = hamlet.split(' ')
fmt='{:>20}: {:7.4} seconds for {} loops'
n=100
print
t = timeit.Timer(stmt="""
counter = defaultdict(int)
for k in LETTERS:
counter[k] += 1
""",
setup="from collections import defaultdict; from __main__ import LETTERS")
print fmt.format("defaultdict letters",t.timeit(n),n)
t = timeit.Timer(stmt="""
counter = defaultdict(int)
for k in WORDS:
counter[k] += 1
""",
setup="from collections import defaultdict; from __main__ import WORDS")
print fmt.format("defaultdict words",t.timeit(n),n)
print
# setdefault
t = timeit.Timer(stmt="""
counter = {}
for k in LETTERS:
counter[k]=counter.setdefault(k, 0)+1
""",
setup="from __main__ import LETTERS")
print fmt.format("setdefault letters",t.timeit(n),n)
t = timeit.Timer(stmt="""
counter = {}
for k in WORDS:
counter[k]=counter.setdefault(k, 0)+1
""",
setup="from __main__ import WORDS")
print fmt.format("setdefault words",t.timeit(n),n)
print
# Counter
t = timeit.Timer(stmt="c = Counter(LETTERS)",
setup="from collections import Counter; from __main__ import LETTERS")
print fmt.format("Counter letters",t.timeit(n),n)
t = timeit.Timer(stmt="c = Counter(WORDS)",
setup="from collections import Counter; from __main__ import WORDS")
print fmt.format("Counter words",t.timeit(n),n)
print
# in
t = timeit.Timer(stmt="""
counter = {}
for k in LETTERS:
if k in counter: counter[k]+=1
else: counter[k]=1
""",
setup="from __main__ import LETTERS")
print fmt.format("'in' letters",t.timeit(n),n)
t = timeit.Timer(stmt="""
counter = {}
for k in WORDS:
if k in counter: counter[k]+=1
else: counter[k]=1
""",
setup="from __main__ import WORDS")
print fmt.format("'in' words",t.timeit(n),n)
print
# try
t = timeit.Timer(stmt="""
counter = {}
for k in LETTERS:
try:
counter[k]+=1
except KeyError:
counter[k]=1
""",
setup="from __main__ import LETTERS")
print fmt.format("try letters",t.timeit(n),n)
t = timeit.Timer(stmt="""
counter = {}
for k in WORDS:
try:
counter[k]+=1
except KeyError:
counter[k]=1 """,
setup="from __main__ import WORDS")
print fmt.format("try words",t.timeit(n),n)
print "\n{:,} letters and {:,} words".format(len(list(LETTERS)),len(list(WORDS)))
输出结果是:
defaultdict letters: 3.001 seconds for 100 loops
defaultdict words: 0.8495 seconds for 100 loops
setdefault letters: 4.839 seconds for 100 loops
setdefault words: 0.946 seconds for 100 loops
Counter letters: 7.335 seconds for 100 loops
Counter words: 1.298 seconds for 100 loops
'in' letters: 4.013 seconds for 100 loops
'in' words: 0.7275 seconds for 100 loops
try letters: 3.389 seconds for 100 loops
try words: 1.571 seconds for 100 loops
175,176 letters and 26,630 words
我个人感到惊讶的是, try
和 except
是实现这个功能最快的方法之一。谁能想到呢……
3
from collections import defaultdict
filetypecounter = defaultdict(int)
filetypecounter[filetype] += 1
或者
from collections import Counter
filetypecounter = Counter()
filetypecounter.update([filetype])
如果你必须使用一个 dict
(字典),那么你检查键是否存在的做法是合理的。也许一种更“符合Python风格”的解决方案可以是:
filetypecounter = {}
filetypecounter[filetype] = filetypecounter.get(filetype, 0) + 1
其实,这个和其他建议都是同一个思路的不同变体。我会使用Counter。