31天Python入门——第14天:异常处理

|----------------|
| 你好,我是安然无虞。 |

文章目录

异常处理

1. Python异常

Python异常指的是在程序执行过程中发生的错误或异常情况. 当代码遇到错误时, 会引发异常并中断程序的正常执行流程.

异常提供了一种机制来处理错误, 以便程序可以在错误发生时采取适当的行动, 而不会导致程序崩溃或产生意外结果.

在Python中, 异常是通过异常类表示的. 每个异常类都是Python内置的或自定义的, 用于表示特定类型的错误. 常见的内置异常类包括:

  • SyntaxError:语法错误, 通常是代码书写不正确.
  • NameError:名称错误, 尝试访问不存在的变量或函数.
  • TypeError:类型错误, 操作或函数应用于不兼容的数据类型.
  • ValueError:值错误, 当函数接收到不合法的值时引发.
  • ZeroDivisionError:零除错误, 尝试将一个数除以零.
  • Exception:所有内置异常类的基类, 即其他所有的异常都是基于它的.

除了这些内置异常类, Python还提供了许多其他的异常类, 用于特定的错误情况. 可以使用try-except语句来捕获和处理异常. 通过在try块中编写可能引发异常的代码, 并在except块中处理异常, 可以保护程序免受错误的影响(在程序出现异常时不会直接中断程序执行), 而是采取适当的措施来处理异常情况.

异常处理可以帮助我们提高程序的健壮性和容错能力, 使得程序能够更好地处理异常情况, 并给出恰当的反馈或采取相应的措施.

2. 异常捕获

try-except语句

在Python中, 我们使用try-except语句来捕获和处理异常. try块用于编写可能引发异常的代码, 而except块用于处理异常情况.

python 复制代码
try:
	# 可能引发异常的代码块
except ExceptionType:
	# 处理异常的代码块
  • try块中的代码会按顺序执行, 如果发生异常, 则会跳转到匹配的except块.
  • except块中的代码会处理特定类型的异常, ExceptionType表示要捕获的异常类型.
  • 可以有多个except块, 每个块可以处理不同类型的异常, 或者使用一个块来处理多个异常类型.
python 复制代码
def divide_numbers(a, b):
  result = a / b
  print("result", result)
  
divide_numbers(10, 2) # 5.0
divide_numbers(10, 0) # 触发除零异常, 程序中断, 后续代码都不会被执行 - 这样的话代码的容错能力很低
divide_numbers(9, 2)
divide_numbers(8, 2)
python 复制代码
def divide_numbers(a, b):
  try:
    result = a / b
    print("result", result)
  except ZeroDivisionError:
    print("0不能作为除数.")
  except TypeError:
    print("参数传递类型错误.")
  except Exception as e:
    print(f"程序出现了意外错误: 具体出错内容: {e}.")
  finally:
    print(f"{a}/{b}的结果计算完毕.")
          
divide_numbers(10, 2) # 5.0
divide_numbers(10, 0) # 显示'0不能作为除数'
divide_numbers(9, 2) # 4.5
divide_numbers(8, 2) # 4.0
捕获所有的异常信息
python 复制代码
 # ---------方式一----------
 try:
  # 可能引发异常的代码块
 except:
  # 处理所有异常
 
 def divide_numbers(a, b):
 try:
  result = a / b
 except:
  print("程序出错了.")
 
 # ---------方式二----------
 # 根据基类Exception来捕获
 
 def divide_numbers(a, b):
 try:
  result = a / b
 except Exception:
  print("程序出错了.")
获取异常对象

在异常处理块中, 可以使用as关键字将异常信息赋给一个变量, 以便进一步处理或打印异常信息.

python 复制代码
 try:
  # 可能引发异常的代码块
 except ExceptionType as e:
  # 处理异常, 并使用变量e访问异常信息
 
 
 def divide_numbers(a, b):
   try:
  	  result = a / b
   except Exception as e: # 注意: 根据基类Exception来捕获异常只能放在最后不能放在其他异常类之前
      print(f"程序出现了意外错误: 具体的出错内容: {e}")

