Effective Python:编写高质量Python代码的90个有效方法(原书第2版)
上QQ阅读APP看书,第一时间看更新

第17条 用defaultdict处理内部状态中缺失的元素,而不要用setdefault

如果字典不是自己创建的,那么对其中缺失的键可以考虑用四种办法解决(参见第16条)。在这四种办法中,get方案要胜过利用in表达式和KeyError异常来解决的那两种方案,对于某些用例,我们可能觉得setdefault应该是代码最简短的办法。

例如,笔者要记录自己去过哪些国家,还要记录在每个国家到过哪些城市。那可以用这样一个字典,把国家名称与包含城市名称的集合(set)关联起来。

079-01

无论字典中有没有这个国家名称,都可以通过setdefault方案把新的城市添加到对应的集合里,这要比利用get方法与赋值表达式(Python 3.8的新特性)来实现的方案短很多。

079-02

如果程序所访问的这个字典需要由你自己明确地创建,那又该怎么写?其实这种情况很常见,例如我们经常需要用字典实例来维护某类对象的内部状态。下面,我们写这样一个类,把刚才那个范例逻辑封装到辅助方法中,使用户可以调用该方法来访问字典中保存的动态的内部状态。

080-01

这个新类把刚才那套复杂的逻辑掩盖了起来,正确地调用了setdefault方法,并给使用这个类的程序员提供了一套相当清晰的接口。

080-02

但问题是,Visits.add方法还是写得不够理想,因为它还是调用了名字很怪的setdefault方法,这使初次读这段代码的人难以理解发生了什么。另外,这种写法也不够高效,因为每次调用add方法时,无论country参数所指定的国家名称是否在字典里,都必须构造新的set实例。

好在Python内置的collections模块提供了defaultdict类,它能够轻松地实现出刚才那套常用的逻辑,因为它会在键缺失的情况下,自动添加这个键以及键所对应的默认值。我们只需要在构造这种字典时提供一个函数就行了,每次发现键不存在时,该字典都会调用这个函数返回一份新的默认值(参见第38条)。下面改用defaultdict实现Visits类:

080-03

这次的add方法相当简短。因为我们可以确定,访问这种字典的任意键时,总能得到一个已经存在的set实例。而且这次不像刚才那样,在字典里已经有这个键的情况下,仍然需要毫无必要地分配一些set,而正是频繁调用add方法,才使那种做法的开销比较大。

这种任务用defaultdict实现要比用setdefault好得多(第37条给出了另外一个例子)。当然还有很多问题没办法用defaultdict解决,但我们可以利用其他的Python工具处理(参见第18条、第43条以及内置类collections.Counter)。

要点

  • 如果你管理的字典可能需要添加任意的键,那么应该考虑能否用内置的collections模块中的defaultdict实例来解决问题。
  • 如果这种键名比较随意的字典是别人传给你的,你无法把它创建成defaultdict,那么应该考虑通过get方法访问其中的键值。然而,在个别情况下,也可以考虑改用setdefault方法,因为那样写更短。