Python 可迭代对象、for循环、迭代器与生成器详解

导语

在做爬虫的时候用到了生成器的相关知识,当时就觉得生成器太厉害了,超级节省空间,于时便去详细的学习了一下生成器的知识,顺便把迭代器也一起学习了下,网上的讲解挺多的,这里我也总结一下,如有错误,敬请指正。

可迭代对象

一、概念

可迭代对象内部要么实现了__iter__()方法,要么实现了 __getitem__() 方法。

二、可作用于for循环的可迭代对象

  1. 由可迭代对象的概念可以知道,实现了__iter__()方法或__getitem__() 方法便是可迭代对象,然而并不是所有可迭代对象都可直接作用于for循环。(例子后面会讲到)
  2. 可直接作用与for循环的可迭代对象特点:要么实现了能返回迭代器的__iter__()方法,要么实现了 __getitem__() 方法而且其参数是从零开始的索引。

(这个网上有很多不同的见解,这里只说了我的理解,我们通常说的可迭代对象应该是指可作用于for循环的可迭代对象)

三、自定义可迭代对象实例

  1. 实现__iter__()方法:
    1)Iterable(可迭代)类型
    例一:
>>> class B:
...     def __init__(self):
...             pass
...     def __iter__(self):
...             pass
...
>>> z = B()
>>> isinstance(z, Iterable)
True
>>> iter(z)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'NoneType'

: : 例二:

>>> class C:
...     def __init__(self):
...             self.List = [1, 3, 5]
...     def __iter__(self):
...             return self
...
>>> I = C()
>>> isinstance(I, Iterable)
True
>>> for i in I:
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'C'
>>> iter(I)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'C'

::上面都实现了__iter__()方法,且都被判断为Iterable类型,但是__iter__()方法却没有返回迭代器,于是不能作用于for循环,故而不可迭代。
::2)可作用于for循环的可迭代对象

>>> class Score:
...     def __init__(self):
...             self.score = [1, 3, 4]
...     def __iter__(self):
...             return iter(self.score)
...
>>> s = Score()
>>> isinstance(s, Iterable)
True
>>> isinstance(s, Iterator)
False
>>> for i in s:
...     print(i)
...
1
3
4
>>> iter(s)
<list_iterator object at 0x000001585F4B09E8>
>>>

::上面的__iter__()方法内部有个python内置函数iter(),它的作用是把一个可迭代对象转化为一个迭代器,故__iter__()方法返回的是一个迭代器,因此它是可以作用于for循环的可迭代对象,但却不是一个迭代器,后面我们会讲到。

  1. 实现__getitem__()方法
>>> class words:
...     def __init__(self, word):
...             self.word = word
...     def __getitem__(self, index):
...             return self.word[index]
...
>>> w = words('Hello World')
>>> isinstance(w, Iterable)
False
>>> for i in w:
...     print(i)
...
H
e
l
l
o

W
o
r
l
d
>>> isinstance(w, Iterator)
False
>>> iter(w)
<iterator object at 0x000001585F4B09E8>
>>>

::上面代码没有实现 __iter__() 方法,但是实现了 __getitem__() 方法,而且其参数是从零开始的索引,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
: :从上面的结果可以看出,实现了 __getitem__() 方法, 它并不是一个Iterable类型,但是它却是一个可作用于for循环的可迭代对象,且能够用iter()方法转化为迭代器。(这里可能有些矛盾,Iterable不就是可迭代对象的意思吗?那为什么它不是Iterable类型呢?我的理解是,从概念可以看出,可迭代对象既包括了Iterable类型,又包括实现了__getitem__() 方法的类。)

四、可迭代对象的for循环机制

从上面的分析我们可以大致了解了在可迭代对象下的for循环机制:

  1. 先判断对象是否为可迭代对象(等价于判断有没有 __iter__() 或 __getitem__() 方法),没有的话直接报错,抛出TypeError异常。
  2. 这里for循环兼容两种机制:有 __iter__() 的话,调用 __iter__() 方法,返回一个迭代器。当for发现如果对象没有 __iter__() ,但是实现了 __getitem__(),会改用下标迭代的方式。
    (iter()方法也会处理这种情况,在不存在 __iter__()的时候,返回一个下标迭代的iterator对象来代替。这也就解释了上面的例子:实现了 __getitem__()方法,不是一个Iterable类型,却可以调用iter()方法转化为一个Iterator类型)
  3. 在python内部不断地调用迭代器的__next__()方法,每次按序返回迭代器中的一个值。
  4. 迭代到最后没有元素时,就抛出异常 StopIteration,for循环会自动处理这个异常终止循环。

迭代器

一、概念

  1. 迭代也便是循环,是访问集合元素的一种方式
  2. 迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

二、内部实现方法

::迭代器实现了两个基本方法:__iter__() 与 __next__() 。
:: __iter__() :返回一个带有 __next__()方法的对象,也即迭代器本身。
:: __next__() :该方法将逐一访问容器中的元素。 当元素用尽时, __next__()将引发 StopIteration 异常,可以使用 内置函数next()来调用 __next__() 方法。

三、与可迭代对象的比较

  1. 可迭代对象有个 __iter__ ()方法,每次都实例化一个新的迭代器。这样就可以保证不同的迭代过程不会互相影响。
  2. 迭代器实现 __iter__() 方法,返回迭代器本身,因此只能遍历一次,此外还要实现 __next__ ()方法,返回下一个元素
  3. 可作用于for循环或者实现__getitem()__()的可迭代对象可通过内置函数iter()转化为迭代器。
  4. 迭代器一定是可迭代对象,因为它实现了__iter__()方法;