else块

else块是try-except语句的可选部分 , 用于指定在try块中的代码执行完毕且没有引发异常时要执行的代码. 如果在try块中发生了异常并被捕获, 则不会执行else块中的代码.

python 复制代码
 try:
  # 可能引发异常的代码块
 except ExceptionType:
  # 处理异常的代码块
 else:
 # 在没有发生异常时执行的代码块, 发生异常时就不会执行

注意点: 在try里如果return了, 即使没有报错, 也不会执行else里的代码

python 复制代码
 # 示例代码, 大家自己跑到pycharm里运行的试试.
 def test():
  try:
      # 执行一些操作
      result = 3
      return result  # 在这里返回结果,函数立即退出
  except ValueError:
     # 处理异常
      pass
  else:
      # 这里的代码不会执行
     print("This code will not be executed.")
 
 # 调用函数
 result = test()
 print(result)

使用 else 块可以提高代码的可读性和可维护性, 但并不是所有人都认同这种做法.有些开发者更倾向于只使用 try-except 块, 将异常处理的逻辑放在一起, 使代码更加紧凑.总之, 使用 try-else 结构是一种代码组织的选择, 而不是强制性的规范.在实际开发中, 我们应根据代码的需求、可读性和一致性等因素来决定是否使用 else

很多大牛在开发中几乎不使用 else, 因为对代码的可读性并没有多大的提升.

所以了解即可.

finally块

除了try-except语句, 还可以使用finally(可选的)块来执行无论是否发生异常都需要执行的代码. finally块中的代码在try块中的代码执行完毕后无论是否发生异常都会执行.

python 复制代码
 try:
  # 可能引发异常的代码块
 except ExceptionType:
  # 处理异常的代码块
 finally:
 # 无论是否发生异常, 都会执行的代码块
 
 
  def divide_numbers(a, b):
  try:
     result = a / b
     print("result", result)
  except ZeroDivisionError:
      print("0不能作为除数.")
  except TypeError:
      print("参数传递类型错误.")
  except Exception as e:
      print(f"程序出现了意外错误: 具体出错内容: {e}.")
  finally:
      print(f"{a}/{b}的结果计算完毕.")

由于finally块来执行无论是否发生异常都会执行的代码块, 所以经常用来处理资源回收.

python 复制代码
try:
  # 没有使用with语句打开文件的话, 需要手动关闭文件
  f = open('./contact1.txt', 'r')
  f.close()
except FileNotFoundError:
  print("不存在此文件")
  
# 上面的代码是有问题的
# 如果打开文件的时候出错了, 比如当前目录下没有这个文件就会导致异常
# 打开文件导致异常后就不会执行f.close()关闭文件
# 但是有一点还需要格外注意就是如果是由于没有找到这个文件触发异常就会在打开文件的时候触发异常然后直接跳到except FileNotFoundError执行后面的代码, 由于表达式的执行是先计算右边的值然后再将计算结果赋值给左边, 这里由于打开文件触发异常导致没有执行赋值语句, 所以此时f变量是不存在的, 后续调用f.close()是没有意义的.

# 还有一种情况是, 如果打开文件没有问题, 但是在后续处理异常时出现问题
# 这个时候就可以使用finally来关闭文件
try:
  f = open('./contact.txt', 'r')
  content = f.read()
  # 后续处理
  # ...
except FileNotFoundError:
  print("不存在此文件")
except Exception:
  print("后续处理异常")
finally:
  f.close()
python 复制代码
# 如果在函数体的try代码块中有return语句和finally, 它们的执行顺序是怎样的:
def divide_numbers(a, b):
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("0不能作为除数.")
    except TypeError:
        print("参数传递类型错误.")
    except Exception as e:
        print(f"程序出现了意外错误: 具体出错内容: {e}.")
    finally:
        print(f"{a}/{b}的结果计算完毕.")


