目录
一、可迭代对象(Iterable)
1、迭代
对 list、tuple、str 等类型的数据使用 for…in… 的循环语法从其中依次拿到数据进行使用,这样的过程被称为遍历,也叫迭代。
2、可迭代对象
可以通过 for…in… 这类语句迭代读取一条数据的对象称之为可迭代对象。
列表、元组、字典、字符串都是可迭代对象。
整数、浮点数、布尔值都是不可迭代的。
3、满足条件
满足以下的条件的对象可以成为可迭代对象:
- 对象实现了
__iter__
方法。 __iter__
方法返回了一个迭代器对象。
4、for 循环工作原理
在内部对可迭代对象调用 __iter__
方法,获取到迭代器对象。
再一次次的通过迭代器对象调用 __next__
方法获取迭代结果。
li = [1, 2, 3, 4]
for i in li:
print(i)
for item in iterable:
先通过 iter() 函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用 next() 方法来获取下一个值,并将其赋值给 item,当遇到 StopIteration 的异常后循环结束。
5、判断迭代
可以使用 isinstance() 判断一个对象是否是 Iterable 对象。
from collections.abc import Iterable
print(isinstance([1, 2], Iterable)) # True
print(isinstance(5, Iterable)) # False
print(isinstance({1, 2}, Iterable)) # True
print(isinstance('abc', Iterable)) # True
可迭代对象的本质就是可以向我们提供⼀个这样的中间“人”。即迭代器帮助我们对其进行迭代遍历使用。
二、迭代器(Iterator)
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式,可以记住遍历的位置的对象。
迭代器是访问可迭代对象的工具,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
for 循环会返回对象的下一条数据,直到所有的都取完了。在这个过程中应该有一个“人”去记录到了第几条数据,把这个帮助我们进行数据迭代的“人”称为迭代器。
基本方法
iter() 和 next()
# 通过 iter() 内置函数取得可迭代对象的迭代器。
li = [1, 2, 3] # 列表是可迭代对象
it = iter(li) # 通过 iter() 方法取得 list 的迭代器
print(it)
# 运行结果:
# <list_iterator object at 0x0000023F8D239FF0>
可迭代对象其实都是 collections 模块里的 Iterable 类创建出来的实例的。比如一个列表,其实它就是 Iterable 类创建的实例对象。
功能
- for 循环
- 逐行遍历文本文件
- 列表推导式
- 元组拆包
特性
- 访问者不需要关心迭代器内部结构,只需不断执行 next() 方法获取下一个内容。
- 不能随机访问集合中的某个值,只能从头到尾顺序的读取。
- 访问到一半时不能回退,不能访问之前的值。
- 适合遍历很大的数据集合,节省内存,提升速度。
特点
可迭代:Python 中的迭代器实现了 __iter__
方法,因此也可以迭代。
易耗损:迭代器经过一次依次取值的循环后就耗尽了,如果想再次迭代要重建迭代器。
优点
节约内存(循环过程中,数据不用一次读入,在处理文件对象时特别有用,因为文件也是迭代器对象)、不依赖索引取值、 实现惰性计算(需要时再取值计算)。
1、可迭代对象和迭代器
凡是可以 for 循环的,都属于 Iterable 可迭代对象,
凡是可以 next() 的都是 Iterator 迭代器。
from collections.abc import Iterable, Iterator
st = 'abc'
print(isinstance(st, Iterable)) # True
print(isinstance(st, Iterator)) # False
it = iter(st) # 转换为迭代器
print(isinstance(it, Iterable)) # True
print(isinstance(it, Iterator)) # True
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。
可迭代对象包含迭代器。
如果一个对象拥有
__iter__
方法,其是可迭代对象;如果一个对象拥有__next__
方法,其是迭代器。定义可迭代对象,必须实现
__iter__
方法;定义迭代器,必须实现__iter__
和__next__
方法。 迭代器也是可迭代对象,因此迭代器必须也实现__iter__
方法。
联系:
Python 从可迭代的对象中获取迭代器。
区别:
可迭代的对象不是迭代器。
2、迭代器协议
概念
对象必须提供一个 __next__
方法,执行该方法要么返回迭代中的下一项,要么就引起一个 stoplteration 异常,以终止迭代(只能往后走,不能往前退)。
条件
- 对象实现了
__next__
方法 __next__
方法返回了某个数值(当然一般情况下,需要的是返回这个对象的特定的数字,并且按照一定的顺序进行依次返回)__next__
方法需要在值取完的时候,抛出 StopIteration 的错误信息。
可迭代对象
实现了迭代器协议的对象,对象内部定义一个 __iter__
方法。
协议是一种约定,可迭代对象实现了迭代器协议,Python 的内部工具(如 for 循环,sum,min,max 函数等)使用迭代器协议访问对象。
可迭代对象是调用对象的
__iter__
方法能够返回迭代器对象的一种对象。
3、应用场景
由于 Python 中没有“迭代器”这个类,因此具有以下两个特性的类都可以称为“迭代器”类:
- 有
__next__()
方法,返回容器的下一个元素或抛出 StopIteration 异常。 - 有
__iter__()
方法,返回迭代器本身。
# 创建一个返回数字的迭代器,初始值为 1,逐步递增 1
class A:
def __iter__(self):
self.a = 1
# 该方法返回的是当前对象的迭代器类的实例
return self # 返回 self(即自己本身),表示自身即是自己的迭代器。
def __next__(self):
x = self.a
self.a += 1
return x
# 按照定义这个 Fib 实例化出来的对象就是迭代器对象,迭代器对象一定是可迭代对象。
my = A()
myiter = iter(my)
print(next(myiter))
print(next(myiter))
print(next(myiter))
# 运行结果:
# 1
# 2
# 3
# 不是迭代器对象的类
class Test:
def __init__(self):
self.n = 1
def funa(self):
self.n += 1
return self.n
te = Test()
print(te.funa())
for i in te:
print(i) # 报错:TypeError: 'Test' object is not iterable
class Test:
def __init__(self):
self.n = 1
def __iter__(self): # 所有不可迭代的,加入 iter 之后,都变得可迭代
return self # 返回的是当前对象的迭代器类的实例
def __next__(self):
if self.n == 7:
raise StopIteration('终止了')
self.n += 1
return self.n
# 按照定义这个 Test 实例化出来的对象就是迭代器对象,迭代器对象一定是可迭代对象。
te = Test()
print(te) # <__main__.Test object at 0x0000011796EA7C40>
print(te.__iter__()) # <__main__.Test object at 0x0000011796EA7C40>
for i in te: # for 循环的本质:循环所有对象,全都是使用迭代器协议。
print(i)
# 运行结果:
# 2
# 3
# 4
# 5
# 6
# 7
- for 循环可以遍历(字符串,列表,元祖,字典,集合,文件对象),那这些类型的数据肯定是可迭代对象。但是其实这些都不是可迭代对象,只不过在 for 循环中,调用了它们内部的
__iter__
方法,把它们变成了可迭代对象。 然后 for 循环调用可迭代对象的__next__
方法取值,而且 for 循环会捕捉 stoplteration 异常,以终止迭代。- 可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其它的数据类型需要调用自己的内置的
__iter__
方法),所以生成器就是可迭代对象。
三、生成器(Generator)
在 Python 中,使用了 yield 的函数被称为生成器。 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
1、生成器函数
常规函数的定义,但是,使用 yield 语句而不是 return 语句返回结果。
yield 语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。
2、yield
效果就是使函数中断,并保存中断的状态,中断后,代码可以继续往下执行,过一段时间还可以重新调用这个函数,并且可以从上次 yield 的下面的一句代码开始执行,yield 可以返回值,也可以接收 send 来的参数。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。
生成器也是一种迭代器,但是只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。
3、优点
(1)首先,生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。
(2)除了延迟计算,生成器还能有效提高代码可读性。
使用生成器的注意事项:生成器只能遍历一次。
4、创建生成器
(1)生成器表达式
类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
只要把一个列表生成式的[]改成()
# 列表推导式
li = [i*2 for i in range(3)]
print(li) # [0, 2, 4]
# 生成器表达式
li2 = (i*2 for i in range(3))
print(li2) # <generator object <genexpr> at 0x000001E4C683C938>
print(next(li2)) # 0
print(next(li2)) # 2
print(next(li2)) # 4
(2)生成器函数
# 定义生成器,由 next 函数触发
def funa():
print('---开始---')
yield 'a' # 返回一个 a, 并暂停函数
yield 'b'
# 取值方式一:
fn = funa() # 调用一个生成器函数,返回的是一个迭代器对象。
print(next(fn))
# 运行结果:
# ---开始---
# a
# 取值方式二:
for i in funa():
print(i)
# 运行结果:
# ---开始---
# a
# b
for 循环中操作的对象是生成器中的 yied 生成的值,原因在于:
生成器是迭代器,会生成传给 yield 关键字的表达式的值。
注意:
不管有没有 return 语句,生成器函数都不会抛出 StopIteration 异常,而是在生成全部值后直接退出。
# 处理文件,用户指定要查找的文件和内容
# 将文件中包含要查找内容的每一行都输出到屏幕
def check(fname, word):
with open(fname, encoding='utf-8') as file:
for i in file:
if word in i:
yield i
ch = check('test.txt', '生成器')
print(next(ch))
5、return 和 yield
相同点:都是返回函数执行的结果 。
不同点:return 在返回结果后结束函数的运行,而 yield 则是让函数变成一个生成器,生成器每次产生一个值 (yield语句),函数被冻结,被唤醒后再产生一个值。
yield 生成器相比 return一次返回所有结果的优势:
- 反应更迅速
- 更节省空间
- 使用更灵活
(1)生成器中,如果没有 return,则默认执行到函数完毕时返回 StopIteration。
def funa():
yield 1
a = funa()
print(next(a))
print(next(a))
# 运行结果:
# StopIteration
# 1
(2)如果在 return 后返回一个值,那么这个值为 StopIteration 异常的说明,不是程序的返回值。
def funa():
yield 1
return '出现异常'
a = funa()
print(next(a))
print(next(a))
# 运行结果:
# StopIteration: 出现异常
# 1
记录学习过程,欢迎讨论交流,尊重原创,转载请注明出处~