#列表生成式
# 是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()