Python 容器使用的 5 个技巧和 2 个误区( 五 )


 
3. 最好不用“获取许可”,也无需“要求原谅”这个小标题可能会稍微让人有点懵,让我来简短的解释一下:“获取许可”与“要求原谅”是两种不同的编程风格 。如果用一个经典的需求:“计算列表内各个元素出现的次数” 来作为例子,两种不同风格的代码会是这样:
 

  1. # AF: Ask for Forgiveness
  2. # 要做就做,如果抛出异常了,再处理异常
  3. def counter_af(l):
  4. result = {}
  5. for key in l:
  6. try:
  7. result[key] += 1
  8. except KeyError:
  9. result[key] = 1
  10. return result


  11.  
  12. # AP: Ask for Permission
  13. # 做之前,先问问能不能做,可以做再做
  14. def counter_ap(l):
  15. result = {}
  16. for key in l:
  17. if key in result:
  18. result[key] += 1
  19. else:
  20. result[key] = 1
  21. return result
整个 Python 社区对第一种 Ask for Forgiveness 的异常捕获式编程风格有着明显的偏爱 。这其中有很多原因,首先,在 Python 中抛出异常是一个很轻量的操作 。其次,第一种做法在性能上也要优于第二种,因为它不用在每次循环的时候都做一次额外的成员检查 。
不过,示例里的两段代码在现实世界中都非常少见 。为什么?因为如果你想统计次数的话,直接用 collections.defaultdict就可以了:
 
  1. from collections import defaultdict


  2.  
  3. def counter_by_collections(l):
  4. result = defaultdict(int)
  5. for key in l:
  6. result[key] += 1
  7. return result
这样的代码既不用“获取许可”,也无需“请求原谅” 。整个代码的控制流变得更清晰自然了 。所以,如果可能的话,请尽量想办法省略掉那些非核心的异常捕获逻辑 。一些小提示:
  • 操作字典成员时:使用 collections.defaultdict类型
    • 或者使用 dict[key]=dict.setdefault(key,0)+1内建函数
  • 如果移除字典成员,不关心是否存在:
    • 调用 pop 函数时设置默认值,比如 dict.pop(key,None)
  • 在字典获取成员时指定默认值: dict.get(key,default_value)
  • 对列表进行不存在的切片访问不会抛出 IndexError异常:["foo"][100:200]
 
4. 使用 next 函数next是一个非常实用的内建函数,它接收一个迭代器作为参数,然后返回该迭代器的下一个元素 。使用它配合生成器表达式,可以高效的实现“从列表中查找第一个满足条件的成员”之类的需求 。
 
  1. numbers = [3, 7, 8, 2, 21]
  2. # 获取并 **立即返回** 列表里的第一个偶数
  3. print(next(i for i in numbers if i % 2 == 0))
  4. # OUTPUT: 8
 
5. 使用有序字典来去重字典和集合的结构特点保证了它们的成员不会重复,所以它们经常被用来去重 。但是,使用它们俩去重后的结果会丢失原有列表的顺序 。这是由底层数据结构“哈希表(Hash Table)”的特点决定的 。
 
  1. >>> l = [10, 2, 3, 21, 10, 3]
  2. # 去重但是丢失了顺序
  3. >>> set(l)
  4. {3, 10, 2, 21}
如果既需要去重又必须保留顺序怎么办?我们可以使用 collections.OrderedDict模块:
 
  1. >>> from collections import OrderedDict
  2. >>> list(OrderedDict.fromkeys(l).keys)
  3. [10, 2, 3, 21]
Hint: 在 Python 3.6 中,默认的字典类型修改了实现方式,已经变成有序的了 。并且在 Python 3.7 中,该功能已经从 语言的实现细节变成了为可依赖的正式语言特性 。
但是我觉得让整个 Python 社区习惯这一点还需要一些时间,毕竟目前“字典是无序的”还是被印在无数本 Python 书上 。所以,我仍然建议在一切需要有序字典的地方使用 OrderedDict 。


推荐阅读