一、迭代器和可迭代对象
1.1 哪些是可迭代对象?如何检测?
只要能用for x in X进行迭代的对象,都叫可迭代对象;检测一个对象是否是可迭代对象,可以用isinstace(seq, Iterable)语句,其中 Iterable类型在collections模块中,需要手动导入。
1.2 常见的可迭代对象、迭代器有哪些?
常见的可迭代对象,包括list, tuple, set, dict, ndarray等。
常见的迭代器,比如文件句柄对象,open()函数的返回值就是迭代器。
1.3 可迭代对象和迭代器的区别
可迭代对象不一定是迭代器,但是迭代器一定是可迭代对象;因此迭代器中包括可迭代对象没有的属性和方法,比如__next__(),这也导致迭代器可以传入next()而可迭代对象不行。
可迭代对象在遍历时不会发生消耗,但是迭代器是会发生消耗的;这句话很重要,是本文重点,也是全网文章中很少讲到的一个点。
二、对1.3中重点语句的进一步实验
2.1 文件对象中的read()、readline()、readlines()
本节主要参考以前我写的一篇文章。
先创建一个txt文件,一共有5行。
当先将第一行的内容传给line后,打印出line,然后再直接用read()读完所有内容,下一次直接没办法迭代。
with open('123.txt', 'r') as f:
for line in f:
print(line)
print(f.read())
print('Loop Ending.')
# ugyiqvqivew
# asaass
# sadasfa
# ggtrhtr
# adxzch
# Loop Ending.
而如果把read()注释掉,那么就会迭代5次,分别对应每一行。
with open('123.txt', 'r') as f:
for line in f:
print(line)
# print(f.read())
print('Loop Ending.')
# ugyiqvqivew
# Loop Ending.
# asaass
# Loop Ending.
# sadasfa
# Loop Ending.
# ggtrhtr
# Loop Ending.
# adxzch
# Loop Ending.
本节的结论:对于迭代器——文件句柄对象,我们可用for x in X来遍历它,每一次的遍历都会导致内容的消耗,当然也可以用read()、readline()、readlines()来遍历其中的内容。
2.2 对于迭代器,普遍可以用iter()、for x in X、islice()等方法遍历
上一节讲的是迭代器中的文件句柄对象可以用read()、readline()、readlines()来遍历;那么对于迭代器,普遍存在哪些方式去遍历其中内容呢?
2.2.1 循环遍历法——for x in X
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(list(it)) # list函数会遍历完迭代器所有元素
# 1
# 2
# 3
# [4, 5, 6]
2.2.2 下一个遍历法——next()
list1 = [1,2,3,4,5,6]
it = iter(list1)
# next函数仅仅遍历迭代器的一个元素,调用一次消耗一次
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
print(list(it)) # [4, 5, 6]
print(next(it)) # 因为上面已经遍历完了,所以再遍历就会出异常;Exception : StopIteration
2.2.3 转换遍历法——list()、tuple()、set()、deque()、dict()
(1)转换成列表——list()
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(list(it)) # list函数会遍历完迭代器所有元素
# 1
# 2
# 3
# [4, 5, 6]
(2)转换成元组——tuple()
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(tuple(it)) # tuple函数会遍历完迭代器所有元素
# 1
# 2
# 3
# (4, 5, 6)
(3)转换成集合——set()
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(set(it)) # set函数会遍历完迭代器所有元素
# 1
# 2
# 3
# {4, 5, 6}
(4)转换成队列——deque()
import collections
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(collections.deque(it)) # deque函数会遍历完迭代器所有元素
# 1
# 2
# 3
# deque([4, 5, 6])
(5)转换成字典——dict()
list1 = [('a',1), ('b',2), ('c',3), ('d',4)]
it = iter(list1)
for i,t in enumerate(it):
print(t)
if i == 1:
print(dict(it)) # dict函数会遍历完迭代器所有元素
# ('a', 1)
# ('b', 2)
# {'c': 3, 'd': 4}
2.2.4 切片遍历法——islice()
from collections import deque
import itertools
import numpy as np
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
print(itertools.islice(it, None, 5, 2)) # 不用变量接收时,仅用print()无法完成遍历
print(list(it)) # [40, 30, 50, 46, 39, 44, 45, 67, 89, 34] ,因此输出的内容保留不变
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
print(list(itertools.islice(it, None, 5, 2))) # 不用变量接收时,但是用list完成了islice对象的遍历
print(list(it)) # [44, 45, 67, 89, 34] ,因此输出内容有所消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
print(tuple(itertools.islice(it, None, 5, 2))) # 不用变量接收时,但是用tuple完成了islice对象的遍历
print(list(it)) # [44, 45, 67, 89, 34] ,因此输出内容有所消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
print(set(itertools.islice(it, None, 5, 2))) # 不用变量接收时,但是用set完成了islice对象的遍历
print(list(it)) # [44, 45, 67, 89, 34] ,因此输出内容有所消耗
iterable = [('a',40), ('b',30), ('c',50), ('d',46)]
it = iter(iterable)
print(dict(itertools.islice(it, None, 5, 2))) # 不用变量接收时,但是用dict完成了islice对象的遍历
print(list(it)) # [] ,因此输出内容有所消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
print(deque(itertools.islice(it, None, 5, 2))) # 不用变量接收时,但是用deque完成了islice对象的遍历
print(list(it)) # [44, 45, 67, 89, 34]
# <itertools.islice object at 0x0000022F09409EA0>
# [40, 30, 50, 46, 39, 44, 45, 67, 89, 34]
# [40, 50, 39]
# [44, 45, 67, 89, 34]
# (40, 50, 39)
# [44, 45, 67, 89, 34]
# {40, 50, 39}
# [44, 45, 67, 89, 34]
# {'a': 40, 'c': 50}
# []
# deque([40, 50, 39])
# [44, 45, 67, 89, 34]
切片遍历法有两种实施方式,要么直接转,要么先传给一个中间变量再转;前者在上面这一块代码,后者具体看下面的这块代码。
import itertools
# 情况1:start和stop都不是None,则在区间内的元素才被消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
d = itertools.islice(it, 2, 4)
print(list(d)) # [50, 46]
print(list(it)) # [39, 44, 45, 67, 89, 34]
# 情况2:stop是None,则所有元素都被消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
d = itertools.islice(it, 4, None, 2)
print(list(d))
print(list(it)) # []
# 情况3:start是None,则从一开头到stop区间中的元素被消耗
iterable = [40,30,50,46,39,44,45,67,89,34]
it = iter(iterable)
d = itertools.islice(it, None, 5, 2)
print(list(d)) # [40, 50, 39]
print(list(it)) # [44, 45, 67, 89, 34]
# [50, 46]
# [39, 44, 45, 67, 89, 34]
# [39, 45, 89]
# []
# [40, 50, 39]
# [44, 45, 67, 89, 34]
对于切片遍历法消耗内容的范围;如果stop设置为None,那么会遍历(消耗)所有元素;而如果start没设置为None,start往前的元素并不会被遍历(消耗)到。 详见上面的代码块。
2.3 哪些方法看起来能遍历但实际无法遍历?
2.3.1 print(iterator)函数无法遍历
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
# 1
# 2
# 3
# 4
# 5
# 6
2.3.2 numpy.array()或numpy.asarray()函数并无法遍历迭代器中的内容
import numpy as np
list1 = [1,2,3,4,5,6]
it = iter(list1)
for i in it:
print(i)
if i == 3:
print(np.array(it)) # 返回的结果和不用np.array()的一样
# 1
# 2
# 3
# <list_iterator object at 0x0000022F08FA78E0>
# 4
# 5
# 6