Python系列(三):关于迭代器和生成器,你该了解这些

一.前言

说起迭代器和生成器,可以说是python语言的精髓之一,生成器可能有人没用过,但是迭代器绝对是大家经常使用的(可能你并不了解自己正在使用迭代器),最常见的迭代器使用场景就是我们用for循环来遍历各种列表,字符串,元组等各种对象了。

mlist = [1,3,5,7,9,2,4,6,8,10]
for item in mlist:
    print(item)

string = 'hello world'
for s in string:
    print(s)

mtuple = ('c','c++','java','python')
for item in mtuple:
    print(item)

怎么样?是不是感觉很熟悉?而对于另一个生成器而言,我先不说太多,你第一印象只需记住它实质上也是一种迭代器,但它更简洁。下面我将深入的为你剖析迭代器和生成器。

二.迭代器

2.1.迭代器对象(iterators)和可迭代对象(iterable)

2.1.1.辨析迭代器对象和可迭代对象:

  • 迭代器对象中定义了__next__()方法,每次调用该方法才会从容器中取出一个元素,当容器没有元素可取时会抛出StopIteration异常。
  • 可迭代对象中定义了__iter__()方法,该方法可以返回一个迭代器对象
  • 迭代器是一次性的,迭代器从第一个元素开始,直到所有的元素被访问完,迭代器在访问过程中只能往前不能退后。
  • 可迭代对象可以多次使用,每次使用iter()方法便可以从可迭代对象中返回一个迭代器。

头两点是区分迭代器对象和可迭代对象最好的方法,我们可以这样理解:迭代器对象和可迭代对象都是用来迭代的,但迭代器对象可以直接通过__next__()方法进行对象中元素的迭代,而可迭代对象需要先使用__iter()__方法返回一个迭代器对象,然后就可以使用迭代器对象中的__next__()方法进行遍历了。
代码示例

#在python中内置了iter()和next()函数,当使用这两个函数时会分别调用__iter__(),__next__()方法。
mlist = [1,3,5,7]

print(next(mlist))
'''
直接使用该行代码会报错:TypeError: 'list' object is not an iterator
因为mlist是一个列表,而列表不是一个迭代器并没有实现__next__()方法
'''
#调用iter方法返回一个可迭代对象
my_itera = iter(mlist)
print(type(my_itera))
#<class 'list_iterator'>
print(next(my_itera))
#1
print(next(my_itera))
#3
print(next(my_itera))
#5
print(next(my_itera))
#7
print(next(my_itera))
#列表中已经没有元素了,抛出异常:StopIteration

2.1.2.常见的可迭代对象

在python中最常见的可迭代对象有:列表,元组,字符串,集合,字典。

2.1.3.为什么for循环能迭代可迭代对象

这里直接给出官方的解释:Behind the scenes, the for statement calls iter() on the container object. The function returns an iterator object that defines the method __next__() which accesses elements in the container one at a time. When there are no more elements, __next__() raises a StopIteration exception which tells the for loop to terminate.
这段话的大概意思是:for循环自动对可迭代对象调用了__iter__()方法返回一个迭代器对象,每次循环时又对该迭代器对象调用__next__()方法获取容器中的一个元素,当容器中没有元素时会抛出StopIteration异常以终止循环。

2.2.如何自定义迭代器类

2.2.1.定义方法

前面我们已经介绍了:可迭代对象(Iterable)需要实现__iter__()方法,迭代器(iterator)需要实现__next__()方法。因此,通常是先实现迭代器类,然实现可迭代对象类,但这样可能比较麻烦,因此如今基本上是同时在一个类中同时定义__iter__()和__next__()两个方法,这样__iter__()方法只需要返回本身就可以了。

2.2.2.代码示例

#定义一个输出斐波那契数列的迭代器
class Fibonacci(object):
    """define a iterator class about fibnacci"""
    def __init__(self,num):
        self.a = 0
        self.b = 1
        self.num = num
	#定义__iter__方法
    def __iter__(self):
        return self
	#定义__next__方法
    def __next__(self):
        if self.a > self.num:
            raise StopIteration
        else:
            self.a,self.b = self.b,self.a + self.b
            return self.a

f = Fibonacci(10000)#指定数列的最大值不超过10000
for item in f:
    print(item,end=" ")
'''
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765  
'''

三.生成器

既然有了一般的迭代器那为什么需要生成器呢?倘若你要自定义迭代器每次都要定义__iter__()方法和__next()__方法,这样有点太麻烦了,但使用生成器却不一样,它的实现方式十分的简洁,因为生成器会自动实现__iter__()和__next__()方法,并会在容器中没有元素时自动抛出StopIteration异常。这样一来,就可以在减少代码量的情况下实现迭代器的功能。

3.1.什么是生成器

这里先给贴出一段代码,下面这段代码便实现了一个生成器(函数Fibonacci),同样是实现一个可以用来迭代的对象,使用生成器来创建却如此的简洁。

def Fibonacci(num):
    a,b=1,1
    while b < num:
        a,b = b,a+b
        yield a

f = Fibonacci(10000)
for item in f:
    print(item,end=" ")
'''
1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 
'''

对于生成器的准确定义,这里仍然引用官方文档的原话:Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data. 提炼出来的大致意思是:生成器是使用yield关键字来代替return的函数。

3.2.yield关键字

yield与return的第一个不同之处是yield返回的并不是值,而是一个生成器,这里给出代码示例为:

def testYield(n):
    yield n

my_gen = testYield(10)
print(my_gen)
print(next(my_gen))
'''运行结果
<generator object testYield at 0x000001BB551C4BF8>
10
'''

第二个不同之处是:return关键字会立刻返回值然后结束函数,但在包含yield的函数(生成器)中,每次运行到yield函数都会暂停并保存当前所有的运行信息,并在下一次执行next()方法时从上一次暂停的位置继续执行下去。代码示例为:

def testYield(n):
   for i in range(n):
    print('before yield')
    yield i
    print('after yield')

my_gen = testYield(10)
print(next(my_gen))
'''
before yield
0
'''
print(next(my_gen))
'''
after yield
before yield
1
'''
print(next(my_gen))
'''
after yield
before yield
2
'''

3.4.如何创建一个生成器

  • 从上面可以看出,在函数中使用yield就可以实现一个生成器,注意yield只能在函数中使用。
  • Python还提供了一种生成器的实现方式,这种方式类似于列表解析式,但使用圆括号"()“代替了方括号”[]",与列表解析式不同的是,生成器像列表解析式一样一次性生成所有的元素的,而是一次生成一个,当你需要下一个时才会生成下一个,这种方式可以节约内存资源。

下面只演示第二种创建方式(第一种前面有栗子)

my_gen = (x**2 for x in range(1000000))
print(my_gen)
#<generator object <genexpr> at 0x000001BADC3F4BF8>
print(next(my_gen))
#0
print(next(my_gen))
#1
print(next(my_gen))
#4
print(next(my_gen))
#9

对于生成器,我在这里推荐一个问题:列表展平问题,即将类似于[1,2,3,[4,5],[7,8]],[1,2,3,4,5,6,[[7,8,9,[10,11]]],[[[[12]]]]]这样的列表展成维度为1的的列表,例如上述两例分别展平为[1,2,3,4,5,6,7,8],[1,2,3,4,5,6,7,8,9,10,11,12]有兴趣的可以看看自己能不能用生成器解决这个问题。

四.结语

善用迭代器和生成器可以帮助你写出更简洁,更漂亮的python代码。当然迭代器和生成器的用法远远不止如此,有要更深入了解的可以自行探索。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斯曦巍峨

码文不易,有条件的可以支持一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值