待手绾青丝

待手绾青丝

待手绾青丝

庭中三千梨花树,再无一朵入我心。 心中只你一朵,似在心中,不在心中,可望可念可想不可及。

109 文章数
2 评论数
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月

06_迭代器与生成器

待手绾青丝
2024-10-23 / 0 评论 / 52 阅读 / 0 点赞

迭代器与生成器

一、迭代器

1、迭代器介绍

​ 所谓的 迭代器 就是用来迭代取值的工具

​ 而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代。

1.单纯的重复

while True:
    inp_uname = input('请输入一个数:').strip()
    print(inp_uname)
# 结果
请输入一个数:1
1
请输入一个数:2
2
请输入一个数:3
3
请输入一个数:4
4

2.真正的迭代过程

​ 下述while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值

goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
index = 0
while index < len(goods):
    print(goods[index])
    index += 1

# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch

3.可迭代对象

​ 通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串、列表、元组。对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行取值的方式,这就用到了迭代器。

​ 要想了解迭代器是什么?必须先搞清楚一个很重要的概念:可迭代对象(iterable)。从语法形式上讲,内置的有__iter__方法的对象都是可迭代对象,比如说:字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。

print('hello world'.__iter__())
print(['123', '456'].__iter__())
print(('123', '456').__iter__())
print({'name': 'lmy', 'age': 22}.__iter__())
print({'lmy', 'lyw', 'lyx'}.__iter__())
with open('a.txt', 'r') as f:
    print(f.__iter__())
    
# 结果
<str_iterator object at 0x0000020A7CF00B50>
<list_iterator object at 0x0000020A7CF00B50>
<tuple_iterator object at 0x0000020A7CF00B50>
<dict_keyiterator object at 0x0000020A7CEEA390>
<set_iterator object at 0x0000020A7CEDFA00>
<_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>

4.迭代器对象

调用*obj.iter()*方法返回的结果就是一个迭代器对象(iterator)。

​ 迭代器对象是内置有 iter()next() 方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象*.iter()*方法得到的仍然是迭代器本身,而执行迭代器的 .next() 方法就会计算出迭代器中的下一个值。

​ 迭代器是Python提供的一种统一的、不依赖于索引的迭代取值的方式,只要存在多个值,无论序列类型还是非序列类型都可以按照迭代器的方式取值。

s = {1, 2, 3}  # 可迭代对象s
i = iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i
print(next(i))  # 本质就是在调用i.__next__()
print(next(i))
print(next(i))
print(next(i))

# 结果
1
2
3
Traceback (most recent call last):
  File "C:\Users\xiaowu\Desktop\pythnon-blog\python\python.py", line 6, in <module>
    print(next(i))
StopIteration

5.疑问?

​ 为什么已经升级成为迭代器对象以后,却还是拥有*iter()方法,这就让我们很是疑惑了,我们知道一个可迭代对象拥有iter()方法的原因是为了将可迭代对象转换成为迭代器对象,但是已经是迭代器对象了为什么还有iter()*方法呢?

​ 原因:迭代器对象的*iter()方法是为了符合for循环的原理而存在的,因为for循环在工作的时候每次都会先调用可迭代对象的iter()方法,所以迭代器对象也拥有iter()*方法。

2、for循环原理

有了迭代器之后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下:

goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
goods_iter = goods.__iter__()  # 拿到迭代器对象
while True:
    try:
        print(goods_iter.__next__())  # 调用迭代器对象的__next__()方法
    except StopIteration:  # 捕捉异常终止循环
        break

# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch

for循环又称之为迭代循环,in的后面可以跟随任意可迭代对象,上述while循环可以简写为

goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
for item in goods:
    print(item)

# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch

for循环在工作的时候,首先会调用可迭代对象goods内置的*iter()方法拿到一个迭代器对象,然后再调用该迭代器对象的next()*方法将取到的值赋值给item,执行循环体完成一次循环,周而复始,直到捕捉StopIteration异常,迭代结束

3、迭代器的优缺点

​ 基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存

1.优点

1.为序列和非序列类型提供了一种统一的迭代取值的方式

2.惰性计算:迭代器对象表示的是一个数据流,可以只在需要的时候才调用*next()*来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,比如说:列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

2.缺点

1.除非取尽,否则无法获取迭代器的长度

2.只能取下一个值,不能回到开始,更像是一次性的,迭代器产生后的唯一目标就是重复执行 next() 方法直到值取尽,否则就会停留在某个位置,等待下一次调用 next()

3.如果想要再次迭代,只能重新调用可迭代对象的 iter() 方法来创建一个新的迭代器对象。

4.如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环取到值

goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
goods_iter = goods.__iter__()
while True:
    try:
        print(goods_iter.__next__())
    except StopIteration:
        break

for item in goods_iter:
    print(item)

4、总结

1.如果你知道你可以循环遍历某个对象,这是一个可迭代对象。

2.如果你知道你正在循环遍历的对象是在循环的时候计算出来,那么这是一个惰性可迭代对象(lazy iterable)。

3.如果你知道你可以传递一些东西给next函数,它就是一个迭代器(这是最常见的惰性可迭代对象)。

4.如果你可以循环多次而不用「耗尽」它,它不是一个迭代器。

5.如果你不能将某些东西传递给next函数,那么它不是一个迭代器。

6.Python3range 对象不是迭代器。 如果你正在指导别人关于 range 对象的知识,请不要使用「迭代器」一词,这会让人十分困惑,并可能导致他人开始滥用「迭代器」这个词

二、生成器

1、生成器与yield

1.创建生成器的第一种方式

如果函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值也就是生成器对象

def my_range(start, stop, step=1):
    print(f'start...')
    while start < stop:
        yield start
        start += step
    print('end...')

