错误信息回溯
长按关注《Python学研大本营》,加入读者群,分享更多精彩 扫码关注《Python学研大本营》,加入读者群,分享更多精彩
Python 3.11于2022 年 10 月 24 日发布。这个最新版本的 Python 速度更快,对用户更友好。
与每个版本一样,Python 3.11 都进行了许多改进和更改。您可以在文档中看到所有这些的列表。在这里,您将探索最酷、最具影响力的新功能。
在本专题的教程中,您将了解新功能和改进,例如:
-
更好的错误消息和更多信息的回溯
-
由于Faster CPython项目付出了巨大的努力,代码执行速度更快
-
简化使用异步代码的任务和异常组
-
改进 Python 的静态类型支持的几个新类型特性
-
本机TOML 支持处理配置文件
如果您想尝试本教程中的任何示例,则需要使用 Python 3.11。Python 3 安装和设置指南(https://realpython.com/installing-python/)以及如何安装 Python 的预发布版本(https://realpython.com/python-pre-release/)引导您完成向系统添加新版本 Python 的几个选项。
除了了解有关该语言的新功能的更多信息外,您还将获得一些关于在升级到新版本之前要考虑什么的建议。下面的链接下载演示 Python 3.11 新功能的代码示例(https://realpython.com/bonus/python-311-examples/)
信息错误回溯
Python 通常被认为是一种很好的初学者编程语言,它具有可读的语法和强大的数据结构。所有人都面临的挑战,尤其是那些刚接触 Python 的人,是如何解释Python 遇到错误时显示的回溯。
在Python 3.10中,Python 的错误信息得到了极大的改进。同样,Python 3.11最受期待的功能之一也将提升您的开发者体验。装饰性注释被添加到回溯中,可以帮助您更快地解释错误消息。
要查看增强回溯的快速示例,请将以下代码添加到inverse.py的文件中:
# inverse.py
def inverse(number):
return 1 / number
print(inverse(0))
你可以用它inverse()来计算一个数的乘法倒数。没有乘法逆元0,因此您的代码在运行时会引发错误:
$ python inverse.py
Traceback (most recent call last):
File "/home/realpython/inverse.py", line 6, in <module>
print(inverse(0))
^^^^^^^^^^
File "/home/realpython/inverse.py", line 4, in inverse
return 1 / number
~~^~~~~~~~
ZeroDivisionError: division by zero
请注意嵌入在回溯中的^和符号~,它们用于引导您注意导致错误的代码。像往常一样使用回溯,您应该从底部开始,然后逐步向上。在此示例中, aZeroDivisionError是由除法引起的1 / number。真正的罪魁祸首是inverse(0),因为0没有相反的情况。
在发现错误时获得这种额外的帮助很有用。但是,如果您的代码更复杂,带注释的回溯功能会更加强大。他们可能能够传达您以前无法从回溯中获得的信息。
要了解改进的回溯的强大功能,您将构建一个关于少数程序员的信息的小型解析器。假设您有一个名为programmers.json的文件:
[
{"name": {"first": "Uncle Barry"}},
{
"name": {"first": "Ada", "last": "Lovelace"},
"birth": {"year": 1815},
"death": {"month": 11, "day": 27}
},
{
"name": {"first": "Grace", "last": "Hopper"},
"birth": {"year": 1906, "month": 12, "day": 9},
"death": {"year": 1992, "month": 1, "day": 1}
},
{
"name": {"first": "Ole-Johan", "last": "Dahl"},
"birth": {"year": 1931, "month": 10, "day": 12},
"death": {"year": 2002, "month": 6, "day": 29}
},
{
"name": {"first": "Guido", "last": "Van Rossum"},
"birth": {"year": 1956, "month": 1, "day": 31},
"death": null
}
]
请注意,有关程序员的信息非常不一致。虽然有关Grace Hopper和Ole-Johan Dahl的信息已完成,但您会错过Ada Lovelace 的出生日期和月份以及她的死亡年份。自然,您只有Guido van Rossum的出生信息。最重要的是,您只记录了Barry 叔叔的名字。
您将创建一个可以包装此信息的类。首先从 JSON 文件中读取信息:
# programmers.py
import json
import pathlib
programmers = json.loads(
pathlib.Path("programmers.json").read_text(encoding="utf-8")
)
您用于pathlib读取 JSON 文件并将json信息解析为 Python 字典列表。
接下来,您将使用数据类来封装有关每个程序员的信息:
# programmers.py
from dataclasses import dataclass
# ...
@dataclass
class Person:
name: str
life_span: tuple[int, int]
@classmethod
def from_dict(cls, info):
return cls(
name=f"{info['name']['first']} {info['name']['last']}",
life_span=(info["birth"]["year"], info["death"]["year"]),
)
每个Person都有一个name和一个life_span属性。此外,您还可以添加一个方便的构造函数,该构造函数Person可以根据 JSON 文件中的信息和结构进行初始化。
您还将添加一个可以一次性初始化两个Person对象的函数:
# programmers.py
# ...
def convert_pair(first, second):
return Person.from_dict(first), Person.from_dict(second)
该convert_pair()函数两次使用.from_dict()构造函数将一对程序员从 JSON 结构转换为Person对象。
是时候探索您的代码了,尤其是查看一些回溯。使用标志-i运行程序以打开 Python 的交互式 REPL,其中包含所有可用的变量、类和函数:
$ python -i programmers.py
>>> Person.from_dict(programmers[2])
Person(name='Grace Hopper', life_span=(1906, 1992))
Grace 的信息是完整的,因此您可以将她封装到一个Person包含有关她的全名和寿命信息的对象中。
要查看实际的新回溯,请尝试转换 Barry 叔叔:
>>> programmers[0]
{'name': {'first': 'Uncle Barry'}}
>>> Person.from_dict(programmers[0])
Traceback (most recent call last):
File "/home/realpython/programmers.py", line 17, in from_dict
name=f"{info['name']['first']} {info['name']['last']}",
~~~~~~~~~~~~^^^^^^^^
KeyError: 'last'
你得到一个KeyError因为last缺少。虽然您可能记得那last是name中的一个子字,但注释会立即为您指出这一点。
同样,回想一下关于 Ada 的生命周期信息是不完整的。你不能为她创建一个Person对象:
>>> programmers[1]
{
'name': {'first': 'Ada', 'last': 'Lovelace'},
'birth': {'year': 1815},
'death': {'month': 11, 'day': 27}
}
>>> Person.from_dict(programmers[1])
Traceback (most recent call last):
File "/home/realpython/programmers.py", line 18, in from_dict
life_span=(info["birth"]["year"], info["death"]["year"]),
~~~~~~~~~~~~~^^^^^^^^
KeyError: 'year'
你得到了另一个KeyError,这次是因为year失踪了。在这种情况下,回溯比前面的例子更有用。您有两个year子字段,一个 forbirth和一个 for death。回溯注释立即显示您错过了死亡年份。
Guido怎么样了?你只有关于他出生的信息:
>>> programmers[4]
{
'name': {'first': 'Guido', 'last': 'Van Rossum'},
'birth': {'year': 1956, 'month': 1, 'day': 31},
'death': None
}
>>> Person.from_dict(programmers[4])
Traceback (most recent call last):
File "/home/realpython/programmers.py", line 18, in from_dict
life_span=(info["birth"]["year"], info["death"]["year"]),
~~~~~~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
在这种情况下,TypeError出现了。您之前可能已经看到过这些'NoneType'类型的错误。众所周知,它们很难调试,因为不清楚哪个对象是出乎意料的None。但是,从注释中,您会看到info["death"]在此示例中是None。
在最后一个示例中,您将探索嵌套函数调用会发生什么。请记住,convert_pair()调用Person.from_dict()两次。现在,尝试将 Ada 和 Ole-Johan 配对:
>>> convert_pair(programmers[3], programmers[1])
Traceback (most recent call last):
File "/home/realpython/programmers.py", line 24, in convert_pair
return Person.from_dict(first), Person.from_dict(second)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/realpython/programmers.py", line 18, in from_dict
life_span=(info["birth"]["year"], info["death"]["year"]),
~~~~~~~~~~~~~^^^^^^^^
KeyError: 'year'
尝试封装 Ada 引发与KeyError之前相同的情况。但是,请注意来自内部的回溯convert_pair()。因为该函数调用两次.from_dict(),所以通常需要一些努力才能确定在处理first或second时是否引发了错误。在最新版本的 Python 中,您会立即看到问题是由second引起的.
这些回溯使 Python 3.11 中的调试比早期版本更容易。您可以在 Python 3.11 预览教程Even Better Error Messages(https://realpython.com/python311-error-messages/)中查看更多示例、有关如何实现回溯的更多信息以及可以在调试中使用的其他工具。有关更多技术细节,请查看PEP 657(https://peps.python.org/pep-0657/)。
带注释的回溯将有助于提高 Python 开发人员的工作效率。
推荐书单
《Pandas1.x实例精解》
本书详细阐述了与Pandas相关的基本解决方案,主要包括Pandas基础,DataFrame基本操作,创建和保留DataFrame,开始数据分析,探索性数据分析,选择数据子集,过滤行,对齐索引,分组以进行聚合、过滤和转换,将数据重组为规整形式,组合Pandas对象,时间序列分析,使用Matplotlib、Pandas和Seaborn进行可视化,调试和测试等内容。此外,本书还提供了相应的示例、代码,以帮助读者进一步理解相关方案的实现过程。 本书适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学用书和参考手册。
链接:https://u.jd.com/UKjx4et
精彩回顾
长按关注《Python学研大本营》
长按二维码,加入Python读者群
扫码关注《Python学研大本营》,加入读者群,分享更多精彩