良许Linux教程网 干货合集 讲解一下Python迭代器

讲解一下Python迭代器

迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来迭代提供了一种通用的不依赖索引的迭代取值方式,下面为大家详细讲解一下Python迭代器。

从for循环说起

我们都知道,在Python中,我们可以for循环去遍历一个列表,元组或者range对象。

for i in [1,2,3]:
   print(i)
for i in range(0,10):
   print(i)

那底层的原理是什么样的呢?这其中涉及到了几个概念,“可迭代”,“迭代器”,“生成器”等,大部分人可能听过这些名词,但是他们具体的含义以及之间的关系可能没搞清楚,以下就是它们之间的关系图,接下来我们就来分析这个关系图。

img

可迭代对象与迭代器(先不关心左边的生成器)

如果一个对象是可迭代对象,那么我们就可以用for循环去遍历它,比如列表、元组、字符串等都是可迭代对象。而我们用for循环去遍历它的原理就是,先获取了它的迭代器,然后使用迭代器的next方法去逐一遍历。

a = [1,2,3]
# for相当于下面的代码
for i in a:
   print(i)
# for循环分解(实际是通过Python底层C语言实现的,此处只是演示)

## 第一步: 获取迭代器
iterator_a = iter(a)
## 第二步: 通过next逐个遍历
while True:
   try:
       print(next(iterator_a))
   except StopIteration:  ## 第三步:遇到StopIteration异常,停止
       break

注意可迭代对象与迭代器的区别,如果一个对象是可迭代对象,那么我们就肯定能调用iter()方法获取它的迭代器,而如果一个对象是迭代器,我们就能用next()方法去拿下一个元素。 我们可以用isinstance判断一个对象是不是可迭代对象,是不是迭代器。

>>> from collections.abc import Iterable
>>> from collections.abc import Iterator
>>> isinstance([1,2,3],Iterable)
True
>>> isinstance([1,2,3],Iterator)
False
>>> i = iter([1,2,3])
>>> isinstance(i, Iterable)
True
>>> isinstance(i, Iterator)
True
>>> type(i)
'list_iterator'>

列表本身不是迭代器,它是可迭代对象,所以你不能用next()操作列表

>>> a=[1,2]
>>> next(a)
Traceback (most recent call last):
 File "", line 1, in 
TypeError: 'list' object is not an iterator
>>> iter_a = iter(a)
>>> next(iter_a)
1
>>> next(iter_a)
2
>>> next(iter_a)   # 当没有剩余元素时,就抛出StopIteration异常
Traceback (most recent call last):
 File "", line 1, in 
StopIteration

可迭代对象必须实现iter()函数,返回迭代器,调用对象自身的iter()函数与将iter()作用于对象效果是一样的,同理对next()和next()也一样。

>>> a=[1,2,3]
>>> iter_a = a.__iter__()
>>> next(iter_a)
1
>>> iter_a.__next__()
2

有趣的是,迭代器也是一个可迭代的对象,所以它本身也需要实现iter()函数,但是,一个迭代器的迭代器,是它本身,所以可能也有些多余了。

>>> a=[1,2,3]
>>> iter_a = iter(a)
>>> iter_b = iter(iter_a)
>>> iter_c = iter(iter_b)
>>> iter_a is iter_b
True
>>> iter_a == iter_c
True
>>> for x in iter_a:
...    print(x)
...
1
2
3

一些的迭代器类型

我们经常会用到一些内置的迭代器,例如filter和map,注意range是可迭代对象,但不是迭代器。

>>> from collections.abc import Iterator
>>> from collections.abc import Iterable
>>> a=range(10)
>>> isinstance(b, Iterable)
Traceback (most recent call last):
 File "", line 1, in 
NameError: name 'b' is not defined
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
False
>>> type(a)
'range'>
>>> print(a)
range(0, 10)

filter

filter函数用于对一个列表进行过滤,传入一个函数和列表,这个函数返回值是True或者False,将列表的元素逐个传入这个函数,结果为True的保留,可以使用lambda函数。

