Python 流程控制
流程控制
简要介绍
在一个朴实无华的程序中,执行总是顺序的,但要实现复杂的逻辑,就必须引入中断和跳转进行流程控制。
中断包括故障中断和结束中断,也可以称作是一种跳转。
跳转控制是一个相当成熟的技术,其结构完善且模式多样,几乎在任何一个程序中都不可或缺。包含但不限于:
- 条件分支结构
- 循环结构
- 异常捕获结构
- 函数调用
- 中断、流的输入与输出
条件和循环是容易理解的,在汇编的学习中就已经能看到这两种结构的影子了。
异常捕获结构也是一种跳转。典型的,在做爬虫请求时,往往需要不断更换 ip
请求,一个 ip 请求失败带来异常,捕获后再根据需求从 ip池
中拿新的 ip 进行请求。
函数调用也很显然,调用就是要跳转到函数实现部分,其中输入参数和返回结果还涉及堆栈的使用,属于数据存储区的跳转,也并不复杂。
中断常常表现为故障结束程序,也是一种跳转。
流的输入与输出不能在严格意义上称为跳转控制,因为像 an_integral = int(input("输入一个整数:"))
只不过是程序停顿下来,等待键入一个值在缓存区,然后获取到赋值给变量。再比如,将一个字符串写入文件中等等实例,都只是在数据的不同区块进行跳转。但是计算机中一切都是数据,代码是、数据是、逻辑也是,所以归纳于此。
总之,流程控制的概念不复杂,下面介绍一些常用结构的语法和案例。
条件结构
if 语句
if
条件语句是流程控制中最常用的语句,用于有条件的执行。
语法:
if '条件1' :
'子句体'
(elif '条件n' : # ()* 表示可以0或多个 elif 子句
'子句体')*
[else : # [] 表示可以0或1个 else 子句
'子句体']
if
根据条件的真假值来判定是否执行:在所有子句体中选择唯一匹配的一个,然后执行该子句体而略过其它。 如果条件均为假值,则else
子句体如果存在就会被执行,否则直接跳过。- 关键字
elif
是else if
的缩写,适用于避免过多的缩进。多个条件判断可以当作其他语言中switch
语句的替代品(python 中没有该语句)。
案例:根据分数获得等级。
score = int(input("输入你的分数:"))
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")
elif score >= 60:
print("D")
else:
print("E")
注意:如果要把一个值与多个常量进行比较,或者检查特定类型、属性,match 语句更实用。
match 语句
match
语句是 3.10 版本开始出现的,其要点是对表达式进行模式匹配。根据其内容的完整性,完全可以作为一个匹配结构,但是匹配也是按条件匹配,所以本文将之归纳于此。
语法:一个简明的语法介绍如下,
match '目标表达式':
(case '匹配模式' [if '条件']:
'语句块'
[case _: # 必定匹配的模块
'语句块'])+ # [] 表示可存在0或1个, ()+ 表示可存在1或多个
- 其中的目标表达式和匹配模式都有复杂的语法与规范,更多信息另请参阅 match 语句。
[if '条件']
是一个加强约束的项。
案例:一个简单的匹配个人姓名和年龄的案例。
class Person:
def __init__(self, name='', age=0):
self.name = name
self.age = age
def main():
persons = [Person('', 0), Person('Ace', 0), Person('Alice', 18)]
for person in persons:
match person:
case Person(name='', ):
print("无名之辈")
case Person(name=x, age=0):
print(f"'{x}'的年龄怎么是'0'呢?")
case Person(name=x, age=y) if y >= 18:
print(f"'{y}'岁的'{x}',你已经成年了。")
case _:
print("必定的匹配")
if __name__ == '__main__':
main()
作为一个新语句,这是足够有意思的;另外需要注意的是,match
和 case
是弱关键字。
循环结构
循环结构的用武之地往往是遍历与重试。
while 语句
在条件表达式保持为真的情况下重复地执行。
语法:
while '条件':
'循环体'
[else:
'结束语'] # [] 表示可存在0或1个
- 都知道
while
语句是先判断再执行的:最后的else
子句作为最后的可选项,循环判断为假时执行,往往是while
循环结构最后的一次操作。 - 要注意的是,被
break
结束的循环是不会执行该子句的,这是因为else
是循环的一部分,break
跳出了循环。所以一般适用于不被break
结束的有限循环。
** 案例**:角谷定理,输入一个整数,为奇则 ×3+1
为偶则 ÷2
,最终会得到 1
。
num = int(input("输入一个整数:"))
n, i = num, 0
while True:
if n == 1:
break
if n % 2:
n = n * 3 + 1
else:
n = n / 2
i += 1
print(f"{num}经过'{i}'次角谷迭代后变为一") # 13, 9
for 语句
for
语句用于对序列(例如字符串、元组或列表)或其他可迭代对象中的元素进行迭代。与 双目运算符 in
联合使用。
语法:
for '条目' in '序列':
'循环体'
[else:
'结束语'] # [] 表示可存在0或1个
for
循环这里应该开始有迭代、可迭代对象、迭代器的概念。- 在迭代前,会先根据
'序列'
产生一个可迭代对象,针对该可迭代对象创建一个迭代器。条目
不断被迭代器赋值迭代,进入循环体,直到结束。 - 几乎和
while
循环一样功能的else
子句,注意相同的问题(即被break
结束的循环会执行不了该语句)。 for
循环也常被用作 列表推导式、元组推导式、生成器 等的构造。
案例1:获取 0~100
中的奇数。
arr = [i for i in range(100) if i % 2]
** 案例2**:筛选有效信息。
info = {'name': 'Ace', 'age': 0, 'weight': 121.5, 'height': None, 'school': ''}
useful_info = {}
for key, value in info.items():
if value:
useful_info[key] = value
print(useful_value)
跳脱与略过
跳脱是控制循环的一大办法。一个死循环的程序可以通过监视循环次数,次数过多则选择跳脱整个循环(break);循环过程中一些无关紧要的项目可以跳过本轮(continue)。
略过这一说法可能过于生动,但不失其为一种准确的逻辑表达。
continue 语句
跳过一些不予处理的项。
案例:把列表中的偶数×10。
lst = [n for n in range(1, 101)]
for i in range(len(lst)): # 把列表中的偶数×10
if lst[i] % 2:
continue
lst[i] *= 10
print(lst)
break 语句
跳出最近的一层循环(存在循环嵌套时注意)。
案例:九九乘法表查找。
x, y = input("输入式子如 '2*3' 然后回车:").split('*')
x, y, flag = int(x), int(y), False
for i in range(1, 10):
for j in range(1, 10):
if i == x and j == y:
print("f{x}x{y}={i*j}")
flag = True
break
if flag:
break
pass 语句
pass
语句不执行任何操作。语法上需要一个语句,但程序不实际执行任何动作时,可以使用该语句。
功能:
- 临时占位,类似于 省略符…。
- 忽略程序,什么都不执行,但维护程序结构。
class A:
pass # 展位
x = 101
if x <= 100:
pass # 不执行语句,只维护结构
else:
print(x, "> 100")
异常捕获结构
任何程序在出现异常时,都有其捕获处理机制。大致来讲,处理分为中断和跳转两种:其一,抛出(raise
)异常中断程序(更多详情见Python:异常处理机制),然后开始调试 bug
;其二,控制指定类型的错误,其出现时跳转到另一个子程序或其它操作。
下面介绍的是,通过尝试try 语句
进行异常控制的简要内容。
try 语句
try
语句的句式结构归结为三种。
# 其一
try:
'尝试区块'
(except ['指定异常' [as '标识符']]: '$1$')+ # 遇到指定异常,执行的区块
[else: '$2$']
[finally: '$3$']
# 其二
try:
'尝试区块'
(except* '指定异常' [as '标识符']: '$1$')+ # 指定异常组
[else: '$2$']
[finally: '$3$']
# 其三
try:
'尝试区块'
finally: '最终执行' # 不可或缺
以一个异常组为案例:
try:
raise ExceptionGroup("eg",
[ValueError(1), TypeError(2), OSError(3), OSError(4)])
except* TypeError as e:
print(f'caught {type(e)} with nested {e.exceptions}')
except* OSError as e:
print(f'caught {type(e)} with nested {e.exceptions}')
print("over")
第一个TypeError
异常组捕获到了 TypeError(2)
;第二个OSError
异常组捕获到了 OSError(3), OSError(4)
,但是抛出的异常还有一个 ValueError
,所以程序会中断,不会输出字符串 over
。
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 2, in <module>
| ExceptionGroup: eg
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
更多请参阅 Python:异常处理机制。
函数结构
函数是一个很庞大的知识,想要要系统熟悉,这里的篇幅是不够的,更多请参阅 Python:函数。
这里简要介绍以下函数、匿名函数的定义,案例也相对简单。
函数的定义
python 内部自带许多函数,如 max()
、min()
等等。一般说到函数定义就是指用户自定义一个函数。自定义函数的语法如下。
语法:
['修饰器']
def '函数名'(['参数列表']) [-> '返回数据类型']:
'函数体'
- 修饰器有很多种,例如日志修饰器。
- 函数名命名规则:见标识符(变量名)命名规则一节。
- 函数可以理解为一定有返回值,只不过空函数返回空值。
匿名函数(lambda 表达式) 的定义
lambda
关键字用于创建小巧的匿名函数。lambda a, b: a+b
函数返回两个参数的和。Lambda 函数可用于任何需要函数对象的地方。
在语法上,匿名函数只能是单个表达式。在语义上,它只是常规函数定义的 语法糖。与嵌套函数定义一样,lambda
函数可以引用包含作用域中的变量。
语法
funcname = lambda *args: short_body
函数案例
关于作用域的一个案例
ambda 表达式可以引用包含作用域中的变量
def pow(n):
return lambda x: n ** x
f = pow(2)
print(f(3)) # 2 ** 3 == 8
print(f(4)) # 2 ** 4 == 16
lambda 表达式返回值作为实参
一个经典的案例就是作为排序的实参!
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1]) # 根据英文排序
print(pairs)