迭代器和可迭代对象
由for循环的内部原理说起
list01
=
[
2
,
434
,
5
,
6
,
8
]
for
item
in
list01
:
print
(
item
)
大家有没有想过list类型对象为什么可以被for循环呢?
能够被for循环的条件是:它是可迭代对象(iterable)。
那么什么是
可迭代对象
呢?
参考一下内置函数item()的官方说明文档:
iter(object[, sentinel])
返回一个 iterator对象。根据是否存在第二个实参,第一个实参的解释是非常不同的。如果没有第二个实参,object 必须是支持迭代协议(有 iter () 方法)的集合对象,或必须支持序列协议(有 getitem () 方法,且数字参数从 0 开始)。如果它不支持这些协议,会触发 TypeError。如果有第二个实参 sentinel,那么 object 必须是可调用的对象。这种情况下生成的迭代器,每次迭代调用它的 next () 方法时都会不带实参地调用 object;如果返回的结果是 sentinel 则触发 StopIteration,否则返回调用结果。
由此我们可以明确知道什么是可迭代的对象:即对象实现了能返回迭代器的 iter 方法,或对象实现了 getitem 方法,而且其参数是从零开始的索引。(两者都实现也行)
一文读懂Python可迭代对象、迭代器和生成器
判断一个对象是否是可迭代对象:
# 看__iter__方法 或 __getitem__方法是否在__dir__字典里(用dir()内置方法),若存在就说明
# 它是一个可迭代对象
>>
>
'__iter__'
in
dir
(
[
]
)
True
>>
>
'__getitem__'
in
dir
(
[
]
)
#还要进一步看其数字参数是否从 0 开始
True
>>
>
情况一:实现了能返回迭代器的__iter__方法(迭代器是具有
__next__()
方法的对象)。
class
SkillIterator
:
"""
迭代器
"""
def
__init__
(
self
,
target
)
:
self
.
target
=
target
self
.
index
=
0
def
__next__
(
self
)
:
# 如果索引越界 则抛出StopIteration异常
if
self
.
index
>
len
(
self
.
target
)
-
1
:
raise
StopIteration
# 返回下一个元素
item
=
self
.
target
[
self
.
index
]
self
.
index
+=
1
return
item
class
SkillManager
:
"""
可迭代对象
"""
def
__init__
(
self
,
skills
)
:
self
.
skills
=
skills
def
__iter__
(
self
)
:
# 创建迭代器对象 传递 需要迭代的数据
return
SkillIterator
(
self
.
skills
)
情况二:对象实现了 getitem 方法,而且其参数是从零开始的索引,python解释器会创建一个迭代器并尝试按顺序(从索引 0 开始)获取元素。
class
Eter_a
:
def
__init__
(
self
,
text
)
:
self
.
sub_text
=
text
.
split
(
" "
)
def
__getitem__
(
self
,
index
)
:
return
self
.
sub_text
[
index
]
ob_a
=
Eter_a
(
"Hello world!"
)
for
i
in
ob_a
:
print
(
i
)
一般我们不用getitem去实现一个迭代器,所以重点还是放在 __iter__上好了。
补充一下:标准迭代器里面大部分都同时实现了__next__和__iter__方法。所以有人这样说:
内部含有'__iter__'并且含有"__next__"方法的对象,就是迭代器
for循环内部原理
实际执行情况如下图:
1)调用可迭代对象的__inter__方法返回一个迭代器对象(iterator)
2)不断调用迭代器的__next__方法返回元素
3)知道迭代完成后,处理StopIteration异常
根据以上条件,我们使用while来模拟一下for循环:
# 1. 获取迭代器对象
list01
=
[
2
,
434
,
5
,
6
,
8
]
iterator
=
list01
.
__iter__
(
)
#(1)先调用__iter__方法获取一个迭代器
while
True
:
try
:
item
=
iterator
.
__next__
(
)
# (2)不断调用迭代器的__next__方法
print
(
item
)
except
StopIteration
:
#(3)直到捕获StopIteration异常停止迭代
break
# (4)跳出循环体
,
生成器generator
(首先它是一个函数,特殊之处在于函数使用yield返回,这样在调用此函数时会返回一个生成器对象,这是一个函数到对象的神奇过程,体现了一种惰性返回的思维(即用到才返回,不用不返回,而不是管你用不用哪怕只用一部分也一次性全部返回return)。生成器是迭代器的一种,明白了吧,想更深入理解就往下看)
生成器及其作用
python 生成器和迭代器有这篇就够了
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器。
生成器:generator
1.定义:能够动态(循环一次计算一次返回一次)提供数据的可迭代对象。
2.作用:在循环过程中,按照某种算法推算数据,不必创建容器存储完整的结果,从而节省内存空间。数据量越大,优势越明显。
3.以上作用也称之为延迟操作或惰性操作,通俗的讲就是在需要的时候才计算结果,而不是一次构建出所有结果。
生成器函数
1.定义:
含有yield语句的函数,返回值为生成器对象
。
2. 创建语法
def
函数名
(
)
:
…
yield
数据
…
– 调用:
for
变量名
in
函数名
(
)
:
语句
3.说明:
- 调用生成器函数将返回一个生成器对象,不执行函数体。
- yield翻译为”产生”或”生成”
4.执行过程:
(1) 调用生成器函数会自动创建迭代器对象。
(2) 调用迭代器对象的__next__()方法时才执行生成器函数。
(3) 每次执行到yield语句时返回数据,暂时离开。
(4) 待下次调用__next__()方法时继续从离开处继续执行。
5.原理:生成迭代器对象的大致规则如下
- 将yield关键字以前的代码放在next方法中。
-
将yield关键字后面的数据作为next方法的返回值。
(qz理解:或者说当内部调用next方法时,会跳到yield关键字之前的代码继续执行,并且将yield关键字后面的数据作为next方法的返回值,因为调试的话执行next会跳到yield关键字之前的代码。)
内置生成器
1,枚举函数enumerate
1).语法:
for
变量
in
enumerate
(
可迭代对象
)
:
语句
for
索引
,
元素
in
enumerate
(
可迭代对象
)
:
语句
2).作用:遍历可迭代对象时,可以将索引与元素组合为一个元组。
2,zip函数
1).语法:
for
item
in
zip
(
可迭代对象
1
,
可迭代对象
2
…
.
)
:
语句
2).作用:将多个可迭代对象中对应的元素组合成一个个元组,生成的元组个数由最小的可迭代对象决定。
生成器表达式
1.定义:用推导式形式创建生成器对象。
2.语法:变量 = ( 表达式 for 变量 in 可迭代对象 [if 真值表达式] )
(注意:可不要误解是元组推导式,不存在元组推导式,因为元组是不可变序列)
>>
>
(
i
for
i
in
range
(
1
,
10
)
)
<
generator
object
<
genexpr
>
at
0x0000023A9DDA7408
>
>>
>
[
i
for
i
in
range
(
1
,
10
)
]
[
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]
>>
>