注:在Python2.x中返回值为list,在Python3.x中返回迭代器

>>> from collections.abc import Iterator
>>> from collections.abc import Iterable
>>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6])
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
True
>>> type(a)
'filter'>
>>> print(a)

>>> for i in a:
...  print(i)
...
2
4
6

如果我们要想通过下表访问,可以把它转换成list

>>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6])
>>> a = list(a)
>>> a[0]
2

map

map函数接收一个函数与一个列表,将这个函数作用域列表的每个元素,生成一个新的序列,返回迭代器。

>>> from collections.abc import Iterator
>>> from collections.abc import Iterable
>>> a=map(lambda x : x * x, [1,2,3])
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
True
>>> type(a)
'map'>
>>>  print(i)
 File "", line 1
   print(i)
   ^
IndentationError: unexpected indent
>>> print(a)

>>> for i in a:
...   print(i)
...
1
4
9

实现一个可迭代对象与它的迭代器

我们将list做一个简单的封装,实现一个可迭代的mylist。

class mylist:
   def __init__(self, l):
       self.l = l

   def __iter__(self):
       return mylist_iterator(self.l)

class mylist_iterator:
   def __init__(self, l):
       self.l = l
       self.current = 0   # 记录当前迭代到哪个元素了        
   def __iter__(self): # 迭代器的迭代器返回自己即可
       return self  
   def __next__(self):
       if self.current return self.l[self.current-1]
       else:
           raise StopIteration

a = mylist([1,2])
for x in a:
   print(x)

i = iter(a)
print(next(i))
print(next(i))
print(next(i))

上述代码并没有实现迭代器带来的好处,因为我们事先传入了一个列表进去,假如这个列表很大,会占内存。假如我们要实现一个类似range()功能,我们可以使用更有效的方法。

class myrange:
   def __init__(self, max_num):
       self.max_num = max_num

   def __iter__(self):
       return myrange_iterator(self.max_num)

class myrange_iterator:
   def __init__(self, max_num):
       self.max_num = max_num
       self.current = 0   # 记录当前迭代到哪个元素了        
   def __iter__(self): # 迭代器的迭代器返回自己即可
       return self  
   def __next__(self):
       if self.current return self.current-1
       else:
           raise StopIteration

a = myrange(2)
for x in a:
   print(x)

i = iter(a)
print(next(i))
print(next(i))
print(next(i))

需要注意的是,我们的myrange不能随机访问,只能一次性顺序遍历,只能前进,不能后退,实际Python的range()可以随机访问。

for循环是否只能用于可迭代对象?

这个答案是No。for循环大部分情况都作用于可迭代对象,但是有一个例如,如果对象是可以通过下标访问的,也能用于for循环。

一个对象如果不能用下标访问,那么就会报下面的错误,实际上它对应的是一个getitem()内置方法。

>>> a={1,2,3}
>>> a[0]
Traceback (most recent call last):
 File "", line 1, in 
TypeError: 'set' object is not subscriptable
>>> a.__getitem__(1)
Traceback (most recent call last):
 File "", line 1, in 
AttributeError: 'set' object has no attribute '__getitem__'

如果我们实现了getitem(),也能通过for循环遍历。

from collections.abc import Iterator
from collections.abc import Iterable
class mylist1:
   def __init__(self, l):
       self.l = l

   def __getitem__(self, i):
       return self.l[i]

print(isinstance(a, Iterable))
print(isinstance(a, Iterator))

a = mylist1([1,2,3])
for x in a:
   print(x)

结果如下,可以看到a既不是可迭代对象,也不是迭代器。

False
False
1
2
3

for循环会先看对象是不是实现了iter(),如果是,就用迭代器的方式,如果没有的话,就看有没有getitem(),都没有就报错,报的错如下:

>>> for x in 5:
...   print(x)
...
Traceback (most recent call last):
 File "", line 1, in 
TypeError: 'int' object is not iterable

