1. 单线程同步流程控制
python与多数其他语言一样默认是单线程同步的顺序执行语句.这也是所有语言的基础.
python中语句会一行一行地被解释执行,而流控制语句则是用于控制当前解释执行哪条语句的工具
1.1. 流程分支
python只有if
语句没有switch
,这也带来了语义上的歧义:
if/else
代表判断逻辑,一般用于根据一条语句的结果正误来确定下一步的执行内容if/elif/else
代表分支,一般用于根据一个变量的取值不同来确定下一步执行的内容.
虽然这种方式看似减少了关键字数量,但实际上增加了错误语义的使用可能.个人认为是python设计的一大败笔.
1.1.1. 使用字典结合匿名函数更加优雅的实现简单流程分支
if/elif
来实现switch
并取优雅,需要为一个变量写多次判断语句,因此如果分支逻辑简单,完全可以使用字典+lambda函数
def which_type(x): return { int:lambda x:print(x,"is int"), float:lambda x:print(x,"is float"), list:lambda x:print(x,"is list") }.get(type(x),lambda x:print("i dont know"))(x)
which_type(2)
2 is int
1.2. 循环语句
循环语句主要目的就是让一段代码反复顺序执行,python有两种循环语句:
for循环
主要用于根据先验循环次数执行循环内代码的情况.他会遍历一个迭代器,先验的次数就是这个迭代器的长度,每取出一个对象都会执行for块中的语句
while循环
主要用于没有先验循环次数,而是根据判断条件执行循环内代码的情况
for i in range(10): print(i)
0 1 2 3 4 5 6 7 8 9
i = 0 while i<10: print(i) i+=1
0 1 2 3 4 5 6 7 8 9
1.2.1. 打断语句break和continue
循环中可以使用break跳出循环或者使用continue跳出当次循环
i = 0 while True: i += 1 if i > 10 : break if i % 2 == 0 : continue print(i)
1 3 5 7 9
1.3. try/except 不仅用于处理错误,还常用于控制流程
在Python中,try/except
不仅用于处理错误,还常用于控制流程.为此,Python 官方词汇表还定义了一个缩略词(口号):
- EAFP
取得原谅比获得许可容易(easier to ask for forgiveness than permission).这是一种常见的Python编程风格,先假定存在有效的键或属性,如果假定不成立,那么捕获异常.这种风格简单明快,特点是代码中有很多try和except语句.与其他很多语言一样(如C 语言),这种风格的对立面是LBYL风格.
- LBYL
三思而后行(look before you leap).这种编程风格在调用函数或查找属性或键之前显式测试前提条件.与EAFP
风格相反,这种风格的特点是代码中有很多if 语句.在多线程环境中,LBYL
风格可能会在"检查"和"行事"的空当引入条件竞争.例如对if key in mapping: return mapping[key]
这段代码来说,如果在测试之后,但在查找之前,另一个线程从映射中删除了那个键,那么这段代码就会失败.这个问题可以使用锁或者EAFP
风格解决.
如果选择使用EAFP
风格,那就要更深入地了解else
子句,并在try/except
语句中合理使用
1.4. 特殊的else语句
else
子句不仅能在if
语句中使用,还能在for
、while
和try
语句中使用.for/else
、while/else
和try/else
的语义关系紧密,不过与if/else
差别很大.
此处的else语句理解为then
可能更加合适,它代表的是"没有遇到特殊情况就执行以下代码"这样的语义.具体到不同的语句,语义如下:
for
仅当
for
循环运行完毕时)即for
循环没有被break
语句终止)才运行else
块while
仅当
while
循环因为条件为假值而退出时(即while
循环没有被break
语句中止)才运行else
块
循环语句中使用else语句可以省去大量的状态变量,最典型的就是为避免网络异常而多次访问某个地址的场景
import requests
which_type(2)
0
which_type(2)
1
which_type(2)
2
which_type(2)
3
which_type(2)
4
which_type(2)
5
- try
仅当try
块中没有异常抛出时才运行else
块。官方文档还指出--else
子句抛出的异常不会由前面的except
子句处理.
在所有情况下,如果异常或者return
,break
或continue
语句导致控制权跳到了复合语句的主块之外,else
子句也会被跳过
1.4.1. EAFP风格的python代码
上面说道EAFP风格,需要注意的是这种风格下try/else
语句应当被大量有针对性地使用,它应当细粒度的防守单条语句而不是像多数人那样防守一段代码.这会让代码看起来很啰嗦,但相对来说更加安全
1.5. 上下文管理器和with块
上下文管理器对象存在的目的是管理with
语句,就像迭代器的存在是为了管理for语句一样.
with
语句的目的是简化try/finally
模式.这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return
语句或sys.exit()
调用而中止,也会执行指定的操作.finally
子句中的代码通常用于释放重要的资源,或者还原临时变更的状态.
上下文管理器协议包含__enter__
和__exit__
两个方法.with
语句开始运行时,会在上下文管理器对象上调用__enter__
方法.with
语句运行结束后,会在上下文管理器对象上调用__exit__
方法,以此扮演finally
子句的角色.
最常见的例子是确保关闭文件对象.这在前文已经有所描述.注意,与函数和模块不同,with
块没有定义新的作用域.
__enter__()
方法__enter__()
方法要求最好返回一个对象(如果不返回一个对象,as语句会捕获一个None),一般是self
,但不一定.除了返回上下文管理器之外,还可能返回其他对象.
__exit__(exc_type, exc_value, traceback)
方法- exc_type 异常类(例如ZeroDivisionError)
- exc_value 异常实例.有时会有参数传给异常构造方法,例如错误消息,这些参数可以使用
exc_value.args
获取 traceback traceback对象
不管控制流程以哪种方式退出with块,都会在上下文管理器对象上调用
__exit__
方法,而不是在__enter__
方法返回的对象上调用.with
语句的as
子句是可选的.对open
函数来说,必须加上as
子句,以便获取文件的引用.不过,有些上下文管理器会返回None
,因为没什么有用的对象能提供给用户.
下面看一个上下文管理器修改上下文环境中print
函数行为的例子:
which_type(2)
6
which_type(2)
7
which_type(2)
8
which_type(2)
9
2 is int
0
2 is int
1
2 is int
2
抛开了with语句,上下文管理器也可以这样使用
2 is int
3
2 is int
4
使用try/finally
可以这样写
2 is int
5
2 is int
4
1.5.1. contextlib模块
contextlib
模块中提供了一些类和其他函数,用于快速的构建上下文管理器
- closing
如果对象提供了close()
方法,但没有实现__enter__/__exit__
协议,那么可以使用这个函数构建上下文管理器
- suppress
构建临时忽略指定异常的上下文管理器
- @contextmanager
这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了
- ContextDecorator
这是个基类,用于定义基于类的上下文管理器.这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数.
- ExitStack
这个上下文管理器能进入多个上下文管理器.with
块结束时,ExitStack
按照后进先出的顺序调用栈中各个上下文管理器的__exit__
方法.如果事先不知道with
块要进入多少个上下文管理器,可以使用这个类.例如同时打开任意一个文件列表中的所有文件.
1.5.2. 使用@contextmanager
@contextmanager
装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类,定义__enter__
和__exit__
方法,而只需实现有一个yield
语句的生成器,生成想让__enter__
方法返回的值. 在使用@contextmanager
装饰的生成器中,yield
语句的作用是把函数的定义体分成三部分:
- yield 语句前面的所有代码在with块开始时(即解释器调用
__enter__
方法时)执行 - yield 语句,用于抛出
__enter__
要返回的对象,并可以接收异常 - yield 语句后面的代码在with块结束时(即调用
__exit__
方法时)执行
2 is int
7
2 is int
8
2 is int
9
其实,contextlib.contextmanager
装饰器会把函数包装成实现__enter__
和__exit__
方法的类.
这个类的__enter__
方法有如下作用:
- 调用生成器函数,保存生成器对象(这里把它称为gen)
- 调用
next(gen)
,执行到yield
关键字所在的位置. - 返回
next(gen)
产出的值,以便把产出的值绑定到with/as
语句中的目标变量上
with
块终止时,__exit__
方法会做以下几件事
- 检查有没有把异常传给
exc_type
;如果有,调用gen.throw(exception)
,在生成器函数定义体中包含yield
关键字的那一行抛出异常. - 否则调用
next(gen)
,继续执行生成器函数定义体中yield语句之后的代码
如果在with
块中抛出了异常,Python解释器会将其捕获,然后在looking_glass
函数的yield
表达式里再次抛出.但是那里没有处理错误的代码,因此looking_glass
函数会中止,永远无法恢复成原来的sys.stdout.write
方法,导致系统处于无效状态.因此上面的例子并不完整,下面给出完整的例子
for i in range(10): print(i)
0
for i in range(10): print(i)
1
for i in range(10): print(i)
2
还没有评论,来说两句吧...