python第11课,生成列表式 生成器,迭代器,异常

xiaoxiao2021-02-28  23

#列表生成式 # 是python内置的简单却强大的可以用来创建list的生成式 #例如要生成[1,2,3,4,5,6,7,8,9] 可以用list(range(1,10)) print(list(range(1,10))) #要生成[1*1,2*2,3*3,4*4...10*10]循环的方法 l=[] for x in range(1,11): l.append(x*x) print(l) #但是循环太繁琐,如果用列表生成式 print([x*x for x in range(1,11)]) #生成式元素放在前边,后边跟for循环 #for循环后还可以加上if判断,如筛选出仅偶数的平方 print([x*x for x in range(1,11) if x%2==0]) # 还可以使用两层循环,可以生成全排列: print([m+n for m in range(1,4) for n in range(4,8)]) #利用列表生成式可以写出很简洁的代码,例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现: import os print([d for d in os.listdir('.')]) #for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value: d={'x':'a','y':'b','z':'c'} for k,v in d.items(): print(k,'=',v) #所以,列表生成式也可以使用两个变量来生成list d={'x':'a','y':'b','z':'c'} print([k+'='+v for k,v,in d.items()]) #最后把一个list中所有的字符串变成小写 l=['Hello', 'Word','IMB','Apple'] print([d.lower() for d in l]) #生成器 #通过列表生成式,我们可以直接创建列表。但是,受到内存限制, # 列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的内存空间,而且,如果仅用前面几个元素的话,那后面的元素都拜拜浪费了 # 所以,列表如果可以按照某种算法推算出来,那我们在循环中能否不断地推出后续的元素呢 # 这样就不必创建完整的list,从而节省大量的空间,在python中,这样一边循环一边计算的机制,称为生成器:generator #要创建一个genrator,有很多种办法,第一种方法很简单,只 # 要把一个列表生成式[]改成(),就创建了一个生成器 l=[x*x for x in range(10)] print(l) g=(x*x for x in range(10)) print(g) #l为一个list,g为一个generator,list元素可以直接打印,generator #怎么打印呢?可以通过next()函数获取generator的下一个返回值 print(next(g)) print(next(g)) print(next(g)) print(next(g)) #generator保存的是算法,每次调用next(g),就计算出下一个元素的值,知道计算到最后一个元素,没 #有最后一个元素时,抛出sopIteration的错误 # 当然,上面这种不断调用next(g)的方法是在是太变态了,正确的方法是使用for循环,因为generator也是可迭代的 g=(x*x for x in range(10)) for n in g: print(n) #所以,我们创建了一个generator后,基本上不用调用next()函数,而是通过for循环来迭代它,并且不需要担心stopinteration的错误 # generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现是,还可以用函数来实现 # 比如,著名的斐波拉契数列,除第一个和第二个数外,任意一个数都由前面两个数相加得到: # 斐波拉契数列用列表生成式写不出来,但是,用函数打印出来和容易: def fib(max): n,a,b=0,0,1 while n<max: print(b) a,b=b,a+b n=n+1 return 'done' fib(20) #注意:赋值语句a,b=b,a+b 相当于 # t=(b,a+b) #t是一个tuple # a=t[0] #fib函数定义了推算规则,从第一个元素开始,推算出后续任意元素,这种逻辑非常类似generator #这个函数和generator仅一步之遥,要把fib函数变成generator,只需要把print(b) # 改成yield b就可以了 def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n=n+1 return 'done' fib(10) #这就是定义generator的另一个方法,如果一个函数包含yield关键字,那么这个函数就 # 就不再是一个普通函数,而是一个generaor #执行流程比较难理解。函数是顺序执行,遇到return语句或最后一句就返回,而变成generator # 的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行是从上次返回的yield语句处执行 #举个例子,定义一个generator,依次返回数字1,3,5 # def odd(): # print('step 1') # yield(1) # print('step 2') # yield(3) # print('step 3') # yield (5) #调用该generator时,首先要生成一个generator对象,然后用next() # 函数不断获得下一个返回值 # o=odd() # next(o) # next(o) # next(o) #可以看到,odd不是普通函数,而是generator在执行中,遇到yield就中断,下次又继续执行, # 回到fib离职,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无线序列出来 #同样的,吧函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接用forlai # 循环来迭代 for n in fib(10): print(n) # 但是用for循环调用generator时,发现拿不到generator的return返回值,如果想拿到返回值,必须捕获stopinteration的 value中 g=fib(b) while True: try: x=next(g) print('g:',x) except StopIteration as e: print('Generator return value:', e.value) break #迭代器 # 我们知道,可以直接作用于for循环的数据类型有以下几种: # 一类是集合数据类型,如list tuple dict set str等 #一类是generator,保括生成器和带yield的generation function #这些可以直接作用于for循环的对象统称为可迭代对象:Iterator #可以用isinstance()判断一个对象是否是Iterable对象 from collections import Iterable print(isinstance([],Iterable)) print(isinstance({},Iterable)) print(isinstance((x for x in range(10)),Iterable)) print(isinstance('abc',Iterable)) print(isinstance(100,Iterable)) #而生成器不但可以作用于for循环,还可以北next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了 #可以被next()函数调用并不断返回下一个值得对象成为迭代器:Iterator #可以使用isinstance()判断一个对象是否是Iterator对象: from collections import Iterator print(isinstance(100,Iterator)) print(isinstance([], Iterator)) print(isinstance({}, Iterator)) print(isinstance('abc', Iterator)) print(isinstance((x for x in range(10)), Iterator)) # 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator # 把list、dict、str等Iterable变成Iterator可以使用iter()函数: print(isinstance(iter([]), Iterator)) print(isinstance(iter({}), Iterator)) print(isinstance(iter('abc'), Iterator)) #你可能会问,为什么list、dict、str等数据类型不是Iterator? # 这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。 # iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然 # 错误处理 #在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数open(),成功时返回文件描述符(就是一个整数),出错时返回-1。 #用错误码来表示是否出错十分不便,因为函数本身应该返回的正常结果和错误码混在一起,造成调用者必须用大量的代码来判断是否出错: def foo(): r=some_function() if r==(-1): return(-1) return r def bar(): r=foo() if r==(-1): print('error') else:pass # 一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。 # 所以高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外 # try # 让我们用一个例子来看看try的机制 try: print('try...') r=10/2 print('result:',r) except ZeroDivisionError as e: print('except:',e) finally: print('finally...') print('END') #当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。 #如果把除数0改成2,则执行结果如下: #由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。 try: print('try...') r = 10 / int('a') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) finally: print('finally...') print('END') # int()函数可能会抛出ValueError,所以我们用一个except捕获ValueError,用另一个except捕获ZeroDivisionError。 # 此外,如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句: try: print('try...') r = 10 / int('2') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) else: print('no error!') finally: print('finally...') print('END') # Python所有的错误都是从BaseException类派生的,常见的错误类型和继承关系看这里: #使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理: def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: print('Error:', e) finally: print('finally...') #也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally的麻烦 #调用栈 #如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来看看err.py: def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main() #结果为: # $ python3 err.py # Traceback (most recent call last): #这是错误的跟踪信息 # File "err.py", line 11, in <module> # main() #调用main()出错了,在代码文件err.py的第11行代码,但原因是第9行: # File "err.py", line 9, in main # bar('0') #调用bar('0')出错了,在代码文件err.py的第9行代码,但原因是第6行: # File "err.py", line 6, in bar # return foo(s) * 2 #return foo(s) * 2这个语句出错了,但这还不是最终原因,继续往下看: # File "err.py", line 3, in foo # return 10 / int(s) #原因是return 10 / int(s)这个语句出错了,这是错误产生的源头,因为下面打印了: # ZeroDivisionError: division by zero #根据错误类型ZeroDivisionError,我们判断,int(s)本身并没有出错,但是int(s)返回0,在计算10 / 0时出错,至此,找到错误源头。 #记录错误 #如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时, # 让程序继续执行下去。 # Python内置的logging模块可以非常容易地记录错误信息: import logging # def foo(s): # return 10 / int(s) # def bar(s): # return foo(s) * 2 # # def main(): # try: # bar('0') # except Exception as e: # logging.exception(e) # # main() # print('END') # 同样是出错,但程序打印完错误信息后会继续执行,并正常退出: # 通过配置,logging还可以把错误记录到日志文件里,方便事后排查 # 抛出错误 # 因为错误是class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。 # 如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例: class FooError(ValueError): pass def foo(s): n = int(s) if n==0: raise FooError('invalid value: %s' % s) return 10 / n foo('0') # 只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。 # 最后,我们来看另一种错误处理的方式: def foo(s): n=int(s) if n==0: raise ValueError('invalid value:%s'%s) return 10/n def bar(): try: foo('0') except ValueError as e: print("ValueError") raise bar()
转载请注明原文地址: https://www.6miu.com/read-2150145.html

最新回复(0)