检查字典中是否已存在给定的键,并将其值递增。

328

如何确定字典中的键是否已经设置为非 None 值?

如果已经存在值,则将其增加;否则将其设置为 1:

my_dict = {}

if my_dict[key] is not None:
  my_dict[key] = 1
else:
  my_dict[key] += 1

12
小代码问题:如果已经有内容,则该代码将my_dict[key]设置为1,如果没有,则将其递增。我认为您想使用==而不是!=。 - QuantumFool
12个回答

354

您正在寻找 collections.defaultdict(适用于 Python 2.5+)。这是一个

from collections import defaultdict

my_dict = defaultdict(int)
my_dict[key] += 1

会做你想要的事情。

对于普通的Python dict,如果没有给定键的值,则在访问该字典时您将不会得到None,而是会引发一个KeyError错误。因此,如果您想使用普通的dict,而不是您的代码,您应该使用:

if key in my_dict:
    my_dict[key] += 1
else:
    my_dict[key] = 1

8
根据他的例子,只需设置"defaultdict(lambda: 0)"并跳过整个"if"语句即可。 - Deestan
这个可以工作,但是混淆了键和值(使其有点奇怪)。'some_value' 应该是 'some_key'。 - mikemaccana
@nailer:已修复,谢谢。我最初使用了“some_value”,因为这是问题中的变量名,但现在我同意更清晰了。 - dF.
27
对于普通的字典,您可以使用my_dict[key] = my_dict.get(key, 0) + 1来实现。 - minmaxavg
1
如何将其扩展到嵌套字典?dict[key1][key2] += 1? - Pablo Ruiz Ruiz

350

我更喜欢用一行代码完成这个操作。

my_dict = {}
my_dict[some_key] = my_dict.get(some_key, 0) + 1

字典有一个名为 get 的函数,它接受两个参数——你想要的键和默认值(如果该键不存在)。我更喜欢这种方法而不是 defaultdict,因为你只需要在这一行代码中处理键不存在的情况,而不是在任何其他地方都要处理。


2
我更喜欢这个解决方案而不是选择的答案,因为它不需要安装另一个依赖项。 - Erol
2
@Erol defaultdict 是 Python 标准库的一部分(https://docs.python.org/3/library/collections.html)。因此无需安装! - François Leblanc

64

我个人喜欢使用setdefault()

my_dict = {}

my_dict.setdefault(some_key, 0)
my_dict[some_key] += 1

1
setdefault非常棒。如果已经为some_key设置了值,则不会更改该值。例如,d={1:2}; d.setdefault(1, 0)不会影响d[1]的值。 - wsaleem
其他答案行不通,但是这个答案对于 self.site_included.setdefault(i, []) 接着 self.site_included[i].append(self.post_index) 是有效的。 - WinEunuuchs2Unix

50
你需要使用key in dict来完成这个目标。
if key in my_dict and not (my_dict[key] is None):
  # do something
else:
  # do something else

不过,你应该考虑使用 defaultdict(正如 dF 建议的那样)。


1
请注意,在至少2.6版本中,has_key()已被弃用,建议使用key in d。我认为在2.5版本中也是这样的。 - David Locke
注意,可以编写my_dict[key] is not None,这样更清晰明了(至少在我的看法中是这样)。 - brandizzi
@brandizzi - 我同意,if key in my_dict and my_dict[key]: - Rob Grant

21

为了回答问题“如何找出在字典中给定索引是否已被设置为非None值”,我更喜欢这种方法:

try:
  nonNone = my_dict[key] is not None
except KeyError:
  nonNone = False

这符合已经引入的 EAFP (宁愿请求宽恕,而不是事先获得许可) 的概念。它还避免了在字典中进行重复键查找,因为它在key in my_dict and my_dict[key] is not None中会出现,如果查找代价昂贵,这一点非常有趣。

对于你提出的实际问题,即如果存在则增加一个整数,否则将其设置为默认值,我也建议使用

my_dict[key] = my_dict.get(key, default) + 1

就像Andrew Wilkinson的回答中所述。

如果你在字典中存储可修改对象,则有第三种解决方案。一个常见的例子是multimap,其中为键存储元素列表。在这种情况下,你可以使用:

my_dict.setdefault(key, []).append(item)
如果字典中不存在键的值,setdefault方法将把它设置为第二个参数。这与标准的my_dict[key]行为一样,返回该键的值(可能是新设置的值)。

对于像我这样的外行来说,看起来确实很Pythonic的是,任何问题都至少有3个有效答案 :) - davka
@davka:这三种用例几乎相同,但又不同:a)查找字典中是否存在非None元素 b)从字典中检索值或在该值不存在时使用默认值 c)从字典中检索值并在该值尚不存在时存储默认值。 - nd.
我知道 :) 这不是批评,我只是对这个事实感到好笑。 - davka
在对@ryeguy的回答发表评论时,Stuart Woodward建议“语言中异常处理的开销始终比确定字典中项是否存在的哈希表查找大一个数量级”,而您则表示“它还避免了字典中的重复键查找...如果查找很昂贵”-有人有任何测量数据表明异常处理比双重键查找更快或更慢吗? - Michael Firth
1
@MichaelFirth 我对Python的异常开销进行了粗略搜索:https://dev59.com/PHE85IYBdhLWcg3w9orw 它比较慢,但差别不大。请记住,在不同的编程语言中,抛出异常这个高级概念的处理方式是非常不同的,你不能一概而论其优缺点。因此,虽然“异常开销增加10倍”可能对于Java是正确的,但对于Python(或Swift或其他语言)则不是。 - nd.

