Python 可迭代对象、迭代器、生成器的详解
一、判断可迭代对象、迭代器:
使用模块from collections import Iterable, Iterator
,可以检测该对象是可迭代对象
还是迭代器
。
Iterable: 检查是否可迭代对象
Iterator:检查是否迭代器
举个例子,判断可迭代对象(Iterable):
from collections import Iterable, Iterator
>>> from collections import Iterable, Iterator
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> my_tuple = (1, 2, 3)
>>> isinstance(my_tuple, Iterable)
True
>>> my_dict = {"name": "fe_cow"}
>>> isinstance(my_dict, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(66)), Iterable) # 注意这是生成器
True
>>> isinstance(666, Iterable)
False
- 以上结论:可以看出
列表、集合、元组、字典、字符串、生成器
都是可迭代对象。
举个栗子,判断迭代器(Iterator):
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance(100, Iterator)
False
>>> my_tuple = (1, 2, 3)
>>> isinstance(my_tuple, Iterator)
False
>>> my_dict = {"name": "fe_cow"}
>>> isinstance(my_dict, Iterator)
False
>>> isinstance(666, Iterator)
False
- 以上结论:除了
生成器是迭代器
外,其余的类型都不是迭代器,因为生成器内部调用了next()函数
。
总结:
1.for 循环
的对象都是可迭代类型(Iterable)
2.可用于next()函数
的对象都是迭代器(Iterator )
,它表示一个惰性计算的序列
3.列表、集合、元组、字典、字符串
都是可迭代对象(Iterable),但不是迭代器(Iterator)
,可以通过iter()函数
变成迭代器(Iterator)对象
-
iter()函数将可迭代对象,变成迭代器对象:
>>> isinstance(iter("abc"), Iterator) True
迭代器一定是可迭代对象,但是可迭代对象不一定是迭代器
那么下面来看看可迭代对象、迭代器、生成器的详解吧!
二、可迭代对象(Iterable):
可迭代对象是指使用iter()
内置函数可以获取迭代器的对象
。
详细的讲:
1.检查对象是否实现了__ iter __ () 方法
,如果实现该方法调用它,会获取一个可迭代对象
2.如果没有实现__ iter __ () 方法
,但是实现了__ getitem __ (index)
方法,按顺序(从索引0开始)获取元素,参数idenx是从0开始的整数,Python会创建一个迭代器
3.若该对象获取失败,Python会抛出TypeError
异常,通常会提示xxxxxx object is not iterable
1.__ iter __ 方法:
class Test(object):
def __init__(self, name, my_list=[]):
self.name = name
self.my_list = my_list
def __iter__(self):
print('__执行iter__方法')
pass
test = Test('fe_cow', [1, 2, 3, 4, 5])
print(test)
print(isinstance(test, Iterable))
# 输出结果如下:
<__main__.Test object at 0x000001C4F2B1B940>
True
- 总结:可以看出一个对象拥有 __ iter __ 方法,该对象是一个可迭代对象
2.__ getitem __ 方法:
class Test(object):
def __init__(self, name, my_list=[]):
self.name = name
self.my_list = my_list
def __getitem__(self, item):
print('__执行getitem__方法')
return self.my_list[item]
test = Test('fe_cow', [1, 2])
print(test) # 输出结果:<__main__.Test object at 0x000002CDAD7EBA20>
print(isinstance(test, Iterator)) # False
for item in test:
print(item)
# 输出结果如下:
__执行getitem__方法
1
__执行getitem__方法
2
- 总结:可以看出在没有 __ iter __ 方法时,会去调用 __ getitem __ 方法,返回的是一个迭代器,通过for 循环可以遍历该对象的返回值。
- 注意:为什么它是迭代器,而使用
isinstance(test, Iterator)
返回的是False呢?该方式会自动忽略 __ getitem __ 函数,仅当对象实现了 __ iter __ 函数才返回 True,其他一律返回 False
3.抛出异常:
class Test(object):
def __init__(self, name, my_list=[]):
self.name = name
self.my_list = my_list
test = Test('fe_cow', [1, 2, 3, 4, 5])
print(test) # 输出结果:<__main__.Test object at 0x0000011B07C3B940>
for item in test:
print(item)
# 报错信息:
Traceback (most recent call last):
File "C:/Users/lh9/PycharmProjects/request/Python魔法函数.py", line 581, in <module>
for item in test:
TypeError: 'Test' object is not iterable
- 总结:若该对象,没有实现以上两种方法,当你把它当作可迭代对象处理时,就会抛出以上错误信息。
三、迭代器(Iterator):
迭代器就是实现__ next __ ()
方法,返回序列中的下一个元素,若该元素没有了,就抛出StopIteration
异常。
1.使用场景:
1.for 循环
2.扩展集合类型
3.列表推导、字典推导、集合推导
4.元组拆包
5.调用函数,使用*拆包实参等
2.特点:
可迭代:迭代器内部实现了__ iter __ 方法,所以它可迭代。
易损耗:迭代器经过一次逐次取值的循环后便耗尽了,若想再次迭代须重建迭代器。
模式:按需要一次获取一个数据。
3.斐波那契数列:
使用斐波那契数列,来讲解迭代器再好不过了,举个使用栗子:
from collections import Iterable, Iterator
class Fib(object):
"""迭代器经典栗子:
使用斐波那契数列:
0, 1, 1, 2, 3, 5 前2位数相加等于第3位数
"""
def __init__(self, num):
self.fib_nums = num
self.a = 0
self.b = 1
self.current_num = 0
def __iter__(self):
"""实现可迭代方法, 返回迭代器本身"""
return self
def __next__(self):
"""实现next方法,返回下一个可用元素"""
if self.current_num < self.fib_nums:
result = self.a
self.a, self.b = self.b, self.a + self.b
self.current_num += 1
return result
else:
raise StopIteration
fib = Fib(6)
print(isinstance(fib, Iterator)) # 输出结果:True,可以判定该fib对象是一个迭代器
for item in fib:
print(item)
# 输出结果如下:
0
1
1
2
3
5
- 总结:如果一个对象拥有
__ next __ ()方法
,该对象是迭代器
。
4.for 循环本质:
for 循环本质就是通过一个可迭代对象,不断调用__ next __ 方法实现
举个栗子:
my_list = [1, 2, 3, 4, 5, 6]
for item in my_list:
pass
等价于:
# 首先获得Iterator对象:
iterator_obj = iter([1, 2, 3, 4, 5, 6])
# 循环:
while True:
try:
# 获得下一个值:
result = next(iterator_obj)
except StopIteration:
# 遇到StopIteration就退出循环
break
5.总结:
迭代器一定拥有 __ iter __ 方法、__ next __ 方法,而可迭代对象一定拥有 __ iter __ 方法,但不能实现 __ next __ 方法,这就是上面所说的,迭代器一定是可迭代对象,但是可迭代对象不一定是迭代器
四、生成器(Generator ):
生成器也是函数,函数中只要有yield关键字,那么它就是生成器函数,返回值为生成器
生成器存在 __ inter __ 、 __ next __ 两种方法(不需要手动实现两种方法),因此它是一个迭代器
1.生成器概念介绍:
生成器函数:拥有yield关键字的Python函数。
生成器表达式:制造生成器的工厂,支持惰性产值。
生成器工厂函数:返回生成器的函数,定义体中可以没有yield关键字。
2.为什么使用生成器?
为什么列表代替不了生成器?因为列表容量肯定有限的,如创建一个包含666万个元素列表,不仅占用很大的存储空间,若我们仅仅需要访问前面几个元素,那后面绝大数元素占用空间都白浪费
,在Python中这种一边循环一边计算的机制,称为生成器(Generator),生成器一次只能产生一个值,这样消耗的内存数量将大大减少
。
3.生成器函数:
只要Python函数的定义体中有yield关键字
,该函数就是生成器函数
,调用生成器函数
时,会返回一个生成器对象
。
简单理解:生成器函数是生成器工厂,普通函数与生成器函数区别在于定义体中yield关键字
def func():
for item in range(6):
yield item
print(func()) # 输出结果:<generator object func at 0x000001BB1D7F26D0> 可以看出它是一个生成器
f = func() # 接收这个生成器
# 对生成器进行操作
print(next(f)) # 输出结果:0
print(next(f)) # 输出结果:1
print(next(f)) # 输出结果:2
看看它的执行流程:
def func():
print('__a__')
yield 1
print('__b__')
yield 2
print('__c__')
return 3
f = func()
print(f) # 输出结果:<generator object func at 0x00000293005D26D0>
# 可以看出是一个生成器,证明函数的return 并没有生效
print(next(f)) # 输出结果:__a__ 1 说明执行第一个yield
print(next(f)) # 输出结果:__b__ 2 说明执行第二个yield
print(next(f)) # 输出结果:抛出异常:StopIteration: 3
# 第二个 yield之后执行,当没有更多yield之后,抛出异常,正好是函数的返回值
总结:
1.生成器函数执行时,并不会执行函数体
2.当next生成器时,会从当前代码执行到之后的第一个yield,会弹出值并暂停函数
3.当再次执行next生成器时,从上次暂停处开始向下执行
4.当没有多余yield时,会抛出StopIteration异常,异常的值是函数的返回值
4.生成器表达式:
生成器表达式,将列表推导中的[ ]替换为( ),让代码表的更整洁
生成器表达式可理解为列表推导
的惰性版本,不会迫切构建列表
,而是返回一个生成器,按惰性生成元素。
使用栗子:
# 列表推导式
my_list = [item for item in range(6)]
print(type(my_list)) # 输出结果:<class 'list'>
print(my_list) # 输出结果:[0, 1, 2, 3, 4, 5]
# 生成器表达式
my_generator = (item for item in range(10))
print(type(my_generator)) # 输出结果:<class 'generator'>
print(my_generator) # 输出结果:<generator object <genexpr> at 0x0000023DE0FD26D0>
5.yield关键字:
函数中使用yield关键字,这个函数就变成了生成器函数
,yield只能定义在函数中,当我们调用这个函数时,函数内部代码并不会立即执行
,这个函数会返回一个生成器对象
,当我们使用for对其进行迭代时,函数内的代码才会被执行。
yield与return区别:
1.return时,这个函数的局部变量都被销毁,所以的return都是已经得到了结果将其返回。
2.yield是产生一个可以恢复的生成器函数,恢复了局部变量,生成器只有在调用next()方法时,才运行函数生成一个结果。
3.yield会记住函数执行的位置,下次再执行的时会从上次的位置继续向下执行;若再函数中使用return,函数就直接退出,无法继续执行。
6.总结:
1.具有yield关键字的函数都是生成器
2.yield可以理解为return,返回的值给调用者,不同return返回后,函数会被释放,而生成器不会
3.直接调用next方法或for语句j进行一下次迭代时,生成器会从yield下一句开始执行,遇到下一个yield为止,若没有yield 会抛出StopIteration