那我们怎么知道它先去找iter()呢?我们在前面的代码里加上几行,

from collections.abc import Iterator
from collections.abc import Iterable
class mylist1:
   def __init__(self, l):
       self.l = l

   def __getitem__(self, i):
       return self.l[i]
   def __iter__(self):
       return self
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
a = mylist1([1,2,3])
for x in a:
   print(x)

结果如下:

True
False
Traceback (most recent call last):

 File "", line 1, in 
   runfile('C:/Users/xlniu/test123/test.py', wdir='C:/Users/xlniu/test123')

 File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile
   execfile(filename, namespace)

 File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile
   exec(compile(f.read(), filename, 'exec'), namespace)

 File "C:/Users/xlniu/test123/test.py", line 49, in 
   for x in a:

TypeError: iter() returned non-iterator of type 'mylist1'

特殊的迭代器-生成器(generator)

接下来我们看关系图的左边,生成器,生成器是迭代器,迭代器是可迭代对象,所以生成器肯定是可迭代对象了。哪些对象是生成器呢?

生成器的来源主要有两个,一个是生成器表达式,例如(i for i in ‘hello, world’), (i for i in range(0,10) if i % 2 == 0),另一个是生成器函数,生成器函数不使用return返回数据,而使用yield。

我们来看一下前面说的filter是不是生成器。

>>> from collections.abc import Iterator
>>> from collections.abc import Iterable
>>> from collections.abc import Generator
>>> a = [1,2,3,4,5,6]
>>> b = filter(lambda x : x % 2 == 0, a)
>>> print(isinstance(b, Generator))
False

它并不是一个生成器。

生成器表达式

生成器表达式与列表推断是差不多的,但是它用”()”括起来,而列表推断用的中括号,一般的语法就是:

expr(val) for val in xxx if yyy

例如

>>> from collections.abc import Generator
>>> a = (i for i in range(0, 10))
>>> next(a)
0
>>> next(a)
1
>>> print(isinstance(a, Generator))
True
>>> print(type(a))
'generator'>
>>> print(a)
 at 0x000001A61011B7C8>  #通过genexpr得到的生成器
>>> b = (i.upper() for i in 'hello, world')
>>> c = (x for x in range(0,10) if x % 5 == 0)
>>> for x in b:
...  print(x)
...
H
E
L
L
O
,

W
O
R
L
D
>>> d = [i for i in range(0,10)]
>>> print(type(d))
>>> type(d)
'list'>
>>> print(d)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如果我们把生成器表达式用在其他的对象上,例如set,list等,它们会自动转换成相应类型。

>>> set(i for i in range(0,5))  # 相当于set( (i for i in range(0,5)) )
{0, 1, 2, 3, 4}
>>> set( (i for i in range(0,5)) )
{0, 1, 2, 3, 4}
>>> tuple(i for i in range(0,5))
(0, 1, 2, 3, 4)
>>> list(i for i in range(0,5))
[0, 1, 2, 3, 4]

生成器函数

另外一种生成器通过生成器函数得到。

from collections.abc import Iterator
from collections.abc import Iterable
from collections.abc import Generator

def myrange(stop):
   i = 0
   while i #返回i,下次调用时会从这个地方继续向下执行
       i += 1

e = myrange(5)
print(isinstance(e, Iterable))
print(isinstance(e, Iterator))
print(isinstance(e, Generator))
print(type(e))
print(e)
print(next(e))
for x in e:
   print(x)

运行结果如下:

True
True
True
'generator'>

0
1
2
3
4

在函数myrange中,有一个特殊的关键词,yield。这个与return类似,但是return后,下次调用会从头开始,但是使用了yield,我们的函数就会返回一个生成器,相当于每次执行,都记住了上次的位置,从上次的位置继续执行。生成器表达式可以认为是一种特殊的生成器函数,类似于lambda表达式和普通函数。但是和生成器一样,生成器表达式也是返回生成器generator对象,一次只返回一个值。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

img
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部