迭代器和生成器、异常捕获

发布时间 2023-12-05 21:53:51作者: JessicaJJmm

一、迭代器(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  常见的错误类型

  1. 语法错误(SyntaxError):代码中存在语法错误,如拼写错误、缺少括号、缩进错误等。
  2. 名称错误(NameError):使用了未定义的变量或函数名。
  3. 类型错误(TypeError):使用了错误的数据类型或数据类型不匹配,如将字符串和数字相加。
  4. 索引错误(IndexError):使用了不存在的索引或切片。
  5. 键错误(KeyError):使用了不存在的字典键。
  6. 属性错误(AttributeError):尝试访问不存在的对象属性或方法。
  7. 文件不存在错误(FileNotFoundError):尝试打开不存在的文件时引发的错误。
  8. 除以零错误(ZeroDivisionError):尝试除以零时引发的错误。
  9. 异常错误(Exception):在代码中使用了未处理的异常。
  10. 导入错误(ImportError):尝试导入不存在的模块或函数。
  11. 内存错误(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
复制代码