Python 流程控制:跳转与中断、if、while、for、try ...(一篇解千疑)

流程控制

简要介绍

在一个朴实无华的程序中,执行总是顺序的,但要实现复杂的逻辑,就必须引入中断和跳转进行流程控制

中断包括故障中断和结束中断,也可以称作是一种跳转。

跳转控制是一个相当成熟的技术,其结构完善且模式多样,几乎在任何一个程序中都不可或缺。包含但不限于:

  • 条件分支结构
  • 循环结构
  • 异常捕获结构
  • 函数调用
  • 中断、流的输入与输出

条件和循环是容易理解的,在汇编的学习中就已经能看到这两种结构的影子了。

异常捕获结构也是一种跳转。典型的,在做爬虫请求时,往往需要不断更换 ip 请求,一个 ip 请求失败带来异常,捕获后再根据需求从 ip池 中拿新的 ip 进行请求。

函数调用也很显然,调用就是要跳转到函数实现部分,其中输入参数和返回结果还涉及堆栈的使用,属于数据存储区的跳转,也并不复杂。

中断常常表现为故障结束程序,也是一种跳转。

流的输入与输出不能在严格意义上称为跳转控制,因为像 an_integral = int(input("输入一个整数:"))只不过是程序停顿下来,等待键入一个值在缓存区,然后获取到赋值给变量。再比如,将一个字符串写入文件中等等实例,都只是在数据的不同区块进行跳转。但是计算机中一切都是数据,代码是、数据是、逻辑也是,所以归纳于此。

总之,流程控制的概念不复杂,下面介绍一些常用结构的语法和案例。


条件结构

if 语句

if 条件语句是流程控制中最常用的语句,用于有条件的执行。

语法

if '条件1' : 		
	'子句体'
(elif '条件n' :	# ()* 表示可以0或多个 elif 子句
	'子句体')*
[else :			# [] 表示可以0或1个 else 子句
	'子句体']
  1. if 根据条件的真假值来判定是否执行:在所有子句体中选择唯一匹配的一个,然后执行该子句体而略过其它。 如果条件均为假值,则 else 子句体如果存在就会被执行,否则直接跳过。
  2. 关键字 elifelse 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或多个
  1. 其中的目标表达式匹配模式都有复杂的语法与规范,更多信息另请参阅 match 语句
  2. [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()

作为一个新语句,这是足够有意思的;另外需要注意的是,matchcase 是弱关键字。

循环结构

循环结构的用武之地往往是遍历与重试。

while 语句

在条件表达式保持为真的情况下重复地执行。

语法

while '条件':
	'循环体'
[else'结束语']  # [] 表示可存在0或1个
  1. 都知道 while 语句是先判断再执行的:最后的 else 子句作为最后的可选项,循环判断为假时执行,往往是 while 循环结构最后的一次操作。
  2. 要注意的是,被 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个
  1. for 循环这里应该开始有迭代可迭代对象迭代器的概念。
  2. 在迭代前,会先根据 '序列' 产生一个可迭代对象,针对该可迭代对象创建一个迭代器。 条目 不断被迭代器赋值迭代,进入循环体,直到结束。
  3. 几乎和 while 循环一样功能的 else 子句,注意相同的问题(即被 break 结束的循环会执行不了该语句)。
  4. 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 '函数名'(['参数列表']) [-> '返回数据类型']:
	'函数体'
  1. 修饰器有很多种,例如日志修饰器。
  2. 函数名命名规则:见标识符(变量名)命名规则一节。
  3. 函数可以理解为一定有返回值,只不过空函数返回空值。

匿名函数(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)
<think>嗯,用户的问题是关于如何使用PIL正确遍历多帧图像的每一帧而不报错。首先,我需要回忆一下PIL库中处理多帧图像的相关模块和方法。记得PIL的Image模块中有个关于多帧图像的处理,比如GIF或者TIFF格式的文件。可能需要使用Image.seek()和Image.tell()方法来遍历每一帧。 用户提到“正确遍历而不报错”,所以可能他们在尝试直接循环读取时遇到了EOFError或者其他异常。这时候,应该考虑在读取帧的时候捕获异常,比如当seek超出帧数时,PIL会抛出EOFError,这时候可以退出循环。此外,可能需要使用try-except块来处理这种情况。 另外,参考用户提供的引用[2]中提到了Image.open方法返回一个Image对象,并检查了format、size、mode属性。但多帧图像可能需要不同的处理方式。例如,GIF文件每一帧可能需要依次跳转。 可能还需要注意,某些图像格式虽然支持多帧,但PIL的某些版本可能存在兼容性问题,比如用户引用[1]提到的安装特定版本的库。如果用户遇到问题,可能需要检查PIL的版本,或者使用Pillow库(PIL的分支)的最新版本。 用户的问题还可能涉及到如何判断图像是否含有多帧。可以使用Image.is_animated属性来判断,如果为True,则表示该图像是多帧的。然后循环读取每一帧,直到捕获到EOFError为止。 总结步骤应该是:打开图像,检查是否为多帧,如果是,则使用seek和tell方法遍历,同时处理异常。此外,保存每一帧时可能需要不同的处理方式,比如转换为其他格式或者单独保存。 可能还需要注意资源管理,使用with语句来确保文件正确关闭。另外,在保存每一帧时,可能需要指定保存参数,比如对于GIF的保存优化等。 最后,生成相关问题时要考虑用户可能进一步的问题,比如处理特定格式、性能优化、错误处理的其他情况,或者其他库的结合使用。</think>### PIL多帧图像遍历方法 处理多帧图像(如GIF/TIFF)时需使用`Image.seek()`和`Image.tell()`方法配合异常捕获,具体步骤如下: 1. **载入图像并验证多帧属性** ```python from PIL import Image with Image.open("multi_frame.gif") as img: if hasattr(img, "is_animated") and img.is_animated: # 判断多帧特性[^2] print(f"总帧数: {img.n_frames}") ``` 2. **安全遍历帧的代码结构** ```python try: while True: img.seek(img.tell() + 1) # 跳转下一帧 current_frame = img.copy() # 处理帧的代码... except EOFError: # 捕获帧尾异常 pass ``` 3. **完整示例代码** ```python from PIL import Image def process_multiframe(path): try: with Image.open(path) as img: if getattr(img, "is_animated", False): for frame in range(img.n_frames): img.seek(frame) print(f"处理第{frame+1}帧,尺寸: {img.size}") # 添加处理逻辑 except Exception as e: print(f"处理错误: {str(e)}") ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值