res1 = divide_numbers(10, 2)

# 执行结果
# 10/2的结果计算完毕.
# 5.0

# 从上面的执行结果说明了:
# 即使在函数定义里的try代码块中使用了return语句还是会执行后面finally代码块中的内容

总结

异常通常用于处理意外或异常的情况, 即那些无法在代码中预测和处理的情况.

当你无法准确预测可能出现的错误或无法在当前上下文中处理错误时, 抛出异常是一种更合适的方式.
异常提供了一种机制, 让你能够在错误发生时终止当前的代码执行, 并将控制权交给上层代码或异常处理机制

3. raise语句

raise语句是 Python 中的一个关键字, 用于手动引发异常.(前面都是自动触发异常)

通过 raise 语句, 我们可以选择性地在代码中引发特定类型的异常, 并提供相应的错误信息.这样, 我们就能够在需要的时候中断程序的正常执行流程, 并进行适当的异常处理.

语法:

python 复制代码
 raise [ExceptionType([args])]
 # ExceptionType 是要引发的异常类型.
 # args 是一个可选参数, 用于向异常类传递附加的信息.
 # 如果只写一个raise, 则会默认引发RuntimeError.
 # raise语句后的代码不会执行.(类似return)
python 复制代码
def check_age(age):
    if age < 18:
        raise ValueError('年龄小于18岁, 不允许注册.') # 手动抛出异常
    else:
        # 注册代码如下:
        pass
        print('恭喜, 注册成功')

try:
    age = int(input("请输入年龄:"))
    check_age(age)
except ValueError as e:
    print(e) # 可以把在上面的raise中指定的抛出异常的信息打印出来, 请细品raise的使用 - 可以Chat一下
    
# 执行结果:
# 请输入年龄:17
# 年龄小于18岁, 不允许注册.

使用注意事项:

  1. 选择适当的异常类型:根据具体的情况, 选择合适的异常类型来反映错误的性质. Python 提供了许多内置的异常类, 如 ValueErrorTypeErrorFileNotFoundError 等, 可以根据需要选择合适的异常类或自定义异常类.
  2. 提供明确的错误信息:在引发异常时, 尽量提供清晰、明确的错误信息, 以便在程序出错时能够准确地定位和理解错误的原因.
  3. 在适当的位置引发异常:raise语句应该放置在程序逻辑中合适的位置, 以便在需要时引发异常.根据代码的要求, 可以在函数、方法或其他控制流结构中使用 raise 语句.
  4. 捕获和处理异常:使用 try-except 块捕获和处理引发的异常.根据具体的异常类型, 编写相应的错误处理代码, 以便优雅地处理异常情况.
  5. 避免滥用 raise 语句:raise 语句应该用于合适的情况, 不应该滥用.只有在必要的时候才使用 raise语句, 以避免引发不必要的异常.

4. 自定义异常

异常类型都是 继承自Exception的类,表示各种类型的错误.

我们也可以自己定义异常,比如我们写一个用户注册的函数, 要求用户输入的电话号码只能是中国的电话号码,并且电话号码中不能有非数字字符.

可以定义下面这两种异常类型:

python 复制代码
# 异常对象,代表电话号码有非法字符
class InvalidCharError(Exception):
    pass

# 异常对象,代表电话号码非中国号码
class NotChinaTelError(Exception):
    pass

定义了上面的异常,当用户输入电话号码时,出现相应错误的时候,我们就可以使用raise 关键字来抛出对应的自定义异常.

python 复制代码
def  register():
    tel = input('请注册您的电话号码:')

    # 如果有非数字字符
    if not tel.isdigit(): 
        raise InvalidCharError()

    # 如果不是以86开头,则不是中国号码
    if not tel.startswith('86'): 
        raise NotChinaTelError()

    return tel

try:
    ret = register()