g = my_range(0, 6)
print(g)

# 结果
<generator object my_range at 0x00000233FCAF9A10>

2.生成器的本质就是一个迭代器

生成器内置有*iternext*方法,所以生成器本身就是一个迭代器

def my_range(start, stop, step=1):
    print(f'start...')
    while start < stop:
        yield start
        start += step
    print('end...')

g = my_range(0, 6)

print(g.__iter__)
print(g.__next__)

# 结果
<method-wrapper '__iter__' of generator object at 0x000002F591AF9A10>
<method-wrapper '__next__' of generator object at 0x000002F591AF9A10>

因而我们可以用*生成器.next()*触发生成器所对应函数的执行

def my_range(start, stop, step=1):
    print(f'start...')
    while start < stop:
        yield start
        start += step
    print('end...')

g = my_range(0, 5)
print(g.__next__())  # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
print(g.__next__())  # 再次调用,函数从上次暂停的位置继续执行,直到遇到新的yield
print(g.__next__())
print(g.__next__())  # 周而复始
print(g.__next__())
print(g.__next__())  # 触发函数执行没有遇到yield则没有值返回,即取值完毕抛出异常结束迭代

# 结果
start...
0
1
2
3
4
end...
Traceback (most recent call last):
  File "C:\Users\xiaowu\Desktop\pythnon-blog\python\python.py", line 14, in <module>
    print(g.__next__())  # 触发函数执行没有遇到yield则没有值返回,即取值完毕抛出异常结束迭代
StopIteration

既然生成器属于迭代器,那么必然可以使用for循环来进行迭代,如下所示:

def my_range(start, stop, step=1):
    print(f'start...')
    while start < stop:
        yield start
        start += step
    print('end...')

for i in my_range(0, 5):
    print(i)
    
# 结果
start...
0
1
2
3
4
end...

有了yield关键字,我们就有了一种自定义迭代器的实现方式,yield可以用于返回值,但不同于return,函数一旦遇到return结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

2、yield表达式应用

1.用函数采用yield

在函数内可以采用表达式形式的yield,此话怎么讲?如下代码所示:

def eater():
    print(f'准备要吃啦...')
    while True:
        food = yield
        print(f'得到一个食物:{food},开始吃掉它...')

# 可以拿到函数的生成器对象持续为函数体send值,如下所示:

g = eater()  # 得到生成器对象
g.__next__()  # 需要事先"初始化"一次,让函数挂起在food = yield,等待调用g.send()方法为其传值
g.send('水果玉米')
g.send('奥尔良烤鸡')

# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
得到一个食物:奥尔良烤鸡,开始吃掉它...

针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用*g.send()*方法为函数体传值,g.send(None)等同于g.next()

2.用装饰器完成yield生成器的初始化操作

我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下所示:

def init(func):
    def wrapper(*args, **kwargs):
        g = func(*args, **kwargs)
        g.__next__()
        return g

    return wrapper


@init
def eater():
    print(f'准备要吃啦...')
    while True:
        food = yield
        print(f'得到一个食物:{food},开始吃掉它...')


generator = eater()
generator.send('水果玉米')
generator.send('烧鸡')


# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
得到一个食物:烧鸡,开始吃掉它...

3.返回每次值的结果

def eater():
    print(f'准备要吃啦...')
    food_list = []
    while True:
        food = yield food_list
        print(f'得到一个食物:{food},开始吃掉它...')
        food_list.append(food)
        print(food_list)


generator = eater()
generator.__next__()
generator.send('水果玉米')
generator.send('烧鸡')

# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
['水果玉米']
得到一个食物:烧鸡,开始吃掉它...
['水果玉米', '烧鸡']

3、三元表达式

1.语法

三元表达式是Python为我们提供的一种简化代码的解决方案,语法如下:

res = 条件成立的时候返回的值 if 条件 else 条件不成立的时候返回的值

2.比大小

不使用三元表达式

def lmy_max(x, y):
    if x > y:
        return x
    else:
        return y
    
res = lmy_max(100, 300)
print(res)

# 结果
300

使用三元表达式

x = 1
y = 2
res = x if x > y else y  # 三元表达式

4、列表生成式

1.语法

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]

类似于

#类似于
res=[]
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)

2.使用场景

没使用列表生成式之前

egg_lis = []
for i in range(10):
    egg_lis.append(f'鸡蛋{i}')
print(egg_lis)

# 结果
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

使用列表生成式之后

print([f'鸡蛋{i}' for i in range(10)])

# 结果
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

5、生成器表达式

1.创建生成器对象的第二种方式

创建一个生成器对象,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将**[]换成()**,也就是:

(expression for item in iterable if condition)

2.对比列表生成式

对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象

print([i ** 2 for i in range(10)])
print((i ** 2 for i in range(10)))

# 结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x7fdc79341c80>

对比列表生成式,生成器表达式的优点自然是节省了内存(一次只产生一个值在内存中)

gen = (i ** 2 for i in range(3))
print(gen.__next__())
print(gen.__next__())
print(gen.__next__())
print(gen.__next__())  # 抛出异常StopIteration

3.读取大文件

如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

with open('udata.json', 'rb') as f:
    nums = (line.__len__() for line in f)
    total_size = sum(nums)  # 依次执行nums.__next__(),然后累加到一起得到结果
print(total_size)

4.生成式

1)列表生成式
print([i for i in range(10) if i > 2])
[3, 4, 5, 6, 7, 8, 9]
2)字典生成式
{k:v for i in range()}
3)集合生成式
print({s for s in range(5)})
{0, 1, 2, 3, 4}
文章不错,扫码支持一下吧~
上一篇 下一篇
评论
最新回复
文章目录
每日一句