python迭代器(Iterator)、生成器(Generator)和生成器表达式(Generator expressions)

python迭代器(Iterator)、生成器(Generator)和生成器表达式(Generator expressions)

一、迭代器(iterator)

官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-generator):用来表示一连串数据流的对象。重复调用迭代器的 __next__() 方法(或将其传给内置函数 next())将逐个返回流中的项。当没有数据可用时则将引发 StopIteration 异常。到这时迭代器对象中的数据项已耗尽,继续调用其 __next__() 方法只会再次引发 StopIteration 异常。迭代器必须具有 __iter__() 方法用来返回该迭代器对象自身,因此迭代器必定也是可迭代对象,可被用于其他可迭代对象适用的大部分场合。

官方迭代器指导(tutorial) https://docs.python.org/zh-cn/3/tutorial/classes.html#iterators

Python 支持在容器(container)中进行迭代(iteration)的概念。容器对象(例如 list)在你每次向其传入 iter() 函数或是在 for 循环中使用它时都会产生一个新的迭代器。

下面解析之。

迭代(iteration)是 Python 最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

可迭代对象必须实现__iter__, 迭代器必须实现__iter__和__next__

如何判断可迭代对象(iterable)和迭代器(iterator)呢?

可迭代对象(iterable)

官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-iterable ):能够逐一返回其成员项的对象。 可迭代对象的例子包括所有序列类型 (例如 list, str 和 tuple) 以及某些非序列类型例如 dict, 文件对象 以及定义了 __iter__() 方法或是实现了 序列 语义的 __getitem__() 方法的任意自定义类对象。
可迭代对象可用于 for 循环以及许多其他需要一个序列的地方(zip()、map() ...)。当一个可迭代对象作为参数传给内置函数 iter() 时,它会返回该对象的迭代器。这种迭代器适用于对值集合的一次性遍历。在使用可迭代对象时,你通常不需要调用 iter() 或者自己处理迭代器对象。for 语句会为你自动处理那些操作,创建一个临时的未命名变量用来在循环期间保存迭代器。

☆使用Iterable模块可以判断对象是否是可迭代的:

from collections.abc import Iterable #引入
s="hello" #定义字符串
l=[1,2,3,4] #定义列表
t=(1,2,3) #定义元组
d={'a':1} #定义字典
set1={1,2,3,4} #定义集合
f=open("d://a.txt") #定义文本,注意a.txt需要存在
# 查看是否都是可迭代的
print(isinstance(s,Iterable)) #运行结果True
print(isinstance(l,Iterable)) #运行结果True
print(isinstance(t,Iterable)) #运行结果True
print(isinstance(d,Iterable)) #运行结果True
print(isinstance(set1,Iterable)) #运行结果True
print(isinstance(f,Iterable)) #运行结果True

运行之,参见下图:

☆使用Iterator模块可以判断对象是否是迭代器:

from collections.abc import Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("d://a.txt") #定义文本,注意a.txt需要存在
# 查看是否都是可迭代的
print(isinstance(s,Iterator)) #运行结果False
print(isinstance(l,Iterator)) #运行结果False
print(isinstance(t,Iterator)) #运行结果False
print(isinstance(d,Iterator)) #运行结果False
print(isinstance(set1,Iterator)) #运行结果False
print(isinstance(f,Iterator)) #运行结果True

运行之,参见下图:

由此可知,只有文件是迭代器,所以可以直接使用next(),而不需要转换成迭代器。

迭代器可以使用函数next()来取值;使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值。

需要注意的是,可迭代对象(Iterable)不一定是⼀个迭代器(Iterator),如字符串对象是⼀个可迭代对象,但不是⼀个迭代器,这意味着它⽀持迭代(Iteration),但我们不能直接对其进⾏迭代操作——不能直接使用内置函数next(),怎样才能对它实施迭代操作呢?使用⼀个内置函数iter(),它将根据⼀个可迭代对象返回⼀个迭代器对象。例如:

my_string = "Hello"

next(my_string)  # 出错

my_iter = iter(my_string) #可用内置函数iter()将?个可迭代对象返回?个迭代器对象

next(my_iter) # 不会出错

参见下图:

迭代器的创建

☆可以利用内置函数iter()和一个序列(如字符串等)来创建。例如:

i = iter("abcd")

from collections.abc import Iterator #使用Iterator模块可以判断对象是否是迭代器

print(isinstance(i,Iterator)) # True,是迭代器

next(i) #可以使用内置函数next()来遍历迭代器的元素

参见下图:

下面使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值,一次值取一个值,当值取完了,再使用一次next()的时候,会报异常StopIteration,可以通过异常处理的方式来避免,try-except-else语句就是一个最常用的异常处理结构,例如:

my_list=[1,2,3]
li=iter(my_list) #将列表转换成一个列表迭代器
while True:
   try:
      print(next(li))
   except StopIteration:
      print("Over")
      break
   else:
      print("get!")

运行之,参见下图:

☆自定义迭代器类
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 。
下面给出一个数数的例子:

class MyCounter():
    def __init__(self, start, stop):
        self.start = start
        self.stop = stop 
        
    def __iter__(self):
        return self
 
    def __next__(self):
        if self.start <= self.stop:
              x = self.start
              self.start += 1
        else:
            raise StopIteration
        return x
 
C = MyCounter(10,12)
print(next(C))
print(next(C))

请注意,有了def __iter__(self)和 def __next__(self),类 MyCounter 就是迭代器了。

运行输出:

10
11

从字面意思,迭代就是重复反馈过程的活动,其目的通常是为了比较所需目标或结果,在python中可以用迭代器来实现。

优点:

迭代器在取值的时候是不依赖于索引的,这样就可以遍历那些没有索引的对象,比如字典和文件;

迭代器与列表相比,迭代器是延迟计算(惰性求值:azy evaluation),更节省内存。

【迭代器与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,比如列表含有中一千万个整数,需要占超过400M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用 next 方法时候才返回该元素(按需调用 call by need 的方式,本质上 python的for 语句循环就是不断地调用迭代器的next方法)】

缺点:

无法获取迭代器的长度,没有列表灵活;

只能往后取值,不能倒着取值。

二、生成器(generator)

官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-generator):返回一个 generator iterator 的函数。它看起来很像普通函数,不同点在于其包含 yield 表达式以便产生一系列值供给 for-循环使用或是通过 next() 函数逐一获取。

通常是指生成器函数,但在某些情况下也可能是指 生成器迭代器。如果需要清楚表达具体含义,请使用全称以避免歧义。

官方生成器指导(tutorial)https://docs.python.org/zh-cn/3/tutorial/classes.html#generators

在 Python 中,使用了 yield语句 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值。并在下一次执行 next() 方法时从当前位置继续运行。

yield 表达式和语句仅在定义 generator 函数时使用,并且仅被用于生成器函数的函数体内部。 在函数定义中使用 yield 就足以使得该定义创建的是生成器函数而非普通函数

当调用生成器(generator)函数时,它会返回一个称为生成器的迭代器。然后,该生成器控制生成器函数的执行。当调用生成器的一个方法时执行开始。此时,执行进行到第一个yield表达式,在那里它再次挂起,将表达式列表的值返回给生成器的调用者,或者如果省略表达式列表,则返回None。所谓挂起,意思是保留所有局部状态,包括局部变量的当前绑定等。当调用生成器的一个方法来恢复执行时,函数可以继续执行,就像yield表达式只是另一个外部调用一样。恢复后yield表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过for或next()内建函数),则结果为None。否则,如果使用send()方法,那么结果将是传递给该方法的值。

yield 语句在语义上等同于 yield 表达式。官方说明如下:
yield 语句https://docs.python.org/zh-cn/3/reference/simple_stmts.html#the-yield-statement
yield 表达式https://docs.python.org/zh-cn/3/reference/expressions.html#yieldexpr

下面解析之。

下面来看一个简单的生成器

def my_yield():
    print('first')
    yield 1
g=my_yield()
print(g)

运行之,参见下图:

生成器也是一个迭代器,测试上面简单的生成器是否是迭代器代码如下:

from collections.abc import Iterator
def my_yield():
    print('first')
    yield 1
g=my_yield() #
print(isinstance(g,Iterator)) #运行结果True

运行之,参见下图:

生成器的执行过程

下面给出一个长点的例子,由此了解执行流程

def my_yield():
    print('first')
    yield 1
    print('second')
    yield 2
    print('Third')
    yield 3
g=my_yield()
next(g)
next(g)
next(g)

运行之,参见下图:

1.定义生成器my_yield,并将其赋值给了g

def my_yield():

g=my_yield()

2.开始第一次执行next(),开始执行生产器函数 ,打印第一语句,遇到yileld的时候暂停,并返回一个1,如果你想打印返回值的话,这里会显示1

print('first')

yield 1

3.再执行2次,打印字符串(每执行一次都会暂停一下)

print('second')

yield 2

print('Third')

yield 3

4.如果再加一次next()就会报出StopIteration异常了

下面给出生成器的例子,分别使用next()函数、__next()__方法和for语句读取元素值的示例:

def test(n):  	# 定义生成器函数
    for i in range(n):  	# 迭代列表
        yield i*i  	# 函数定义中使用了 yield

g = test(5)  	# 调用生成器函数,生成一个生成器对象
print(next(g))	# 读取第1个元素值
print(g.__next__())	# 读取第2个元素值
for i in g: 	# 读取后面3个元素值
    print(i)

运行之,参见下图:

生成器中使用send()方法的示例

def down(n):	# 定义生成器函数
    while n >= 0:	# 设置递减循环的条件
        m = yield n	# 注意这句有yield,每次迭代生成的值并返回
        if m:	# 当条件为True,则改写递减变量的值
            n = m
        else:	# 正常情况下,m为None,则递减
            n -= 1
d = down(5)	# 调用生成器函数
for i in d:
    print(i)	# 打印元素
    #if i == 5:	# 当打印完第一个元素后,
    #    d.send(3)	# 注意这句,修改yield表达式的值为3

运行之,参见下图:

如果不设置

if i == 5:    # 当打印完第一个元素后,
        d.send(3)    # 注意这句,修改yield表达式的值为3

或注释掉,则

运行结果如下:

总之,用简单的话讲,迭代(Iteration)就是从某个地方(比如一个列表)取出一个元素的过程。当我们使用一个循环来遍历某个东西时,这个过程本身就叫迭代。

生成器(Generators)也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中(这样做会消耗大量资源),而是在运行时生成值。若想通过遍历来使用它们,要么用一个"for"循环,要么将它们传递给任意可以进行迭代的函数或结构。大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield(生出、产生)一个值。

下面,以计算斐波那契数列为例,说明是否用生成器的区别

先看使用生成器

#计算斐波那契数列,使用生成器版
def fibon(n):
    a =	b = 1
    for	i in range(n):
        yield a
        a, b =	b, a + b
#现在可以这样使用生成器
for x in fibon(5000):
    print(x)

用这种使用生成器方式,我们可以不用担心它会使用大量资源(内存)。下面是不使用生成器方式

#计算斐波那契数列,不用生成器版
def fibon(n):
    a =	b = 1
    result = []
    for	i in range(n):
        result.append(a)
        a, b =	b, a + b
    return result
#现在可以这样使用生成器,思考当调用时输入的参数很大时可能发生的问题
for x in fibon(5000):
    print(x)

思考当调用时输入的参数很大时可能发生的问题——它会使用大量资源(内存)。

三、生成器表达式(Generator expressions)

也有人称为生成器推导式

【官方说明 https://docs.python.org/zh-cn/3/reference/expressions.html?highlight=comprehension#generator-expressions 】

生成器表达式会产生一个新的生成器对象。 其句法与推导式相同,区别在于它是用圆括号而不是用方括号或花括号括起来的。例如: (x*y for x in range(10) for y in range(x, x+10))


【关于推导式(comprehension)可参见 https://blog.csdn.net/cnds123/article/details/117395558

列表推导和列表生成器表达式之间的差异十分重要,但很微妙。生成器推导式的结果是一个生成器对象。生成器对象类似于迭代器对象,具有惰性求值的特点,只在需要时生成新元素,比列表推导式具有更高的效率,空间占用非常少,尤其适合大数据处理的场合。

下面看一下列表推导和生成器表达式之间的差异:

L = [(i+2)**2 for i in range(10)]  #列表推导式
print(L)
G = ((i+2)**2 for i in range(10)) #列表生成器表达式
print(G)	

运行结果如下

使用生成器对象的元素时,可以使用生成器对象的__next__()方法或者内置函数next()进行遍历,或者直接使用for循环来遍历其中的元素。

列表生成器表达式对象元素的遍历示例:

G = ((i+2)**2 for i in range(10)) #列表生成器表达式
print(next(G)) # 读取第一个元素
print(G.__next__()) # 读取第二个元素
print(next(G)) # 读取第三个元素
for i in G:  # 读取剩余的元素
    print(i)

运行结果如下:

还可以根据需要将生成器表达式转化为列表或元组

将生成器对象转换为列表或元组示例:

G = ((i+2)**2 for i in range(10)) #列表生成器表达式
L=list(G) #将生成器对象转换为列表
print(L)

G = ((i+2)**2 for i in range(10)) #列表生成器表达式
T=tuple(G) #将生成器对象转换为元组
print(T)

运行结果如下:

进一步学习:

Python迭代器详解 https://blog.51cto.com/nxlhero/2480488

python可迭代对象、迭代器与生成器 https://mp.weixin.qq.com/s/--zEhMcQ6nzwzFguOVxaVg

生成器(generator) https://www.cnblogs.com/mswei/p/9283386.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值