十八、可迭代对象、迭代器对象和生成器


什么是迭代?
每一次过程的重复称为一次迭代,而每一次迭代的结果作为下一代初始值,单纯的重复不是迭代。

# 迭代取值
my_list = [1, 2, 3, 4, 5, 6]
index = 0

while index < len(my_list):
    print(my_list[index])
    index += 1

上述while循环就是一个迭代过程,满足重复,每次打印之后将新的值赋值给index作为下一次循环的索引,知道达到结束条件后取完所有的值。

一 可迭代对象

通过索引的方式进行迭代取值,实现简单,但是仅适用于有序的数据类型,像dict和set等非有序数据类型,必须找到一种不依赖索引取值的方法,所以就有了迭代器。
了解迭代器之前,先要来了解一个概念,什么是可迭代对象(iterable),有内置方法__iter__方法的就是可迭代对象,字符串、列表、字典、元组、集合、打开的文件都是可迭代对象(文件本身也是一个迭代器对象)。

# 可迭代对象
'jasper'.__iter__()
[1, 2].__iter__()
{'name': 'jasper'}.__iter__()
(1,).__iter__()
{1, }.__iter__()

二 迭代器对象

可迭代对象调用__iter__方法返回的就是一个迭代器对象(iterator),迭代器对象内置方法有__iter__和__next__,迭代器对象调用iter方法得到的还是迭代器本身,而调用next方法返回的迭代器中的一个值。迭代器是python提供的一种不依赖索引取值的方式,无论是有序数据类型还是无序数据类型都可以进行取值。

# 迭代器对象
name = 'jasper'  # 可迭代对象iterable
res = name.__iter__()  # 迭代器对象iterator
print(res)  # <str_iterator object at 0x000001E943A230D0>
print(res.__next__())  # j
print(res.__next__())  # a
print(res.__next__())  # s
print(res.__next__())  # p
print(res.__next__())  # e
print(res.__next__())  # r
print(res.__next__())  # StopIteration 抛出异常 代表无值可取 迭代停止

name1 = 'jack'
print(name1.__iter__().__next__())  # j 每次产生都是不一样的iterator object
print(name1.__iter__().__next__())  # j
print(name1.__iter__().__next__())  # j
print(name1.__iter__().__next__())  # j

三 迭代器优缺点

优点:

  1. 为有序和无序的数据类型提供了统一的取值方式
  2. 迭代器对象是一个数据流,可以只在需要时才去调用next方法来计算出一个值,对于迭代器本身来说,迭代器只占用内存中一块内存,存放的是迭代器的内存地址,那么这个迭代器就可以存放很大的数据流而不占用多的空间,相对于其他容器类型,如果有很多个数据值,需要把每个数据放到内存中,可能会造成内存的溢出。

缺点:

  1. 只能全部取完值才可以获取迭代器的长度
  2. 不能取下一个值,如果要再次迭代同一个对象,必须创建一个新的迭代器对象,如果两个循环使用了同一个迭代器,那么只有先运行的循环可以取到值。
# 两个循环中使用一个迭代器 只有一个可以取到值
l = [1, 2, 3, 4, 5]
res = l.__iter__()

count = 0
while count < len(l):
    print(res.__next__())
    count += 1
    
count1 = 0
while count1 < len(l):
    print(res.__next__())  # StopIteration
    count += 1

三 for循环原理

for i in range(5):
    print(i)

# 原理
res = range(5).__iter__()  # range(5)有__iter__方法 说明是一个iterable object

# 调用iter方法后变为iterator object
print(res.__next__())  # 0
print(res.__next__())  # 1
print(res.__next__())  # 2
print(res.__next__())  # 3
print(res.__next__())  # 4
print(res.__next__())  # StopIteration 迭代停止

for循环的语法是for 变量名 in 可迭代对象:
for循环的原理就是将iterable object调用__iter__方法,转换成iterator object,在调用iterator的__next__方法,将返回值赋值给变量名,完成一次迭代,周而复始,直至捕捉到StopIteration异常,迭代停止。

四 异常处理

4.1 什么是异常

程序运行时报错就是异常,程序一旦出现异常,如果没有处理,程序的运行就会终止。
在这里插入图片描述

异常的类型:NameError、TypeError、IndexError、SyntaxError、KeyError等

异常的分类:

  1. 语法错误
    这种错误是不应该犯的,应在程序运行前就改正。
  2. 逻辑错误
    出现错误时修改即可

4.2 异常操作

为了保证程序的可靠性和容错性,即在程序遇到错误时有相应的处理机制让程序不崩溃掉,我们需要对程序进行异常处理。

