在编程的世界里,错误和异常是不可避免的。它们是编程生涯中不可或缺的一部分,也是提升编程能力的重要途径。面对bug和错误,不仅要解决它们,更要从中学习,这样才能成为更优秀的程序员。在任何编程语言中,编写程序时都有一定的规则,比如在定义变量名时不能使用空格,if语句后必须加上冒号等。如果不遵守这些规则,就会遇到语法错误,程序也会因此拒绝执行,直到修正这些错误。
然而,有时候即使程序在语法上是正确的,执行时仍然会抛出错误,这些在执行过程中被检测到的错误被称为异常。处理这些异常的过程就叫做异常处理。在Python中,异常处理是一个非常重要的概念,它可以帮助优雅地处理程序运行中可能出现的错误,防止程序崩溃。
为什么要学习异常处理呢?这里有两个主要的理由。首先,假设编写了一个脚本来读取多个目录中成千上万的文件,可能会遇到文件类型缺失、格式错误或不同扩展名等问题。在这种情况下,不可能打开所有文件并为每种情况编写相应的脚本。异常处理允许定义多种条件,例如,如果格式错误,可以先纠正格式,然后再尝试读取文件;否则,跳过该文件,并创建一个日志文件,以便稍后处理。
其次,假设正在从网站抓取餐厅数据,脚本寻找餐厅的名称、评论和地址。由于某些原因,网站上缺少餐厅的地址。在这种情况下,如果不处理异常,脚本可能会在中途停止。因此,在收集大量数据时,处理异常是至关重要的。
以下是在Python中可能会遇到的一些常见异常:
ZeroDivisionError
:当尝试将一个数字除以零时会引发此异常。ImportError
:当尝试导入未安装的库或提供了错误名称时会引发此异常。IndexError
:在序列中找不到索引时会引发此异常。例如,如果列表的长度是10,而尝试访问第11个索引,就会得到这个错误。IndentationError
:当缩进没有正确指定时会引发此异常。ValueError
:当内置函数的数据类型有有效的参数类型,但参数值无效时会引发此异常。Exception
:所有异常的基类。如果不确定可能发生哪种异常,可以使用基类。它将处理所有异常。可以在这里阅读更多关于常见异常的信息。
在像Python这样的编程语言中,try()函数用于处理可能在代码块执行期间发生的异常或错误。它允许优雅地捕获和处理异常,防止程序崩溃。
try()的工作原理如下:
让定义一个函数来除以两个数字a和b。如果b的值非零,它将正常工作;但如果b的值为零,它将产生错误:
# 示例代码
def divide(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
return "Error: Division by zero!"
在Python中,还可以指示程序在没有发生异常时执行某些代码行,使用else子句。现在,如果没有异常发生,希望打印“没有发生错误!”。让看看如何做到这一点:
# 示例代码
try:
# 尝试执行的代码
pass
except SomeException:
# 异常处理代码
pass
else:
# 如果没有异常发生,执行这里的代码
print("No Error occurred!!")
现在,如果需要执行某种动作,无论是否发生错误(如维护日志),可以使用Python中的finally子句。它将始终被执行,无论程序是否遇到任何异常。
将看到如何在本文稍后使用finally子句来写日志。现在,在上述示例中,希望在每次执行后无论是否发生错误都打印a和b的值。让看看如何做到这一点:
# 示例代码
try:
# 尝试执行的代码
pass
except SomeException:
# 异常处理代码
pass
else:
# 如果没有异常发生,执行这里的代码
pass
finally:
# 无论是否发生异常,都会执行这里的代码
print("Value of a and b after execution")
到目前为止,已经看到了一些随机数据的异常处理。让通过一个实际的例子来理解这个概念。有包含员工详细信息的数据,如他们的教育背景、年龄、参加的培训次数等。数据按地区划分为多个目录。同一地区的员工详细信息存储在同一个文件中。
现在,任务是读取所有文件并将它们合并成一个文件。让首先导入一些所需的库。
# 示例代码
import glob
import pandas as pd
# 使用glob.glob函数和目标目录的路径查看目录结构
directory_structure = glob.glob('/path/to/directory/*')
可以看到文件夹名称表示为一些数字,下一步将遍历每个目录并查看其中的文件:
# 示例代码
for folder in directory_structure:
files = glob.glob(folder + '/*.csv')
for file in files:
print(file)
在每个文件夹中,都有一个CSV文件包含该地区员工的详细信息。可以打开并查看任何CSV文件。以下是region_1.csv文件中数据的样子。它包含属于地区1的员工的详细信息:
现在知道目录和文件名结构有一个模式。在目录n中,有一个名为region_n的CSV文件。所以现在将尝试使用循环读取所有这些文件。
# 示例代码
for i in range(1, 35): # 假设最大数字是34
file_path = f'/path/to/directory/{i}(region_{i}.csv)'
try:
data = pd.read_csv(file_path)
# 处理数据
except FileNotFoundError:
print(f'File not found: {file_path}')
except Exception as e:
print(f'An error occurred: {file_path}, Error: {e}')
可以看到文件region_7不存在。因此,处理这个问题的一个简单方法是在程序中添加一个if条件——如果目录名称是7,则跳过该文件的读取。但如果必须一起读取成千上万的文件,每次遇到错误时更新if条件将是一项繁琐的任务。
在这里,将使用try和except语句来处理错误。如果在运行时读取任何文件时发生任何异常,将跳过该步骤并继续读取下一个文件夹。如果错误是FileNotFoundError,将打印文件名并附上“文件未找到!”;如果发生任何其他错误,将打印文件名并附上“其他错误!”。
# 示例代码
parse_error = False
file_not_found = False
for i in range(1, 35):
file_path = f'/path/to/directory/{i}(region_{i}.csv)'
try:
data = pd.read_csv(file_path)
except FileNotFoundError:
file_not_found = True
print(f'File not found: {file_path}')
except pd.errors.ParserError:
parse_error = True
print(f'Incorrect format: {file_path}')
data = pd.read_csv(file_path, skiprows=1)
else:
# 如果没有异常发生,执行这里的代码
# 将数据帧追加到数据帧列表
pass
# 示例代码
for i in range(1, 35):
file_path = f'/path/to/directory/{i}(region_{i}.csv)'
try:
data = pd.read_csv(file_path)
except Exception as e:
# 处理异常
pass
else:
# 如果没有异常发生,执行这里的代码
pass
finally:
# 无论是否发生异常,都会执行这里的代码
# 将文件状态写入日志文件
pass