Phthon3 学习记录-0611
异常处理
try 尝试执行代码块
except 捕获异常
finally 无论是否发生异常都会执行的代码块
raise 抛出异常
以下是一个综合展示 try、except、finally 和 raise 关键字用法的 Python 示例:
try:num1 = int(input("请输入第一个数字: "))num2 = int(input("请输入第二个数字: "))if num2 == 0:# raise 抛出异常,如果除数为0raise ZeroDivisionError("除数不能为零")result = num1 / num2print(f"结果是: {result}")
except ValueError:# 捕获输入非数字引发的 ValueError 异常print("输入的不是有效的数字")
except ZeroDivisionError as e:# 捕获除零异常print(f"捕获到异常: {e}")
finally:# 无论是否发生异常,都会执行这里的代码print("这是 finally 块,总会执行")
代码解释
try
块:- 尝试执行
try
块内的代码,这里尝试获取用户输入并将其转换为整数,然后进行除法运算。如果在这个过程中发生异常,程序会跳转到相应的except
块。
- 尝试执行
raise
语句:- 在
try
块中,如果num2
为零,通过raise ZeroDivisionError("除数不能为零")
手动抛出一个ZeroDivisionError
异常,并附带自定义的错误信息。
- 在
except
块:except ValueError:
捕获用户输入非数字时引发的ValueError
异常,并打印错误提示。except ZeroDivisionError as e:
捕获ZeroDivisionError
异常,并将异常对象赋值给e
,然后打印异常信息。
finally
块:- 无论
try
块中是否发生异常,finally
块中的代码都会执行。这里打印一条信息表明finally
块的执行。
- 无论
except
与 raise
的核心区别
1. 角色定位
-
except
:异常处理的接收端
类似“消防员”,当程序运行中意外着火(发生异常)时,负责灭火(处理异常)。try:print(10 / 0) # 引发异常的火源 except ZeroDivisionError:print("错误:除数不能为零") # 灭火动作
-
raise
:异常生成的触发端
类似“火警按钮”,主动触发异常事件。def check_age(age):if age < 0:raise ValueError("年龄不能为负数") # 主动拉响警报
2. 执行阶段
关键字 | 作用阶段 | 执行条件 |
---|---|---|
except | 异常发生后(被动响应) | 当try 块出现匹配的异常时 |
raise | 异常生成前(主动触发) | 满足特定条件时手动触发 |
3. 典型应用场景
-
except
的用途- 防止程序崩溃
- 记录错误日志
- 提供友好错误提示
try:file = open("data.txt") except FileNotFoundError:print("文件未找到,已创建新文件")file = open("data.txt", "w")
-
raise
的用途- 参数合法性校验
- 业务流程阻断
- 自定义异常类型传递
class InsufficientFundsError(Exception):passdef withdraw(balance, amount):if amount > balance:raise InsufficientFundsError("余额不足")return balance - amount
4. 协同工作示例
def calculate_bmi(weight, height):if height <= 0:raise ValueError("身高必须大于零") # 主动抛出异常return weight / (height ** 2)try:print(calculate_bmi(70, 0)) # 触发异常
except ValueError as e:print(f"输入错误: {e}") # 捕获并处理异常
finally:print("计算流程结束") # 最终清理
5. 关键差异总结表
特征 | except | raise |
---|---|---|
执行方向 | 异常传播的终点 | 异常传播的起点 |
代码位置 | 必须跟在try 块后 | 可出现在任何代码位置 |
主动性 | 被动响应已发生的异常 | 主动创建新的异常 |
多级处理 | 可捕获上级未处理的异常 | 抛出的异常可被上层except 捕获 |
6. 进阶理解
-
异常传播链
raise
抛出的异常会沿着调用栈向上传递,直到被某个except
捕获或导致程序崩溃:def layer1():raise TypeError("类型错误")def layer2():try:layer1()except ValueError:print("不会捕获到这里的异常")try:layer2() except TypeError:print("捕获到传播上来的异常") # 最终在此处被捕获
-
异常上下文保留
使用raise from
保留原始异常信息:try:10 / 0 except ZeroDivisionError as original_error:raise ValueError("计算失败") from original_error
掌握这两个关键字的区别,能帮助开发者更好地构建健壮的异常处理体系:通过raise
精确控制错误触发点,通过except
实现分层次的错误处理策略。
作用域
global关键字: 在函数内部,如果想要修改全局变量,就需要使用global关键字声明该变量。
示例代码如下
message = '初始全局消息'def change_global():global messagemessage = '修改后的全局消息'print('函数内部:', message)change_global()
print('函数外部:', message)
- 代码解释:
- 首先定义了一个全局变量
message
,初始值为'初始全局消息'
。 - 在
change_global
函数中,使用global message
声明要修改的message
是全局变量。然后对message
进行赋值操作,修改了全局变量的值。 - 函数内部打印修改后的
message
值,函数外部再次打印message
值,此时可以看到全局变量的值已经被修改
- 首先定义了一个全局变量
nonlocal
关键字:- 当在嵌套函数中,内部函数想要修改外部(非全局)函数的局部变量时,需要使用
nonlocal
关键字。 - 示例代码如下:
- 当在嵌套函数中,内部函数想要修改外部(非全局)函数的局部变量时,需要使用
def outer():num = 10def inner():nonlocal numnum = 20print('内部函数中修改后的值:', num)inner()print('外部函数中修改后的值:', num)outer()
- 代码解释:
- 在
outer
函数中定义了局部变量num
,初始值为10
。 - 在内部函数
inner
中,使用nonlocal num
声明要修改的num
是外部函数outer
的局部变量,而不是在inner
函数中创建一个新的局部变量。然后对num
进行赋值操作,修改了外部函数outer
中num
的值。 - 内部函数打印修改后的
num
值,外部函数也打印修改后的num
值,表明num
的值在外部函数中也被成功修改。如果不使用nonlocal
关键字,在inner
函数中对num
赋值将会创建一个新的局部变量,而不会影响外部函数的num
变量。
- 在
异步编程
async
关键字:async
关键字用于定义一个异步函数。异步函数返回一个coroutine
对象,该对象在适当的时候可以被await
或传递给事件循环来执行。- 示例代码:
import asyncioasync def async_function():print('开始异步函数')await asyncio.sleep(2)print('异步函数结束')async def main():print('开始主函数')await async_function()print('主函数结束')if __name__ == '__main__':asyncio.run(main())
- 代码解释:
async def async_function():
定义了一个异步函数async_function
。在这个函数内部,首先打印'开始异步函数'
,然后使用await asyncio.sleep(2)
暂停函数执行 2 秒,模拟一个异步操作(这里是等待 2 秒),最后打印'异步函数结束'
。async def main():
定义了另一个异步函数main
。在main
函数中,首先打印'开始主函数'
,然后使用await
调用async_function
,等待async_function
执行完毕,最后打印'主函数结束'
。asyncio.run(main())
运行main
函数,asyncio.run
是 Python 3.7+ 用于运行异步函数的入口点,它会创建一个新的事件循环,并在事件循环中运行传入的协程,直到协程完成。
await
关键字:await
只能在async
定义的异步函数内部使用。它用于暂停当前异步函数的执行,直到被等待的coroutine
(通常是一个异步操作)完成,并返回其结果。- 继续上面的例子,
await asyncio.sleep(2)
和await async_function()
都是await
的使用场景。 - 例如,假设我们有一个模拟异步获取数据的函数:
import asyncioasync def fetch_data():await asyncio.sleep(1)return '数据已获取'async def process_data():print('开始处理数据')result = await fetch_data()print(f'处理的数据: {result}')if __name__ == '__main__':asyncio.run(process_data())
- 代码解释:
async def fetch_data():
定义了一个异步函数fetch_data
,它使用await asyncio.sleep(1)
模拟异步获取数据的延迟,1 秒后返回'数据已获取'
。async def process_data():
定义了另一个异步函数process_data
。在这个函数中,首先打印'开始处理数据'
,然后使用await
等待fetch_data
函数执行完毕,并将返回的结果赋值给result
,最后打印处理的数据。asyncio.run(process_data())
运行process_data
函数,开始整个异步操作流程。
if __name__ == '__main__':
基础概念:
if __name__ == '__main__': 这行代码的作用是判断当前脚本是否是直接运行的。当 Python 脚本直接运行时,__name__ 变量会被设置为 '__main__'。如果该脚本是被其他脚本作为模块导入的,__name__ 会被设置为模块的名字。
让我们通过两个不同的使用方式来进一步理解 if __name__ == '__main__':
。
直接运行脚本
创建一个名为 example.py
的文件,内容如下:
def add_numbers(a, b):return a + bif __name__ == '__main__':result = add_numbers(3, 5)print(f"两数之和为: {result}")
作为模块导入
现在,假设我们有另一个文件 main_program.py
,它导入 example.py
:
import example# 这里不会执行 example.py 中 if __name__ == '__main__': 块内的代码
# 因为此时 example 模块中的 __name__ 是 'example',而不是 '__main__'result_from_import = example.add_numbers(2, 4)
print(f"从导入模块得到的两数之和为: {result_from_import}")
在 main_program.py
中,我们导入了 example
模块,并调用了 add_numbers
函数。但 example.py
中 if __name__ == '__main__':
块内的代码并没有执行,因为当 example.py
作为模块被导入时,它的 __name__
是模块名 'example'
,而不是 '__main__'
。
这种机制非常有用,它允许你在同一个 Python 文件中编写可复用的函数或类,同时也可以包含仅在该文件作为主程序运行时执行的测试代码或特定逻辑。这样,当其他开发者将你的代码作为模块导入到他们的项目中时,不会意外执行这些测试或特定逻辑。
if __name__ == '__main__':
是 Python 中一种固定且常用的语法结构 。它基于 Python 对模块和脚本执行的机制设计。
在 Python 中,每个模块都有一个内置变量 __name__
。当一个 Python 文件作为脚本直接运行时,Python 解释器会将该文件的 __name__
设置为 '__main__'
。当该文件作为模块被其他文件导入时,__name__
则被设置为模块的名称(通常就是文件名去掉 .py
后缀)。
这种固定语法的作用主要有两点:
- 测试代码隔离:在开发模块时,可以在模块内编写一些测试该模块功能的代码,并放在
if __name__ == '__main__':
块中。这样当模块被导入到其他项目中时,测试代码不会被执行,而在单独运行模块文件时,测试代码会被执行,方便模块功能的调试和验证。例如:
def multiply(a, b):return a * bif __name__ == '__main__':result = multiply(3, 4)print(f"测试乘法结果: {result}")
- 程序入口标识:对于可执行的 Python 脚本(如命令行工具等),
if __name__ == '__main__':
块可以作为程序的入口点。在这个块中可以处理命令行参数、初始化环境等操作。例如: -
import sysdef main():if len(sys.argv)!= 3:print("用法: python script.py <数字1> <数字2>")returnnum1 = int(sys.argv[1])num2 = int(sys.argv[2])result = num1 + num2print(f"两数之和: {result}")if __name__ == '__main__':main()
在这个例子中,if __name__ == '__main__': 块调用 main 函数,该函数负责处理命令行参数并执行主要的程序逻辑。
- 列表(List)
- 列表是一种有序的可变数据类型,可以包含不同类型的元素。
- 示例代码:
# 创建列表
my_list = [1, 'apple', 3.14, True]# 访问列表元素
print(my_list[0]) # 输出: 1# 修改列表元素
my_list[1] = 'banana'
print(my_list) # 输出: [1, 'banana', 3.14, True]# 添加元素到列表末尾
my_list.append('cherry')
print(my_list) # 输出: [1, 'banana', 3.14, True, 'cherry']# 插入元素到指定位置
my_list.insert(2, 'grape')
print(my_list) # 输出: [1, 'banana', 'grape', 3.14, True, 'cherry']# 删除元素
my_list.remove('banana')
print(my_list) # 输出: [1, 'grape', 3.14, True, 'cherry']# 获取列表长度
print(len(my_list)) # 输出: 5
- 代码解释:
- 首先创建了一个包含整数、字符串、浮点数和布尔值的列表
my_list
。 - 通过索引访问列表元素,索引从 0 开始。
- 可以直接通过索引修改列表元素的值。
- 使用
append
方法在列表末尾添加元素,insert
方法在指定位置插入元素。 remove
方法用于删除指定元素。len
函数用于获取列表的长度。
- 首先创建了一个包含整数、字符串、浮点数和布尔值的列表