1 引言
各位朋友大家好,欢迎来到月来客栈,我是掌柜空字符。
不知道各位客官在行走江湖的过程中有没有遇到类似这样的情景:
场景一:
假如现在有一个元素全为自然数的列表,需要你将其按奇数和偶数的方式把这些自然数分成两类,并存放到一个字典中。对于这个场景,掌柜相信每个客官看完之后就能写出类似如下的代码:
xxxxxxxxxx
111if __name__ == '__main__':
2 num = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 result = {'odd': [], 'even': []}
4 for n in num:
5 if n % 2 != 0:
6 result['odd'].append(n)
7 else:
8 result['even'].append(n)
9 print(result)
10
11# {'odd': [1, 3, 5, 7, 9], 'even': [0, 2, 4, 6, 8]}
在上述代码中,我们首先定义了一个包含有两个key的字典;然后依次遍历列表中的每一个数并按条件放到字典中的不同地方。
再例如现在有一个元素全为自然数的列表,需要你将其中相同的元素放到同一个列表中。对于这种类似场景,最直接的写法就是:
xxxxxxxxxx
111if __name__ == '__main__':
2 num = [0, 0, 1, 5, 2, 3, 1, 6, 5, 7, 9]
3 result = {}
4 for n in num:
5 if n in result:
6 result[n].append(n)
7 else:
8 result[n] = [n]
9 print(result)
10
11# {0: [0, 0], 1: [1, 1], 5: [5, 5], 2: [2], 3: [3], 6: [6], 7: [7], 9: [9]}
可以看到,虽然在上述两个场景中我们都能够以最直接的方式给实现出来,但总觉得繁杂了一点。那有没有更简单的方式呢?
场景二:
在数据预处理的过程中(又尤其是NLP)通常都会返回包含一串元素的数据类型。类似某数据预处理后返回的类型为一个二维列表,列表中的每一个元素对应的都是一个样本(列表),每个样本里面又包含了该样本对应的信息。例如SQuAD数据预处理后每个样本就会返回类似如下的字段:
xxxxxxxxxx
11[[example_id, feature_id, input_ids, seg, start_position, end_position, answer_text, question_id, input_tokens],[],[],...]
对于这样的形式,通常在后续都会以元素索引的方式来取每个样本中的对应内容。但是这样做面临的一个问题就是,当整个数据样本作为参数传到不同(多个)函数之后,我们往往就会忘了每个索引所指代的元素到底是什么含义,又需要跳转到最开始的地方数一数来确定。而这为我们写代码带来了极大的不便。那有没有什么好的方法能够解决这个问题呢?
2 解决方案
2.1 默认字典defaultdict
对于场景一当中的问题,我们可以通过collections
包中的defaultdict
模块来实现。从模块的名字可以看出,它返回的仍旧是一个字典,只是我们在定义这个字典的时候可以指定它的默认类型。例如可以通过defaultdict(list)
来得到一个默认元素为list的字典。
有了defaultdict
后,场景一中的第二个示例我们便可以通过如下代码来实现:
xxxxxxxxxx
91if __name__ == '__main__':
2 num = [0, 0, 1, 5, 2, 3, 1, 6, 5, 7, 9]
3 result = collections.defaultdict(list)
4 for n in num:
5 result[n].append(n)
6 print(result)
7 print(result[0])
8#defaultdict(<class 'list'>, {0: [0, 0], 1: [1, 1], 5: [5, 5], 2: [2], 3: [3], 6: [6], 7: [7], 9: [9]})
9# [0, 0]
从上面的输出结果可以看出,defaultdict
返回字典和普通的dict
字典在通过Key取Value的用法都一样。同时也可以通过同dict
一样的方式来遍历defaultdict
字典:
xxxxxxxxxx
111 for k, y in result.items():
2 print(k, y)
3 #
40 [0, 0]
51 [1, 1]
65 [5, 5]
72 [2]
83 [3]
96 [6]
107 [7]
119 [9]
当然,对于defaultdict
来说,其默认指定的类型还可以是str
、dict
等任何你所需要的类型。
2.2 命名体元组namedtuple
对于场景二中的问题,我们可以借助collections
包中的namedtuple
模块来完成。从名字来看namedtuple
返回的本质上是一个元组,只是元组里面的元素可以通过其对应的名称以类成员变量的方式来进行访问与使用。
因此,对于类似场景二中的问题,我们便可以通过如下所示代码来进行解决:
xxxxxxxxxx
211import collections
2import numpy as np
3
4if __name__ == '__main__':
5 Point = collections.namedtuple("point", ['x', 'y'])
6 X = np.random.randint(low=0, high=10, size=5)
7 Y = np.random.randint(low=0, high=10, size=5)
8 points = []
9 for x, y in zip(X, Y):
10 points.append(Point(x=x, y=y))
11 print(points)
12
13 for p in points:
14 print(f"{p.x} + {p.y} = {p.x + p.y}")
15#
16[point(x=2, y=9), point(x=1, y=9), point(x=2, y=3), point(x=9, y=4), point(x=9, y=1)]
172 + 9 = 11
181 + 9 = 10
192 + 3 = 5
209 + 4 = 13
219 + 1 = 10
在上述代码中,第5行用来定义一个包含有两个元素的元组,且该元组的名称为point
,两个元素对应的名称分别为x
和y
;第9-10行则是分别将这5个点依次保存到列表points
中,可以看到此时points
中的每一个元素都是一个命名体元组了;第13-14行则是遍历列表中的每一个元素,然后通过以元组中元素名称的方式来访问对应的值。
所以,在有了命名体元组这一利器之后,对于场景二中的问题便可以通过定义一个相应的namedtuple
来进行解决。
本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎点赞转发分享三连!若有任何疑问与建议,请添加掌柜微信nulls8进行交流。青山不改,绿水长流,我们月来客栈见!