4.2.1 语法

# 语法
"""
try:
    被检测代码块
except 异常类型 as e:
    检测到异常执行的代码
"""

4.2.2 实际操作

利用异常处理用while循环实现for循环功能

# while循环实现for循环功能

my_list = [1, 2, 3, 4, 5]

res = my_list.__iter__()

while True:
    try:
        print(res.__next__())
    except StopIteration:  # 当捕获到StopIteration异常时结束循环
        break

本来程序一旦异常,程序就会立刻结束,有了异常处理之后,在捕获到异常类型时,就会执行except的内部代码,其余代码正常运行。

4.2.3 异常操作补充

1.当一个程序需要捕捉不同类型的异常时,需要使用多个except,类似于if分支,在执行一个except分支后,就不会执行另外的分支。

try:
    被检测代码块
except 异常类型1 as e:
    检测到异常1执行的代码
except 异常类型2 as e:
    检测到异常2执行的代码
except 异常类型3 as e:
    检测到异常3执行的代码
except 异常类型4 as e:
    检测到异常4执行的代码

2.当我们想让不同类型的异常采用一种逻辑代码处理 可以将异常类型写在一个元组中

try:
	被检测代码块
except (TypeError, IndexError, NameErroe...) as e:
	检测到异常执行的代码

3.当我们想捕获所有的异常,就可以采用万能异常类型(Exception)

my_list = [1,2,3,4]
try:
    my_list[5]
except NameError:
    print('name error')
except Exception:
    print('Error')  # Error

4.在多分支的except后还可以跟一个else(必须跟在except后且不能单独存在),再有在else前所有的except都没执行才会执行else

try:
    name = 'jasper'
except IndexError:
    print('NameError')
else:
    print('except不执行才会执行')

5.try还可以和finally连用,无论被检测的代码是否触发异常,都会执行finally的子代码。

try:
    name
except NameError:
    print('NameError')
finally:
    print('except执不执行都会执行')

6.如果想主动抛出异常,就需要使用到raise关键字

name = input('>>>:').strip()

if name == 'jasper':
    raise Exception('Error')
else:
    print('...')

在这里插入图片描述
7.断言语句assert

assert isinstance(1, str)

在这里插入图片描述表达式如果不成立,就会抛出AssertionError

总结:异常捕获能少用就少用 被try检测的代码越少越好

五 生成器对象

5.1 生成器对象使用

某函数体代码包含yield关键字,调用函数,不会执行函数,而是返回一个生成器对象(generator),生成器有__iter__和__next__方法,所以生成器对象本质就是一个迭代器对象。

自定义一个range方法

# 自定义range方法

def my_range(start, end=None, step=1):
    if step < 0:
        if start < end:
            return
        else:
            while start > end:
                yield start
                start += step
    else:
        if not end:
            end = start
            start = 0
        while start < end:
            yield start
            start += step


res = my_range(5)
print(res)  # <generator object my_range at 0x000001C3D4D0C890>

所以可以使用next方法使函数运行

print(res.__next__())  # 0
print(res.__next__())  # 1
print(res.__next__())  # 2
print(res.__next__())  # 3
print(res.__next__())  # 4
print(res.__next__())  # StopIteration

生成器是迭代器,那么肯定是可迭代对象,就可以使用for循环

for i in my_range(5):
    print(i)
# 输出 0\n1\n2\n3\n4\n

5.2 yield其他用法

def eat():
    print('打算吃什么')
    while True:
        food = yield
        print(f'打算吃{food}')


res = eat()
print(res)  # <generator object eat at 0x00000217922CC890>
res.__next__()  # 打算吃什么 让函数停在yield
res.send('烤鸡')  # 打算吃烤鸡
res.send('烤鸭')  # 打算吃烤鸭

函数内有yield关键字,调用一次函数,转换成生成器对象,调用__next__方法让函数停在food = yield位置,等待调用send方法为其传值,并自动调用__next__方法。

5.3 生成器表达式

语法:(表达式 for 变量名 in 可迭代对象)
返回值就是一个生成器对象

res = (i**2 for i in range(5) if i != 2)
print(res)  # <generator object <genexpr> at 0x000002209C626970>
print(list(res))  # [0, 1, 9, 16]

六 索引取值与迭代取值差异

  • 索引取值
    优点:可以重复随意获取任意一个值
    缺点:只能针对有序容器类型,无序的取不了
  • 迭代取值
    优点:提供了统一的取值方式,省空间
    缺点:不能倒着取,取完在想取需生成一个新的迭代器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值