except InvalidCharError:
    print('电话号码中有错误的字符')
except NotChinaTelError:
    print('非中国手机号码')

5. 函数调用里面产生的异常

请看下面这段代码:

python 复制代码
def level_3():
    print ('进入 level_3')
    a = [0]
    b = a[1]
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    level_3()
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    level_2()
    print ('离开 level_1')


level_1()

print('程序正常退出')

运行该代码会得到类似下面的结果:

python 复制代码
进入 level_1
进入 level_2
进入 level_3
Traceback (most recent call last):
  File "E:\err.py", line 18, in <module>
    level_1()
  File "E:\err.py", line 14, in level_1
    level_2()
  File "E:\err.py", line 9, in level_2
    level_3()
  File "E:\err.py", line 4, in level_3
    b = a[1]
IndexError: list index out of range

函数调用次序是这样的

主体部分调用 函数 level_1

函数level_1调用 函数level_2

函数level_2调用 函数level_3

大家注意:函数 level_3 中有个 列表索引越界的错误.

所以执行到该函数的时候,解释器报错了。它在终端上显示了错误代码的具体位置. 也就是:

python 复制代码
File "E:\err.py", line 4, in level_3
    b = a[1]

大家可以发现,上面还有输出的信息,说明了这行引起异常的代码, 是怎样被 一层层 的调用进来的.

这就是函数调用栈的信息.

当异常在函数中产生的时候,解释器会终止当前代码的执行, 查看当前函数是否 声明了该类型异常的 except 处理,如果有,就执行, 随后继续执行代码.

如果当前函数没有 声明了该类型异常的处理, 就会中止当前函数的执行,退出到调用该函数的上层函数中, 查看上层是否有 声明了该类型异常的 except 处理. 如果有,就执行该异常匹配处理. 随后继续执行代码.

如果上层函数也没有 该类型异常的匹配处理, 就会到继续到再上层的函数查看是否有 该类型异常的匹配处理.

如此这般,直到到了最外层的代码. 如果依然没有 声明了该类型异常处理,就终止当前代码的执行.

补充练习

案例: 模拟用户名校验.

python 复制代码
def check_username(username):
    """
    1. 长度不能小于5.
    2. 只能包含字符.
    3. 禁止使用系统用户名. admin, root.
    :param username: 传入的用户名.
    :return: None.
    """
    if username in ['admin', 'root']:
        raise ValueError('禁止使用系统用户名')
    if len(username) < 5:
        raise ValueError('用户名长度小于5')
    if not username.isalpha():
        raise ValueError('用户名只能包含字符')
    # 如果上面的3个判断都没进, 就会走到这里.
    print(f'{username}校验成功.')


try:
    username = input("请输入用户名:")
    check_username(username)
except ValueError as e:
    print(e)
python 复制代码
"""
对之前写的通讯录加上异常处理机制 使用异常捕获完善. 使其健壮性更强.
1. 如果用户输入了我们规则之外的指令. '1 2 3 4 5'以外的. 应该如何处理.
2. 读写我们上节课使用的'r+', 实际上读跟写要分离开, 才是最好的.
3. 若是我们本地没有通讯录文件的时候, 第一次读取, 会出错, 如何处理.
4. 删除联系人时, 如果用户输入错了想要删除的人, 如想要删除'张', 输入成了'刘', 容易出现误删, 而且会出现删除通讯录里不存在的人. 应该如何处理.
5. 手机号是不存在字母的, 如果用户输入有字母的话, 应该如何处理.
"""
import json


def read_file():
    try:
        with open("./contact.txt", 'r', encoding='utf8') as f:
            content = f.read()
            return content
    except FileNotFoundError:
        return False


def write_file(content):
    with open("./contact.txt", 'w', encoding='utf8') as f:
        f.write(content)


