异常处理、迭代器、推导式、生成器
异常处理:
导致程序运行过程中出现异常中断和退出的错误,我们称之为异常。
为了保证程序的正常运行,提高程序的健壮性和可用性。我们应当尽量考虑全面。将可能出现的异常进行处理
python处理异常的机制:
基本语法:
# try: # pass # 程序代码 # except Exception: # pass # 程序异常使执行的代码 # try: # print('123') # a = 1/0 # except Exception as e: # print(e) # print('after except!') # 运行结果: # 123 # division by zero # after except! # 当程序发生的异常不在捕获列表中时,依然会抛出异常 # try: # a = 1/0 # except IndexError as e: # IndexError 表示下标索引错误 # print(e) # 运行结果报错:ZeroDivisionError: division by zero
try......except
在python的异常中,有一个通用异常:Exception,它可以捕获任意异常
1)很多时候程序只会弹出那么几个异常,没有必要针对所有异常进行捕获,那样的效率会很低
2)根据不同的异常种类,制定不同的处理措施,用于准确判断错误的类型,存储错误日志
(1)函数嵌套处理异常
# def f1(): # a = 1/0 # def f2(): # print('f2') # f1() # def f3(): # print('f3') # f2() # try: # f3() # except Exception as e: # print(e) # 运行结果: # f3 # f2 # division by zero
(2)try.....except的嵌套
# 如果一个异常没有与任何的except匹配,那么这个异常将会传递给上层的try中,直到程序最顶端如果还没有被捕获,那么将弹出异常
# try: # try: # a = 1/0 # except IndexError as e1: # print('this is e1:',e1) # except ZeroDivisionError as e2: # print('this is e2:',e2)
# 运行结果:this is e2 division by zero
(3)使用一个try跟多个except(推荐使用)
1 # try: 2 # a = 1/0 3 # except IndexError as e1: 4 # print('this is e1:',e1) 5 # except ZeroDivisionError as e2: 6 # print('this is e2:',e2)
7 # 运行结果:this is e2: division by zero
# (4)或者except后面跟一个元组,元组中包含多个异常类,有点像Exception的特殊形式
1 # try: 2 # a = 1/0 3 # except (IndexError,ZeroDivisionError): 4 # print('error')
try...except...finally(else)...
# finally和else子句:
# else子句必须放在所有的except子句之后,会在try子句没有发生异常的时候执行
# finally子句无论try和except情况如何finally子都会执行
1 # try: 2 # a = 1/0 3 # except ZeroDivisionError as e: 4 # print('error') 5 # else: 6 # print('else') 7 # finally: 8 # print('finally')
raise:主动抛出异常
# 主动抛出异常:raise
# python内置了一个关键字raise,可以主动触发异常,作为警告或者特殊处理
# 使用raise抛出自定义的异常
# 例题:用户录入自己的性别:1表示男 2表示女 # class MyError(Exception): # def __init__(self,msg): # self.msg = msg # def __str__(self): # return self.msg # sex = input('enter your sex:') # if sex!='1' and sex!='2': # raise MyError('您输入的性别有误!') # else: # print('right!') # 运行结果:报错终止程序 # raise MyError('您输入的性别有误!') # __main__.MyError: 您输入的性别有误!
自定义异常
# 自定义异常应该继承Exception类,直接继承或间接继承都可以
# class MyErr(Exception): # def __init__(self,msg): # self.msg = msg # def __str__(self): # return self.msg # err = MyErr('find error range') # print(err) # find error range
迭代器
# 迭代:通过for循环遍历"对象"的每一个过程,这里的对象指的就是可迭代对象。for循环遍历的只能是可迭代的对象
# 可迭代对象:list/tuple/string/dict/set/bytes/文件都是可迭代的数据类型/可迭代对象
# 如何判断一个对象是否为可迭代对象:可以通过collections模块作用在isinstance中来判断对象是否可迭代
# from collections import Iterable # Iterable迭代类型 # from collections.abc import Iterable # class Test(): # pass # alist = [1,2,3] # str = 'abc' # t = Test() # print(isinstance(alist,Iterable)) # True # print(isinstance(str,Iterable)) # True # print(isinstance(t,Iterable)) # False
python提供了不依赖于索引取值的方式就是迭代器迭代取值
# 迭代器是一种可以被遍历的对象,并且能作用于next()函数
# 性质:迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完结束。
# 迭代器只能往后遍历不能回溯,不像列表可以根据搜索引取值
迭代器作用:节省内存
iter()和next()
# 迭代器通常要实现两个基本方法:iter()/.__iter__()和next()/.__next__()
# 注:1、可迭代对象并不一定是迭代器
# 2、常见的数据结构,字符串、列表、元组都属于可迭代对象,并不是迭代器
# 可以使用type()查看列表和迭代器类型的不同:
# 使用python内置的iter()方法创建迭代器对象
# from collections.abc import Iterable
# alist = [1,2,3]
# print(type(alist)) # <class 'list'>
调用可迭代对象的下的__iter__方法会将其装换成迭代器,当进行迭代时才会调用iter方法
# # 将列表转换成迭代器
# iter_alist = iter(alist)
# print(type(iter_alist)) # <class 'list_iterator'>
# 使用next()方法获取迭代器的下一个元素
from collections.abc import Iterable alist = [1,2,3] iter_alist = iter(alist) # print(next(iter_alist)) # print(next(iter_alist)) # print(next(iter_alist)) # 运行结果: # 1 # 2 # 3
使用for循环遍历迭代器:
# for i in iter_alist: # print(i) d = {"a":1,"b":2,"c":3} for k in d: print(k)
for循环的工作原理:(也可称之为迭代器循环)
1、d.__iter__()得到一个迭代器对象
2、迭代器对象.__next__()拿到一个返回值,然后该返回值赋给k
3、循环往复步骤2,直到抛出StopInteration异常,for循环会捕捉异常结束循环
总结:
# 可迭代对象的优缺点:
# 优点:可以直观查看里面的对象,如直接查看列表的内容
# 缺点:全部内容要加载至内存中,故占用内存
# 迭代器的优缺点:
# 优点:提供了一种通用不依赖索引的迭代取值方式;
# 节省内存,迭代器在内存中相当于只占一个数据的空间:因为每次取值上一条数据会在内存释放,加载当前的此条数据。
# 缺点:因为有next()方法,即只能往后取值,不能往前,取值不能取指定的某一个值;无法预测迭代器的长度
# 总结:迭代器和可迭代的区别
可迭代对象 Iterable 迭代器Iterator
# 1、凡是可作用于for循环的对象都是可迭代类型
# 2、凡是可作用于next()函数的对象都是迭代器对象
# 3、list、dict、str等是可迭代对象但不是迭代器,因为next()函数无法调用他们
# 可以通过iter()函数将它们转换成迭代器
自定义迭代器
# 为了让我们自己写的类成为一个迭代器,需要在类里实现__iter__()和__next__()方法
# 实际上在使用next()函数的时候,调用的就是迭代器对象__next__方法
# python要求迭代器本身也是可迭代的,
# 所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器
# 迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可
# 例题:#迭代生成指定范围数列的平方值 # class IterObj(): # def __init__(self,start,end): # self.start = start # self.end = end # self.index = 1 # def __iter__(self): # print('iter') # return self # def __next__(self): # if self.index > self.end: # raise StopIteration # res = self.index **2 # self.index +=1 # print('next') # return res # i = IterObj(1,5) # print(type(i)) # for j in i: # print(j)
# 斐波那切数列实现
# class IterObj(): # def __init__(self,count): # self.count = count # self.index = 0 # 记录生成第几个元素 # # self.num1 = 0 # self.num2 = 1 # self.num3 = 1 # def __iter__(self): # return self # def __next__(self): # if self.index > self.count: # raise StopIteration # value = self.num1 # self.index += 1 # self.num1,self.num2,self.num3 = self.num2,self.num3,self.num2+self.num3 # return value # obj = IterObj(10) # for i in obj: # print(i)
# 实现类似iter功能的类:可以使得将列表转换成迭代器
# class IterObj(): # def __init__(self,alist): # self.alist = alist # self.index = 0 # def __iter__(self): # return self # def __next__(self): # if self.index >= len(alist): # raise StopIteration # value = alist[self.index] # self.index += 1 # return value # obj = IterObj([1,2,3]) # for i in obj: # print(i)
推导式
(1)列表推导式:
语法:[表达式 for item in 可迭代对象 if 条件] 返回结果为列表
lst = [x*2 for x in range(1,10)]
# 增加条件语句:alist = [x*x for x in range(1,11) if x%2==0]
# 多重循环:
# 同时循环a和b两个变量:re = [a+b for a in '123' for b in 'abc']
(2)字典推到式
语法:{键:值 for k in 可迭代对象 if 条件}
字典推导式 {k:v for k,v in dic.items()}
# dic = {x:x**2 for x in (2,4,6)} # x:x**2指字典的key:value
例1 将一个字典的键值对倒换
dic = {"id":1001,"name":"yuan","age":22,"weight":"70kg"}
new_dic={v:k for k,v in dic.items()}
# {1001: 'id', 'yuan': 'name', 22: 'age', '70kg': 'weight'}
例2:将一个字典的键转换成大写
new_dic={k.upper():v for}
new_dic={k.upper():v for k,v in dic.items()}
print(new_dic)
# {'ID': 1001, 'NAME': 'yuan', 'AGE': 22, 'WEIGHT': '70kg'}
例3:保留name与age的键值对
new_dic={k:v for k,v in dic.items() if k=='name' or k=='age'}
print(new_dic) # {'name': 'yuan', 'age': 22}
(3)集合推导式
# a = {x for x in 'sfgfgh'}
(4)元组推导式
# tup = (x for x in range(9)) # 是一个生成器
# print(tup) # <generator object <genexpr> at 0x000001F55F9955F0>
# tup = tuple(x for x in range(9))
# print(tup) # (0, 1, 2, 3, 4, 5, 6, 7, 8)
生成器
数据序列或集合内的元素的个数非常巨大,如果全造出来并放入内存,对计算机的压力非常大 如果元素可以按照某种算法推算出来,需要该元素的话那就计算到哪个元素,那么就可以在循环的过程中不断推算出后续的元素,而不必创建完整的元素集合,从而节省大量的空间。在Python中,这种一边循环一边计算出元素的机制,称为生成器:generator。 **生成器是一种特殊的迭代器,生成器自动实现了”迭代器协议“(即iter方法和next方法)**,生成器就是自定义的迭代器
创建生成器 创建生成器的两种方式:
1)生成器推导式:
tup = (x for x in range(3)) # 可以通过next()函数获得generator的下一个返回值 next(tup) print(type(tup)) # <class 'generator'> # 通常情况下,使用for循环遍历生成器 for i in tup: print(i)
2)yield关键字创建生成器
在 Python中,使用yield返回的函数会变成一个生成器(generator)。 在调用生成器的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行next()方法时从当前位置继续运行。下面重点理解yield关键字的使用:
*yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器
*当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
*当你使用for进行遍历的时候或者调用next函数后,函数中的代码才会执行
def mySum(): print('before') print('after') yield 'result' f = mySum() print(type(f),next(f)) # 运行结果: # before # after # <class 'generator'> result
思考:下述函数的执行结果是什么?
def yieldTest(): i = 0 while i < 3: temp = yield i print(temp) i += 1 obj = yieldTest() # 返回的生成器对象 print(next(obj)) print(next(obj)) 运行结果: 0 # yield i 停止 None # 将None赋值给temp 1 # i+1 = 1 yield i 停止 next()会触发函数体代码的运行,然后遇到yield停下来,将yield后的值当作本次调用的结果返回 for i in yieldTest(): # 就是迭代器 print(i) 运行结果: 0 None 1 None 2 None
总结yield:
有了yield关键字,我们就有了一种自定义迭代器的实现方式,yield可以用于返回值,但不同于return,函数一遇到return就结束了,而yield可以保存函数的运行状态挂起函数用来返回多次值