迭代器和生成器、异常捕获
一、迭代器(Iterator)
1、可迭代对象(Iterable)和可索引对象
存储了元素的一个容器对象,且容器中的元素可以通过“__iter__( )”方法或“__getitem__( )”方法访问。可迭代对象不能独立进行迭代,可通过“for…in”遍历来完成
2、常见的可迭代对象
字符串、列表、元组、字典、集合、文件
## 使用__iter__()创建可迭代对象 class MyIterable: # 构造函数 def __init__(self, data): self.data = data def __iter__(self): return iter(self.data) my_iterable = MyIterable([1, 2, 3]) for i in my_iterable: print(i) ## 使用__getitem__() 创建可索引对象 class MyIndexable: def __init__(self, data): self.data = data def __getitem__(self, index): return self.data[index] my_indexable = MyIndexable([1, 2, 3]) print(my_indexable[2])
3、迭代器对象
可迭代对象调用__iter__( )方法成为迭代器对象,迭代器对象是可以记住遍历的位置的对象。
4、迭代器特性
-
迭代器对象可以使用
iter()
函数来创建。 -
迭代器对象可以使用
next()
函数来访问容器中的元素。 -
当迭代器对象遍历完容器中的元素时,它将引发
StopIteration
异常。 -
可以使用
for
循环来遍历迭代器对象,因为for
循环自动处理了StopIteration
异常。 -
迭代器对象在遍历过程中只能向前移动,不能后退或重置。
-
迭代器对象可以被多个迭代器同时使用,每个迭代器都会维护自己的迭代状态。
-
生成器对象是一种特殊的迭代器,它们可以使用
yield
语句来定义。 -
迭代器对象可以用于惰性计算,即只有在需要时才计算下一个元素,从而节省内存和计算资源。
-
迭代器其实是一种不依赖于索引取值的方式!
5、易混淆
ll = [1, 2, 3, 4] # StopIteration 当数据被取值完的时候,如果在次next会直接报错 res = ll.__iter__() print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) # 取出来的值都是 1,因为每次打印都调用的ll.__iter__方法,数据被重置 print(ll.__iter__().__next__()) # 1 print(ll.__iter__().__next__()) # 1 print(ll.__iter__().__next__()) # 1 print(ll.__iter__().__next__()) # 1
二、生成器(generator)
1、背景
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
理解生成器,最好的方法就是给他取个突出其本质的别名:生成数据的机器代码,同时生成器是一种特殊的迭代器。
2、创建的生成器
方式 1:元组的生成式就是生成器
G = (x * 2 for x in range(5)) print(G) <generator object <genexpr> at 0x7fc528166c50>
方式 2:使用 yield 关键字
模拟range 的功能
# range 可以传参,首先想到是函数 # 循环打印出指定范围的数字 def my_range(start, end=None, step=1): if not end: # 如果 end 为空执行下面的代码 end = start # end=10 start = 0 # start=0 while start < end: # 顾头不顾尾 yield start # 返回 start 的值 start += step print(my_range(10)) # <generator object my_range at 0x7fad80056c50> print(list(my_range(1, 10, 2))) # [1, 3, 5, 7, 9] print(list(my_range(2, 10))) # [2, 3, 4, 5, 6, 7, 8, 9] print(list(my_range(10))) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
注意:由于 my_range()是生成器,数据只有在使用的时候才会打印出来,上面使用list 转数据类型调用,或者使用下面的 for 循环。
for i in my_range(10): # __next__ print(i)
3、yield 传参
def eat(name): print('%s正在干饭' % name) while True: food = yield print('%s正在吃%s' % (name, food)) # 函数里面只要有yield关键字,就不会执行函数,变成了生成器 res = eat('kevin') res.__next__() # 要求调用next()方法取值,yield 才能正常拿到send传的参数 res.send('馒头')
4、生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
5、__next__()&next()和send()方法
1、__next__()&next()的区别
__next__()是生成器对象的实例方法,next()是python的内置方法。他们实现的功能是一样的,只能对可迭代的对象使用。
2、next()和send()用以唤醒生成器
当生成器函数中执行yield时,程序会卡在yield的地方不执行,next()和send()的作用就是唤醒卡住的程序,让他继续执行。
3、send()方法的作用
send()作为生成器对象的实例方法,可以向生成器发送数据并唤醒生成器函数。一般send()方法要写在next()方法后面,当next()和send()写在一块时,相当于唤醒两次生成器。
三、异常捕获
1、代码不能正常运行的时候,编辑器会曝出错误提示,包含错误追踪、错误类型和错误详细信息
2、python 常见的错误类型
- 语法错误(SyntaxError):代码中存在语法错误,如拼写错误、缺少括号、缩进错误等。
- 名称错误(NameError):使用了未定义的变量或函数名。
- 类型错误(TypeError):使用了错误的数据类型或数据类型不匹配,如将字符串和数字相加。
- 索引错误(IndexError):使用了不存在的索引或切片。
- 键错误(KeyError):使用了不存在的字典键。
- 属性错误(AttributeError):尝试访问不存在的对象属性或方法。
- 文件不存在错误(FileNotFoundError):尝试打开不存在的文件时引发的错误。
- 除以零错误(ZeroDivisionError):尝试除以零时引发的错误。
- 异常错误(Exception):在代码中使用了未处理的异常。
- 导入错误(ImportError):尝试导入不存在的模块或函数。
- 内存错误(MemoryError):尝试使用过多的内存时引发的错误。
3、解决异常
语法结构
except 可以写多种错误类型
try # 被监测的代码: 一般是可能会发生的错误 except 错误类型1 as e print(e) except 错误类型2 as e print(e) except 错误类型3 as e print(e) except 错误类型4 as e print(e)
万能的捕获结构(关键字 Exception)
try
except Exception as e
由于错误类型太多,没法全部列出,使用Exception关键字可以捕获任意类型的错误
try: # print(name) # 1/0 # l = [1,2,3] # print(l[6]) # 1/0 d = {'a':1} print(d['aaa']) except NameError as e: print(e) # name 'name' is not defined except IndexError as e: print(e) # name 'name' is not defined except Exception as e: print(e) else: print('看一下else什么时候走的?') finally: print('看一下finally什么时候走?')
注意:
没有异常的时候else会走,有异常的时候else 不执行代码块
finally是不管有没有异常都会走
引用案例
统计函数的执行次数,即每次运行,输出这是第几次调用函数
def jilu(func): def inner(*args, **kwargs): global res try: with open(r'存储文件.txt', 'r', encoding='utf8') as f: res = int(f.read()) except Exception as e: res = 0 finally: res += 1 res = str(res) with open(r'存储文件.txt', 'w', encoding='utf8') as f1: f1.write(res) res1 = func() return res1 return inner @jilu def index(): print('调用函数') index() print(res)
刚开始文件没有内容,会报错
ValueError: invalid literal for int() with base 10: ''
一旦报错,except进行捕捉:res 赋值为 0
finally 无论怎么都执行,res +=1,将结果写入文件
第二次调用,文件有内容,不在报错。
finally 无论怎么都执行,在原有的基础上 res +=1,将结果写入文件
4、断言 assert
s = 1 + x
assert s == 2
assert 条件 条件必须成立,如果不成立,代码在这一行直接中断
5、raise
关键字
用于引发异常。它允许开发者显式地引发一个指定的异常,从而中断当前的代码执行流程,并将控制权传递给异常处理程序。
class Animal: # @abc.abstractmethod 不使用抽象方法实现强制子类有某个特性 def speak(self): raise Exception("请先实现speak方法") class People(Animal): # def speak(self): # pass pass """主动抛出异常""" stu = People() stu.speak() # 输出 # raise Exception("请先实现speak方法") # Exception: 请先实现speak方法
6、自定义异常信息
class MyException(BaseException): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg raise MyException("这是异常信息")
四、小练习
1、不使用 for 循环取出 l 的元素
l = [1, 2, 3, 4, 5, 6, 7] l1 = l.__iter__() count = 0 while count <= len(l): try: print(next(l1)) count += 1 except Exception: break