def query_all():
    content = read_file()
    if content:
        # 如果存在, 就要展示所有的联系人.
        try:
            json_data = json.loads(content)
            if not json_data:
                print(f'该通讯录目前没有联系人, 请先添加联系人')
                return
        except json.decoder.JSONDecodeError:
            print('系统出错, 稍后在尝试.')
            # 给开发人员发出提醒. 赶紧去修复.
            return
        else:
            print('为您查询到的所有联系人如下:')
        for key, val in json_data.items():
            print(f'{key}: {val}')
    else:
        print(f'该通讯录目前没有联系人, 请先添加联系人')


def query_contact():
    name = input("请输入想要查询的联系人姓名:")
    content = read_file()
    if content:
        # 如果存在, 就要做查询操作.
        json_data = json.loads(content)
        phone = json_data.get(name)
        print(f'为您查询到的指定联系人{name}的电话是: {phone}')
    else:
        print(f'未找到指定联系人: {name}')


def del_contact():
    name = input("请输入想要删除的联系人姓名:")
    content = read_file()
    if content:
        # 如果存在, 就要做删除操作.
        res = input(f"是否确认删除联系人; {name}. y/n?")
        if res.lower() == 'y':
            json_data = json.loads(content)
            # pop_value = json_data.pop(name, None)
            try:
                json_data.pop(name)
            except KeyError:
                print('没有找到指定的联系人.')
                return
            write_file(json.dumps(json_data, ensure_ascii=False))
            print(f'删除{name}成功')
        else:
            print('已取消删除操作')
    else:
        # 如果不存在, 就没办法删了.
        print(f'未找到指定联系人: {name}')


def add_contact():
    name = input("请输入联系人姓名:")
    phone = input("请输入联系人电话:")
    try:
        int(phone)
    except ValueError:
        print('您输入的手机号并不是纯数字的. 请重新添加')
        return
    contact_dict = {
        name: phone
    }
    content = read_file()
    if content:
        json_data = json.loads(content)
        json_data.update(contact_dict)
    else:
        json_data = contact_dict
    write_file(json.dumps(json_data, ensure_ascii=False))
    print(f'添加{name}成功')


def menu():
    print("欢迎使用通讯录管理系统")
    print("------welcome-----")
    print("菜单选项:")
    print("1. 添加联系人.")
    print("2. 删除联系人.")
    print("3. 查询指定联系人.")
    print("4. 查看所有的联系人.")
    print("5. 退出通讯录.")
    while True:
        try:
            choice = int(input("请输入您想要操作的选项:"))
        except ValueError:
            print('请输入1-2-3-4-5的对应指令.')
            continue
        if choice == 1:
            add_contact()
        elif choice == 2:
            del_contact()
        elif choice == 3:
            query_contact()
        elif choice == 4:
            query_all()
        elif choice == 5:
            print('已退出通讯录管理系统')
            break
        else:
            print('请输入1-2-3-4-5的对应指令.')

menu()

|----------------------|
| 遇见安然遇见你,不负代码不负卿。 |
| 谢谢老铁的时间,咱们下篇再见~ |

相关推荐
qianmoq几秒前
轻松掌握Java多线程 - 第二章:线程的生命周期
java·后端
Postkarte不想说话1 分钟前
FreeSWITCH与FreeSWITCH对接
后端
小戴同学1 分钟前
实时系统降低延时的利器
后端·性能优化·go
风象南2 分钟前
Spring Boot 实现文件断点续传
java·spring boot·后端
Cache技术分享2 分钟前
36. Java 控制流语句 Break 语句
前端·后端
极特架构笔记3 分钟前
百万QPS秒杀如何解决超卖少卖问题?(图解+秒懂)
后端
创新技术阁5 分钟前
FastAPI 的两大核心组件:Starlette 和 Pydantic 详解
后端·python
关山月6 分钟前
被低估的服务器发送事件(SSE)
python
GOTXX10 分钟前
BoostSiteSeeker项目实战
前端·c++·后端·mysql·搜索引擎·项目实战·boost
21 分钟前
Golang标准库介绍
后端