Python 学习笔记——3. 流程控制与异常
一些关于流程控制的语法,除了继续吐槽一下 Python 的缩进规则之外,也没什么好说的,各种语言都大同小异,直接给例子即可。
条件语句
if 条件
直接给例子,只需要注意一下用elif而非else if
1 | if a==1: |
最简单的单行 if 也是可以的
1 | if True: print("hello") |
还可以使用如下的单行 if 表达式,类似于 C 语言的三目运算符,它算作表达式,因此也可以用在 lambda 表达式中
1 | result = 1 if True else -1 # 1 |
注意:在 if 中的条件可以包括与或非等更复杂的逻辑判断,但是只能使用 and or not 来代表,不支持 && || !;(C++对这两套都支持,但是主要使用后者)
这里还需要注意一下语法的优先级,例如下面两个语句是不一样的
1 | a, b if a>b else None |
match 匹配
Python 早期不支持 switch 语句,只能用 if 语句,较新的版本加上了与之类似的match语句。
match语句支持比 C++ 的 switch 更复杂的逻辑,后者只能基于一个整数判断,前者甚至可以进行正则匹配,这里不做讨论。
例如
1 | def http_error(status): |
注意 Python 特别规定在 match 语句中的 _ 可以匹配任意值,在其它语句中,_ 通常表示忽略的变量。
循环语句
Python的循环语句效率非常低,对运行效率有要求时应尽量避免使用,可以使用numpy提供的向量化操作、列表推导式等进行替代。
while 循环
直接给例子即可
1 | a=1 |
最简单的单行 while 也是可以的
1 | a=2 |
for 遍历
Python 的 for 语句只能基于一个可遍历的对象(列表,元组,字典等),因此称为 for 遍历比 for 循环更合适。
对于列表、元组和集合,for 循环可以获取其中的每一项
1 | words = ['a1','b1','c1'] |
对于字典,for 循环获取的是每一个键值对中的键,而不是对应的值!
1 | dict = {'a':1,'b':2} |
注意:
- 和 C++的迭代器一样,在过程中尝试修改遍历对象的行为是不可控的!如果需要修改,考虑引入副本进行处理
1 | users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'} |
- for 遍历语句中的临时变量并不会在退出遍历时被自动删除,而是仍然保留着遍历过程中的最后一项的值,在后文中仍然可用,for 语句并不能开辟一个独立的作用域,但是如果遍历一次都没有执行,那么临时变量不会被赋值
for range 遍历
对于 C/C++的 for 语法功能,在 Python 中需要通过 range() 函数来辅助实现,或者用 while 实现。
range() 只能使用整数参数,含义如下:
- 三个参数 a,b,c 代表从 a 开始,每次增加的步长为 c(c可以是负数),到 b 停止(不含 b)
- 若步长
c>0,相当于for(i=a,i<b,i=i+c) - 若步长
c<0,相当于for(i=a,i>b,i=i+c)
- 若步长
- 两个参数 a,b 代表从 a 开始,每次增加的步长默认为 c=1,到 b 停止(不含 b)
- 一个参数 b 代表默认从 a=0 开始,每次增加的步长默认为 c=1,到 b 停止(不含 b)
例如
1 | range(5) |
使用 for range() 可以实现 C 风格的 for 循环,例如
1 | for i in range(3,10,2): |
break 和 continue 语句
break 和 continue 就是编程语言中普遍采用的含义:
break语句可以直接跳出一层 for 或者 while 循环,对于多层循环只能跳出最内层的一层。continue语句可以直接进入下一轮循环中,跳过当前循环的剩余部分。
else 语句
除了 if 语句,在 while 循环和 for 遍历结构中,我们也可以附带 else 分支语句,
在循环正常结束(而非通过break语句跳出循环)时触发,例如
- while 循环遇到判断条件为 False 时正常退出
- for 遍历正常结束(默认会捕获遍历终止的异常)
其实除了 if 语句,其它情况下的 else 理解为 then 都更加合适。
例如
1 | digits = [0, 1, 2, 3, 4] |
补充
有几个函数经常在循环语句中使用,这里补充介绍。
zip 函数
zip 函数可以将两个可迭代对象(列表/元组等)的对应项一一对应包装为元组,然后返回一个可迭代的 zip 对象,相当于一个字典的效果。如果两个列表长度不一样,以短的为准,多余的部分被丢弃。
注意这里 for 循环遍历的每一项是含有两个元素的元组,因此可以用两个变量直接解包
1 | a = [1,2,3] |
enumerate 函数
enumerate 函数和 zip 类似,只不过只需要一个列表,会自动给这个列表添加从 0 开始序号,得到一个元组(序号在前,列表元素在后),在 for 遍历中非常方便,因为不仅仅需要迭代的每一项,还需要当前的遍历序号。
1 | animals = ["dog", "cat", "mouse"] |
tqdm 进度条
在执行非常耗时的 for 遍历或 while 循环任务中,使用一个进度条可以显著提高用户体验,tqdm 是 Python 最推荐的第三方进度条库,使用非常方便。
tqdm 可以直接包装在 for 遍历语句中,例如
1 | from tqdm import tqdm |
可以使用可选参数来调整进度条的细节,包括描述文字,进度条颜色等。
1 | from tqdm import tqdm |
为了避免结束时的输出格式异常,可以调用 tqdm.close() 方法进行清理,更建议使用 with 语句
1 | from tqdm import tqdm |
除了封装可遍历的对象,也可以手动控制进度条的显示和更新,使用update()方法更新,需要指定总量,每次传入增量,例如
1 | with tqdm(total=100, desc="Manual Update") as pbar: |
在 while 循环中,必须使用手动方式来更新进度条,下面是一个时间推进的 while 循环示例
1 | from tqdm import tqdm |
除了最重要的进度以及自动生成的耗时和预期时间数据,我们有时还需要显式更多数据,可以使用 set_postfix 添加自定义后缀文本(以一个字典形式),例如
1 | from tqdm import tqdm |
添加更多数据例如
1 | pbar.set_postfix( |
从 tqdm 改成 tqdm.auto 模块,可以让进度条在不同的运行环境(notebook,终端)自动调整为更合适的样式,其它用法不变
1 | from tqdm.auto import tqdm |
海象运算符
在if和while语句中,我们需要对一个变量或表达式进行判断,在Python中提供了一个海象运算符可以将逻辑判断和变量赋值结合到一起,从而简化代码。
在if语句中的用法例如
1 | if (x := len('hello')) > 3: |
相当于
1 | x = len('hello') |
while语句的用法也是类似的,例如
1 | file = open("demo.txt", "r") |
相当于
1 | file = open("demo.txt", "r") |
注意这里定义的变量在if语句的else分支中仍然可以使用,甚至在if和while结构之后仍然可以使用。
在C++中,如果在if语句中定义一个变量,在else分支中仍然可以使用,但是跳出if语句就无法使用了。
异常处理
异常处理的流程非常类似于 C++,与C++不同的是在 Python 中并没有严格的函数调用栈之类的,一切都是 Python 解释器提供的动态对象。
捕获异常
捕获异常的语法结构如下:
1 | try: |
例如,下文遍历一个列表直到找到一个有效数据可用于计算倒数
1 | import sys |
抛出异常
除以 0 的操作可以触发一个 ZeroDivisionError 异常,同样地,我们可以自行在语句中使用raise语句抛出异常,例如
1 | try: |
上文中如果输入不满足要求,会抛出 Python 提供的 ValueError 异常,然后立刻捕获异常。
Python 还提供一些带有更明显语义的异常,例如
1 | class Animal: |
和 C++一样,Python 支持自定义异常,但是要求继承标准异常类 Exception,略。
断言 assert
类似于C语言的 assert,但实质是一种抛异常的语法糖:如果断言为真则无事发生,否则抛出异常。
第一种形式
1 | assert expression |
大致相当于
1 | if __debug__: |
第二种形式会附加信息
1 | assert expression1, expression2 |
大致相当于
1 | if __debug__: |
例如
1 | assert x>1 |
还可以使用 f-string 来输出更详细的信息,例如
1 | n = 4 |
这里 assert 语句中的 f-string 在通过 python 脚本执行的情况下会被替换,但是在交互式环境例如jupyter可能不会被替换。
在执行时如果加上 -O 参数,那么 assert 语句将不会被执行。
1 | python -O test.py |