三、实例

  1. 自定义迭代器的构造:
>>> class MyIter(object):
...     def __init__(self, myList):
...             self.myList = myList
...             self.index = 0
...     def __iter__(self):
...             return self
...     def __next__(self):
...             if self.index != len(self.myList):
...                     data = self.myList[self.index]
...                     self.index += 1
...                     return data
...             else:
...                     raise StopIteration
...
>>> m = MyIter([1, 3, 5])
>>> isinstance(m, Iterable)
True
>>> isinstance(m, Iterator)
True
>>> for i in m:
...     print(i)
...
1
3
5
  1. 可迭代对象转化为迭代器
>>> test1 = [1, 3, 5]
>>> isinstance(test1, Iterable)
True
>>> isinstance(test1, Iterator)
False
>>> test3 = 'hello'
>>> isinstance(test3, Iterable)
True
>>> isinstance(test3, Iterator)
False
>>> test4 = {7, 8, 9}
>>> isinstance(test4, Iterable)
True
>>> isinstance(test4, Iterator)
False
>>> test5 = {'x': 10, 'y': 11, 'z': 12}
>>> isinstance(test5, Iterable)
True
>>> isinstance(test5, Iterator)
False
>>> test6 = open('E:/test6.txt', 'r')
>>> isinstance(test6, Iterable)
True
>>> isinstance(test6, Iterator)
True
>>> isinstance(iter(test1), Iterator)
True
>>> isinstance(iter(test5), Iterator)
True

::从上面可以看出,基本数据类型:list、tuple、str、set、dict都是可迭代对象,却不是迭代器,但可以通过iter()方法转化为迭代器。
::但不止上面的类型,只要可作用于for循环或者实现__getitem()__()的可迭代对象可通过内置函数iter()转化为迭代器

四、迭代器的for循环机制

  1. 调用 __iter__()方法,返回自身self,也就是返回迭代器
  2. 不断地调用迭代器的next()方法,每次按序返回迭代器中的一个值
  3. 迭代到最后没有元素时,就抛出异常 StopIteration

生成器

一、概念

  1. 生成器(generator )是一个用于创建迭代器的简单而强大的工具,可以这么说,生成器是一种特殊的迭代器,自然也是一种可迭代对象。其内部实现了__iter__()和__next()__()方法
  2. 生成器函数( generator iterator ):它看起来很像普通函数,不同点在于其包含 yield 表达式以便产生一系列值供给 for-循环使用或是通过 next() 函数逐一获取。
  3. 生成器迭代器(generator iterator):generator 函数所创建的对象。
    每个 yield 会临时暂停处理,记住当前位置执行状态(包括局部变量和挂起的 try 语句)。当该 生成器迭代器恢复时,它会从离开位置继续执行(这与每次调用都从新开始的普通函数差别很大)。
  4. 生成器表达式(generator expression ):返回一个迭代器的表达式。 它看起来很像普通表达式后面带有定义了一个循环变量、范围的 for 子句,以及一个可选的 if 子句。

注意我们通常说的生成器是指生成器函数

二、生成器表达式

  1. 通过列表推导式语法,只不过将[]改为(),但这却不是一个元组推导式,元组没有推导式。
  2. 示例:
>>> gen = (x for x in range(4))
>>> gen
<generator object <genexpr> at 0x000001585F458D68>
>>> for i in gen:
...     print(i)
...
0
1
2
3

再看下一个例子,注意与上面的比较:

>>> gen = (x for x in range(4))
>>> gen
<generator object <genexpr> at 0x000001585F458DE0>
>>> tuple(gen)
(0, 1, 2, 3)
>>> for i in gen:
...     print(i)
...
>>>

这里用for循环并没有取到值,因为生成器也是迭代器,故而其值只能使用一次,前面我们用了tuple()函数将生成器转化为了元组,相当于使用了一次生成器,故而第二次取值时,已经没有值了。

3.特点:
生成器表达式能做的事情列表推导式基本都能处理,只不过在需要处理的序列比较大时,列表推导比较费内存,生成器的优势也就体现出来了。

三、生成器函数

  1. 在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。
  2. 示例:
    利用生成器实现斐波那契数列(除第一个和第二个数外,任何一个数都可以由前两个相加得到):
>>> def fib(max):
...     a, b, n = 0, 1, 0
...     while n < max:
...             yield b
...             a, b = b, a+b
...             n += 1
...
>>> f = fib(4)
>>> f
<generator object fib at 0x000001585F458D68>
>>> for i in f:
...     print(i, end=' ')
...
1 1 2 3

::内部实现:在 for 循环执行时,每次循环都会执行 fib 函数内部的代码,执行到 yield b 时,fib 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

  1. 特点:
    生成器函数可以生产一个无限的序列,这是列表根本无法处理的:
>>> def even():
...     n = 0
...     while True:
...             yield n
...             n += 2
...
>>> e = even()
>>> e
<generator object even at 0x000001585F458E58>
>>> count = 0
>>> for i in e:
...     if count >4:
...             break
...     print(i)
...     count += 1
...
0
2
4
6
8

这里我们做了循环终止条件,故不会一直输出,使用生成器不会将所有数据取出来存入内存中,而是返回了一个对象,可以通过对象获取数据;用多少取多少,可以节省内存空间。

四、yield

关于yield的详细知识这里就不说了,推荐一篇浅显易懂的博文:python中yield的用法详解——最简单,最清晰的解释:https://blog.csdn.net/mieleizhi0522/article/details/82142856

总结

网上找的一幅图片,总结得很好:
总结

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值