对 enumerate, zip, zip_longest, chain ... 等不常用迭代器的强化认识.
快一个月都没有看看 Python 了, 一直在写 sql, 曾经好几次, 都认为 在数据分析方面, sql 要比 编程语言更加强大. 尤其是在数据可视化这块, 面对大量的数据集, 如果用 Pandas 来读取 SQL 为 DataFrame 操作, 这是不太现实的. 当然小量数据集, 百万级一下, 还是可以, 多了就性能上可能不是太好. 尤其是在的引入 BI 后, 其实整个处理过程, 就是在图表中直接嵌入 sql, 这样反而显得 Python 是一点可施展的地方都没有呢.
当然, 不能绝对, 在一些线下的小数据集, Python 绝对是最强的, 没有之一. 同时, 二者配合起来, 某一段查询集用 Pyhton 来获取为 DataFrame 来处理也是可以的, 即现在 sql 层运行一把, 做聚合后, 再用 Python 来安排一波, 这样就非常灵活和强大了.
不扯了, 还是继续上次的抄书的地方, 继续来安排上, 找找感觉.
序列上索引值迭代
需求
在迭代一个序列的同时跟踪正在被处理的元素索引
方案
用内置枚举函数 enumerate() 最为适合解决这类问题.
# enumerate()
lst = ['a', 'b', 'c']
for idx, val in enumerate(lst):
print(idx, val)
默认索引是从 0 开始的, 当然也可以从 1 开始, 传递一个起始参数即可. 当然, 默认还是从 0 开始计数比较统一些.
lst = 'you520'
for index, value in enumerate(lst, 1):
print(index, value)
还有在遍历文件时, 想知道错误行号的定位时也比较有用, 代码是这样的:
def parse_data(file_name):
with open(file_name, 'r') as f:
for line_no, line in enumerate(f, 1):
cur_line = line.split()
try:
# todo: 对每行的各种骚操作
# count = int(cur_line[1])
pass
except ValueError as e:
print(f'Line {line_no}: Parse error: {e}')
enumerate() 还用于跟踪某些值在列表中出现的位置. 比如现在想将一个文件中出现的单词, 映射到它出现的行号上去, 实现的效果大致是这样的.
from collections import defaultdict
word_summary = defaultdict(list) # 有序字典
with open('./test.txt') as f:
lines = f.readlines()
for idx, line in enumerate(lines):
words = [w.strip().lower() for w in line.split()]
for word in words:
word_summary[word].append(idx)
# test
print(word_summary)
defaultdict(<class 'list'>,
{'hello,': [0], 'nice': [1], 'to': [1, 2], 'see': [1], 'you': [1, 2], 'do': [2], 'want': [2], 'drink': [2], 'with': [2], 'me': [2], '?': [2]})
enumerate() 函数返回的是一个 enumerate 对象 实例, 它是一个迭代器, 返回连续的包含一个技术和一个值的元组, 元组中的值通过传入序列上调用 next() 返回.
同时迭代多个序列
需求
同时遍历多个序列, 每次分别从一个序列中取出一个元素出来
方案
用内置的 '拉链' zip( ) 函数.
# zip() 拉链函数
lst_01 = [1, 2, 3]
lst_02 = [100, 'abc', [1,2,'cj'], 4, 5]
for x, y in zip(lst_01, lst_02):
print(x, y)
# zip => inner join
lst_01 = [1, 2, 3, 4, 5, 6, 7]
lst_02 = [100, 'abc', [1,2,'cj']]
for x, y in zip(lst_01, lst_02):
print(x, y)
enumerate 对象索引从 0 开始, 而 zip 是从 1 开始... 慢慢就会知道, 其实Python也是很多地方比较随意的
zip (a, b) 会生成一个可返回元组的 (x, y) 的迭代器. 迭代的长度, 以最短的序列长度为基础. 有点, inner join 的感觉.
当然, 也是可以实现 left join 的, 用 itertools.zip_longest() 来实现.
# zip: 外连接
from itertools import zip_longest
for i in zip_longest(lst_01, lst_02):
print(i)
(1, 100)
(2, 'abc')
(3, [1, 2, 'cj'])
(4, None)
(5, None)
(6, None)
(7, None)
# zip: 外连接奇怪在于, 以最长的为主表
from itertools import zip_longest
a = [1,2]
b = ['a', 'b', 'c', 'd']
for i in zip_longest(a, b):
print(i)
(1, 'a')
(2, 'b')
(None, 'c')
(None, 'd')
更多用 zip() 地场景是当需要成对处理数据的时候. 比如将两个列表拼接为一个字典.
# 爬虫的时候,会经常用到
cols = ['name', 'age', 'gender', 'height', 'major']
val = ['youge', 24, 'M', 173, 'markting']
info = dict(zip(cols, val))
print(info)
{'name': 'youge', 'age': 24, 'gender': 'M', 'height': 173, 'major': 'markting'}
zip() 函数会创建一个迭代器作为结果返回. 如果需要将结对的值存储在列表中, 用在外面套一个 list 即可呀.
[('name', 'youge'),
('age', 24),
('gender', 'M'),
('height', 173),
('major', 'markting')]
不同集合上的元素迭代
需求
需在多个对象上执行相同操作, 但这些对象在不同的容器中. 实现代码在不失去可读性前提下, 避免写重复循环.
方案
通过 itertools.chain() 方法. 输入多个序列对象, 可以统一对其进行遍历输出
from itertools import chain
lst_01 = [1, 2, 3]
lst_02 = ['a', 'b', 'c', 'd']
for i in chain(lst_01, lst_02):
print(i)
其优雅的地方就在于, 不用分别去迭代 单个的序列, 或者将其全部手动拼接起来去遍历操作.
for i in lst_01:
pass
for j in lst_02:
pass
for k in lst_03:
pass
# 在操作都一样的前提下, 这样用 chain() 难道不香嘛?
for x in chain(lst_01, lst_02, lst_03):
pass
小结
- 序列计数或索引, 优先用 enumerate( ) 函数, 索引从 0 开始, 可指定
- 处理成对序列, 优先有 zip( ) 函数, 默认以最短为基准, 当然也可用 zip_longest() , 在爬虫有经常用到
- 迭代多个对象, 在不同容器, 优先 itertools.chain( ) 函数, 还是比较优雅的.
- 内置的 itertools 模块, 里面很多好用的工具, 值得去挨个使用一把呢
耐心和恒心, 总会获得回报的.