14

我同意 cgoldberg 的观点。我是这样做的:

try:
    dict[key] += 1
except KeyError:
    dict[key] = 1
所以要么按照上述方式操作,要么像其他人建议的一样使用默认字典。不要使用 if 语句,这不是 Python 风格。

8
if语句在Python中不符合Pythonic风格是如何表现的? - Adam Parkin
2
我认为这是Python的EAFP不是最好的方式之一。你上面的例子有重复的代码;如果有一天我们想要+=2或者-=1怎么办?你必须记得同时更改这两行。现在可能看起来微不足道,但这些是那种愚蠢的小“微不足道”的错误,它们会回来咬你。 - Cam Jackson
3
这看起来很好,而且运行良好,但我通常避免这样做,因为我认为在各种编程语言中使用异常处理的额外开销往往比哈希表查找字典中某个项是否存在的开销高一个数量级。 - Stuart Woodward

11

从众多答案中可以看出,有几种解决方法。一种LBYL(先检查再执行)的实例尚未被提及,即has_key()方法:

my_dict = {}

def add (key):
    if my_dict.has_key(key):
        my_dict[key] += 1
    else:
        my_dict[key] = 1

if __name__ == '__main__':
    add("foo")
    add("bar")
    add("foo")
    print my_dict

6
"has_key()"比起"in"运算符来说更慢,而且可读性较差。 - Abgan
9
这个功能在Python 2.6中已被弃用,并在Python 3中被移除。 - Tim Pietzcker

8
有点晚了,但这应该可以解决问题。
my_dict = {}
my_dict[key] = my_dict[key] + 1 if key in my_dict else 1

哇,作为一名Java程序员,这是一个非常疯狂的结构。它看起来像一个奇怪顺序的三元运算符? - forresthopkinsa

8
你正在尝试的方法称为LBYL(先看后跳),因为你在尝试增加值之前检查条件。
另一种方法称为EAFP(先求谅然后请求许可)。在这种情况下,您可以直接尝试操作(增加值)。如果失败了,您就捕获异常并将值设置为1。这是一种稍微更具Python风格的方式(以我个人的看法)。
参考链接:http://mail.python.org/pipermail/python-list/2003-May/205182.html

5

虽然这并没有直接回答问题,但是我觉得你可能需要 collections.Counter 的功能。

from collections import Counter

to_count = ["foo", "foo", "bar", "baz", "foo", "bar"]

count = Counter(to_count)

print(count)

print("acts just like the desired dictionary:")
print("bar occurs {} times".format(count["bar"]))

print("any item that does not occur in the list is set to 0:")
print("dog occurs {} times".format(count["dog"]))

print("can iterate over items from most frequent to least:")
for item, times in count.most_common():
    print("{} occurs {} times".format(item, times))

这将导致输出结果。
Counter({'foo': 3, 'bar': 2, 'baz': 1})
acts just like the desired dictionary:
bar occurs 2 times
any item that does not occur in the list is set to 0:
dog occurs 0 times
can iterate over items from most frequent to least:
foo occurs 3 times
bar occurs 2 times
baz occurs 1 times

计数器的工作方式类似于 defaultdict(int),但具有一些额外的功能,因此在处理纯整数时可以完美地工作,但您没有显示任何相关行为。 - Tadhg McDonald-Jensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接