简介
在这个实验中,你将学习如何在 Python 中实现异常处理。你将了解如何使用日志记录模块(logging module)进行更好的错误报告,这对于调试和维护你的代码至关重要。
你还将练习修改 reader.py
文件,以优雅地处理错误,而不是让程序崩溃。这种实践经验将提升你编写健壮 Python 程序的能力。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在这个实验中,你将学习如何在 Python 中实现异常处理。你将了解如何使用日志记录模块(logging module)进行更好的错误报告,这对于调试和维护你的代码至关重要。
你还将练习修改 reader.py
文件,以优雅地处理错误,而不是让程序崩溃。这种实践经验将提升你编写健壮 Python 程序的能力。
在这一步中,你将学习 Python 中的异常。异常是编程中的一个重要概念,它能帮助我们处理程序运行时可能出现的意外情况。你还将了解当前代码在处理无效数据时崩溃的原因。理解这些将有助于你编写更健壮、更可靠的 Python 程序。
在 Python 中,异常是程序执行过程中发生的事件,它会打乱程序的正常指令流。你可以把它想象成高速公路上的路障。一切顺利时,程序会按既定路径执行,就像汽车在畅通的道路上行驶。但当出现错误时,Python 会创建一个异常对象。这个对象就像一份报告,包含了出错信息,比如错误类型和代码中出错的位置。
如果这些异常没有得到妥善处理,程序就会崩溃。程序崩溃时,Python 会显示一个回溯信息(traceback message)。这个信息就像一张地图,能精确显示代码中出错的位置,对调试非常有用。
首先,我们来看看 reader.py
文件的结构。这个文件包含了用于读取和转换 CSV 数据的函数。要在编辑器中打开这个文件,我们需要导航到正确的目录。在终端中使用 cd
命令:
cd /home/labex/project
现在我们已经进入了正确的目录,来看看 reader.py
的内容。这个文件有几个重要的函数:
convert_csv()
:该函数接收数据行,并使用提供的转换器函数对其进行转换。它就像一台机器,将原材料(数据行)按照特定的配方(转换器函数)转化为另一种形式。csv_as_dicts()
:该函数读取 CSV 数据并将其转换为字典列表。它还会进行类型转换,确保字典中的每个数据项都是正确的类型,如字符串、整数或浮点数。read_csv_as_dicts()
:这是一个包装函数。它就像一个管理者,调用 csv_as_dicts()
函数来完成任务。让我们看看代码处理无效数据时会发生什么。我们将打开一个 Python 解释器,它就像一个游乐场,你可以在其中交互式地测试 Python 代码。在终端中使用以下命令打开 Python 解释器:
python3
Python 解释器打开后,我们将尝试读取 missing.csv
文件。这个文件包含一些缺失或无效的数据。我们将使用 reader.py
文件中的 read_csv_as_dicts()
函数来读取数据。
from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])
运行这段代码时,你会看到类似这样的错误信息:
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: ''
这个错误的出现是因为代码试图将空字符串转换为整数。空字符串不能表示有效的整数,所以 Python 无法完成转换。函数在遇到第一个错误时就会崩溃,并且停止处理文件中其余的有效数据。
要退出 Python 解释器,请输入以下命令:
exit()
错误发生在 convert_csv()
函数中,具体是在以下这一行:
return list(map(lambda row: converter(headers, row), rows))
map()
函数将 converter
函数应用于 rows
列表中的每一行。converter
函数会尝试将类型(字符串、整数、浮点数)应用到每一行。但当遇到包含缺失数据的行时,它就会失败。map()
函数没有内置的异常处理机制,所以一旦出现异常,整个处理过程就会崩溃。
在下一个步骤中,你将修改代码以优雅地处理这些异常。这意味着程序不会崩溃,而是能够处理错误并继续处理其余的数据。
在这一步中,我们将着重让你的代码变得更加健壮。当程序遇到无效数据时,通常会崩溃。但我们可以使用一种名为异常处理的技术来优雅地处理这些问题。你将修改 reader.py
文件以实现这一点。异常处理能让你的程序即使在遇到意外数据时也能继续运行,而不是突然停止。
Python 提供了一种强大的方式来使用 try-except 块处理异常。下面我们来详细了解它们的工作原理。
try:
## Code that might cause an exception
result = risky_operation()
except SomeExceptionType as e:
## Code that runs if the exception occurs
handle_exception(e)
在 try
块中,你放置可能引发异常的代码。异常是程序执行过程中出现的错误。例如,如果你尝试将一个数除以零,Python 会引发一个 ZeroDivisionError
异常。当 try
块中出现异常时,Python 会停止执行 try
块中的代码,并跳转到匹配的 except
块。except
块包含处理异常的代码。SomeExceptionType
是你想要捕获的异常类型。你可以捕获特定类型的异常,也可以使用通用的 Exception
来捕获所有类型的异常。as e
部分允许你访问异常对象,该对象包含有关错误的信息。
现在,让我们将所学的 try-except 块知识应用到 convert_csv()
函数中。在编辑器中打开 reader.py
文件。
convert_csv()
函数替换为以下代码:def convert_csv(rows, converter, header=True):
"""
Convert a sequence of rows to an output sequence according to a conversion function.
"""
if header:
headers = next(rows)
else:
headers = []
result = []
for row_idx, row in enumerate(rows, start=1):
try:
## Try to convert the row
result.append(converter(headers, row))
except Exception as e:
## Print a warning message for bad rows
print(f"Row {row_idx}: Bad row: {row}")
continue
return result
在这个新的实现中:
for
循环而不是 map()
来处理每一行。这让我们对每一行的处理有更多的控制权。except
块。except
块中,我们为无效行打印一条错误消息。这有助于我们识别哪些行有问题。continue
语句跳过当前行并继续处理剩余的行。完成这些更改后保存文件。
让我们使用 missing.csv
文件来测试你修改后的代码。首先,在终端中运行以下命令打开 Python 解释器:
python3
进入 Python 解释器后,运行以下代码:
from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")
运行这段代码时,你应该会看到每一个有问题的行的错误消息。但程序会继续处理并返回有效的行。以下是你可能看到的输出示例:
Row 4: Bad row: ['C', '', '53.08']
Row 7: Bad row: ['DIS', '50', 'N/A']
Row 8: Bad row: ['GE', '', '37.23']
Row 13: Bad row: ['INTC', '', '21.84']
Row 17: Bad row: ['MCD', '', '51.11']
Row 19: Bad row: ['MO', '', '70.09']
Row 22: Bad row: ['PFE', '', '26.40']
Row 26: Bad row: ['VZ', '', '42.92']
Number of valid rows processed: 20
我们还来验证一下程序在处理有效数据时是否能正常工作。在 Python 解释器中运行以下代码:
valid_port = read_csv_as_dicts('valid.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(valid_port)}")
你应该会看到所有行都被无错误地处理。以下是输出示例:
Number of valid rows processed: 17
要退出 Python 解释器,运行以下命令:
exit()
现在你的代码更加健壮了。它可以通过跳过无效行来优雅地处理无效数据,而不是崩溃。这使得你的程序更加可靠和用户友好。
在这一步中,我们将让你的代码更完善。不再使用简单的打印消息,而是使用 Python 的 logging
模块进行规范的日志记录。日志记录是跟踪程序运行情况的好方法,尤其是在处理错误和理解代码流程时。
Python 中的 logging
模块为我们提供了一种灵活的方式,可从应用程序中输出日志消息。它比简单的打印语句强大得多。以下是它的功能:
现在,让我们修改代码以使用日志记录模块。打开 reader.py
文件。
首先,我们需要导入 logging
模块,并为该模块设置一个日志记录器。在文件顶部添加以下代码:
import logging
## Set up a logger for this module
logger = logging.getLogger(__name__)
import logging
语句引入了 logging
模块,以便我们可以使用其功能。logging.getLogger(__name__)
为这个特定模块创建了一个日志记录器。使用 __name__
可确保日志记录器具有与模块相关的唯一名称。
接下来,我们将修改 convert_csv()
函数,使用日志记录而不是打印语句。以下是更新后的代码:
def convert_csv(rows, converter, header=True):
"""
Convert a sequence of rows to an output sequence according to a conversion function.
"""
if header:
headers = next(rows)
else:
headers = []
result = []
for row_idx, row in enumerate(rows, start=1):
try:
## Try to convert the row
result.append(converter(headers, row))
except Exception as e:
## Log a warning message for bad rows
logger.warning(f"Row {row_idx}: Bad row: {row}")
## Log the reason at debug level
logger.debug(f"Row {row_idx}: Reason: {str(e)}")
continue
return result
这里的主要更改如下:
print()
替换为 logger.warning()
来输出错误消息。这样,消息会以适当的警告级别进行记录,并且我们可以在以后控制其可见性。logger.debug()
消息,包含有关异常的详细信息。这为我们提供了更多关于出错原因的信息,但只有在日志级别设置为 DEBUG 或更低时才会显示。str(e)
将异常转换为字符串,以便我们可以在日志消息中显示错误原因。完成这些更改后,保存文件。
让我们在启用日志记录的情况下测试你的代码。在终端中运行以下命令打开 Python 解释器:
python3
进入 Python 解释器后,执行以下代码:
import logging
import reader
## Configure logging level to see all messages
logging.basicConfig(level=logging.DEBUG)
port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")
在这里,我们首先导入 logging
模块和我们的 reader
模块。然后,使用 logging.basicConfig(level=logging.DEBUG)
将日志级别设置为 DEBUG。这意味着我们将看到所有日志消息,包括 DEBUG、INFO、WARNING、ERROR 和 CRITICAL。然后,我们调用 reader
模块中的 read_csv_as_dicts
函数,并打印处理的有效行数。
你应该会看到如下输出:
WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'
...
Number of valid rows processed: 20
注意,日志记录模块会为每条消息添加一个前缀,显示日志级别(WARNING/DEBUG)和模块名称。
现在,让我们看看如果将日志级别更改为仅显示警告会发生什么。在 Python 解释器中运行以下代码:
## Reset the logging configuration
import logging
logging.basicConfig(level=logging.WARNING)
port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])
这次,我们使用 logging.basicConfig(level=logging.WARNING)
将日志级别设置为 WARNING。现在你只会看到 WARNING 消息,而 DEBUG 消息将被隐藏:
WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
...
这显示了使用不同日志级别的优势。我们可以在不更改代码的情况下控制日志中显示的详细程度。
要退出 Python 解释器,运行以下命令:
exit()
恭喜!你现在已经在 Python 程序中实现了规范的异常处理和日志记录。这使你的代码更可靠,并在出现错误时为你提供更详细的信息。
在这个实验中,你学习了 Python 中异常处理和日志记录的几个关键概念。首先,你掌握了在数据处理过程中异常是如何产生的,并实现了 try-except 块来优雅地处理它们。你还修改了代码,以便在出现错误时能继续处理有效数据。
其次,你了解了 Python 的日志记录模块及其相对于打印语句的优势。你实现了不同的日志级别,如 WARNING 和 DEBUG,并了解了如何为不同的详细程度配置日志记录。这些技能对于编写健壮的 Python 应用程序至关重要,特别是在处理易出错的外部数据、构建无人值守的应用程序或开发需要诊断信息的系统时。现在,你可以将这些技术应用到你的 Python 项目中,以提高其可靠性和可维护性。