作者:梁唐来源:早起Python大家好,今天给大家介绍一个python中非常好用又基础的工具库,叫collections。Collection在英文中是容器的意思,顾名思义,这是一个容器的集合。这个库里有很多容器,有些不是很常用。本文选择了最常用的,一起介绍给大家。defaultdictdefaultdict可以说是这个库中最容易使用的了,它的定义也很简单,从名字上我们基本就能看出来。解决了我们在使用dict时最常见的问题,即key为空的情况。一般情况下,我们在获取dict中的元素时,需要考虑key为空的情况。如果不考虑这一点,那么当我们拿到一个不存在的key时,系统就会抛出异常。当然我们可以在每次get之前写一个if判断,但是这样很麻烦,比如:ifkeyindict:returndict[key]else:returnNone当然这是最笨的方法,dict给我们提供了默认值的get方法。比如我们可以这样写:returndict.get(key,None)这样,当key在dict中不存在时,会自动返回我们设置的默认值。这样省去了很多麻烦的判断,但是在一些特殊情况下还是会出现一些问题。例如,当键重复时,我们希望将具有相同键的值存储在列表中,而不是只保留一个。这样的话,写代码会比较复杂:data=[(1,3),(2,1),(1,4),(2,5),(3,7)]d={}fork,vindata:ifkind:d[k].append(v)else:d[k]=[v]由于dict的value是一个list,所以我们还是要判断它是否为空,我们不能直接使用默认值,间接操作当然可以,但是还是不够简单:fork,vindata:cur=d.get(k,[])cur.append(v)d[k]=v这和使用if没什么区别,为了完美解决这个问题,我们可以在集合中使用defaultdict:fromcollectionsimportdefaultdictd=defaultdict(list)fork,vindata:d[k].append(v)使用defaultdict后,如果key不存在,容器会自动返回到我们预设的默认值。需要注意的是,defaultdict传入的默认值可以是类型,也可以是方法。如果我们传入int,默认值会被设置为int()的结果,即0。如果我们想自定义或者修改,我们可以传入一个方法,比如:d=defaultdict(lambda:3)fork,vindata:d[k]+=vCounter这个是我们经常用到的一个很常用也很强大的工具。在我们实际编程中,经常会遇到一个问题,就是计数和排序。比如我们在分析文本的时候,会得到一堆单词。可能会有大量的长尾词,在整个文本中可能只出现几次。所以我们要统计这些词出现的次数,只保留出现次数最多的。我们自己实现这个需求当然不难。我们可以创建一个字典,将这些单词一个一个地遍历。本来,我们还需要考虑这个词之前没有出现过的情况。如果我们上面提到的defaultdict就简单多了。但是我们还需要计数和排序的步骤。如果使用Counter,这一步将减少到一行代码。例如:words=['apple','apple','pear','watermelon','pear','peach']fromcollectionsimportCountercounter=Counter(words)print(counter)Counter({'apple':2,'pear':2,'watermelon':1,'peach':1})我们直接传入一个列表作为参数给Counter,它会自动帮我们统计每一个元素。如果我们要过滤topK,也很简单,它为我们提供了most_common的方法,我们只需要传入需要的K:counter.most_common(1)[('apple',2)]另外,其构造函数还接收一个dict类型。我们可以直接通过一个值为int类型的dict来初始化一个Counter,比如:c=Counter({'apple':5,'pear':4})c=Counter(apple=4,pear=3)and,它还支持加减操作,比如我们可以把两个Counter相加,它会自动合并这两个Counter,将同一个key对应的value累加起来。减法也是如此。对应的值会被减去,被减去的无法匹配的key会被保留,减数中无法匹配的key会被丢弃。需要注意的是,Counter支持负值。我们都知道deque是一个队列,deque也是一个队列,只是有点特殊,它是一个双端队列。对于队列,只允许在队尾插入元素,在队头弹出元素。由于deque被称为双端队列,也就是说它的头部和尾部都支持元素的插入和弹出。与普通队列相比,更加灵活。除了clear、copy、count、extend等常用API外,deque中最常用、最核心的API还有append、pop、appendleft、popleft。从名字我们可以看出,append和pop与list的append和pop是一样的,而appendleft和popleft是在队列的左边,即head进行pop和append操作。很容易理解。在日常使用中,真正使用双端队列的算法并不多。在大多数情况下,我们使用双端队列主要有两个原因。第一个原因是deque是由GIL管理的,它是线程安全的。该列表没有GIL锁,因此它不是线程安全的。也就是说,在并发场景下,list可能会出现一致性问题,而deque则不会。另一个原因是双端队列支持固定长度。当长度满了,我们继续追加的时候,它会自动弹出最早插入的数据。比如当我们有大量的数据,不知道它的数量,但是又想保留最后出现的指定数量的数据,就可以使用deque。fromcollectionsimportdequedque=deque(maxlen=10)假设我们要在f.read()中获取文件fori的最后10条数据:dque.append(i)namedtuplenamedtuple很特别,它涉及到的概念元编程。简单介绍一下元编程的概念,我们就不深入了。简而言之,它是通用的面向对象。我们都是定义类,然后通过类的构造函数创建实例。元编程就是我们定义元类,基于元类创建的不是实例,而是类。如果用模具和成品分别描述类和实例,元类就相当于模具的模具。namedtuple是一个非常简单的元类,通过它我们可以很方便的定义我们想要的类。它的用法很简单,直接上例子吧。比如我们要定义一个学生类,它有name、score、age三个字段,那么这个类就会写成:classStudent:def__init__(self,name=None,score=None,age=None):self.name=nameself.score=scoreself.age=age这只是粗略的写法。如果考虑规范,还需要定义property等注解,同样需要大量的代码。如果我们使用namedtuple来简化这个工作,我们看代码:fromcollectionsimportnamedtuple这是一个类,columns也可以写成'namescoreage',即用空格分隔Student=namedtuple('Student',['name','score','age'])Thisisanexamplestudent=Student(name='xiaoming',score=99,age=10)print(student.name)通过使用namedtuple,我们只需要一个行来定义一个类,但是这样定义的类是没有缺失值的,但是namedtuple非常强大,我们可以通过传入defaults参数来定义缺失值。Student=namedtuple('Student',['name','score','age'],defaults=(0,0))你可以注意到,虽然我们定义了三个字段,但我们只设置了两个缺失值。在这种情况下,namedtuple会自动将缺失值匹配到score和age这两个字段。因为在Python规范中,强制参数必须在可选参数之前。所以nuamdtuple会自动右对齐。详细的,我们今天的文章介绍了四种数据结构defaultdict、Counter、deque和namedtuple的用法。除了这四个之外,collections库中还有一些其他的工具类,但是我们使用的频率稍微低一些,篇幅原因这里就不细说了。感兴趣的同学可以自行查看相关的api和文档。
