Python 3.11在10月24日发布。它是Python最新版本,运行速度更快且更佳友好。在经过17个月的研发,终于到了可以使用的黄金时期。
和每个发布版本一样,Python 3.11做了大量的变更及提高。你可以通过查看文档来了解大部分内容。这里,我们会阐述最酷且最有影响力的新特性。
教程里将涵盖如下内容:
如果你想运行本教程所提供的示例文件,你需要先安装Python 3.11.
Python通常被认为对新手友好的编程语言,原于它具备可读性语法强,内置大量的数据结构。对于大部分编码人员的挑战,尤其是新手来说,在Python遇到错误时该如何更好的显示信息来辅助追溯。
在Python 3.10中,错误信息有很强的提高。同样,Python 3.11最期待的特性,它极大的提高了开发体验。通过在回溯中添加信息注解来辅助你快速去识别错误信息。
我们通过例子来快速体验一下回溯改善,通过添加如下代码至inverse.py文件:
# inverse.py
def inverse(number: int) -> int:
return 1 / number
print(inverse(0))
这里使用inverse()来获取一个整数类型的倒数。0不存在倒数,所以在执行该代码我们将获得如下异常:
$ python inverse.py
Traceback (most recent call last):
File "/home/aidan/workbench/projects/new-features/inverse.py", line 8, in <module>
print(inverse(0))
^^^^^^^^^^
File "/home/aidan/workbench/projects/new-features/inverse.py", line 5, in inverse
return 1 / number
~~^~~~~~~~
ZeroDivisionError: division by zero
注意在回溯中嵌入了^和~符号。这里注意到实际引发错误的代码。和其它回溯一样,我们应该从下往上来溯源。本例子中,1/number引发了ZeroDivisionError异常。罪魁祸首是调用了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 Dhl具有完整信息,Ada Lovelace的出生和逝世信息都有不同的缺失。Guido van Rossum仅包含出生信息。Uncle Barry仅包含它的名字。
我们将创建一个类来包装这些信息。先从JSON文件读取信息:
# programmers.py
import json
import pathlib
programmers = json.loads(
pathlib.Path("programmers.json").read_text(encoding="utf-8")
)
我们使用pathlib来读取JSON文件,使用json将内容解析为包含字典内容的Python的列表。
接下来,我们使用data class来包装每个程序员的信息
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属性。例外,我们添加一个构造函数用于基于JSON结构化的数据来初始化Person。
我们还添加一个函数,用来一次初始化两个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程序,它将运行程序后保持一个Python交互环境,它环境还带有执行后所包含变量,类和方法。
$ python -i programmers.py
>>> Person.from_dict(programmers[2])
Person(name='Grace Hopper', life_span=(1906, 1992))
Grace的信息是完整,所以我们可以成功将它包装具有name和life span的Person对象。
要看看回溯情况,我们来试着转换Uncle Barry:
>>> programmers[0]
{'name': {'first': 'Uncle Barry'}}
>>> Person.from_dict(programmers[0])
Traceback (most recent call last):
File "" , line 1, in <module>
File "/home/aidan/workbench/projects/new-features/programmers.py", line 18, in from_dict
name=f"{info['name']['first']} {info['name']['last']}",
~~~~~~~~~~~~^^^^^^^^
KeyError: 'last'
因为键缺失,我们会得到一个KeyError,你可能记得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子项,一个是birth下,一个是death下。回溯里的注解快速指出了缺失为death的year项。
那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
类型错误。众所周知它非常难以debug,因为我们不知道具体哪个对象是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调试内容。更多技术细节,可以查看PEP 657。
回溯注解将极大提高Python开发者生产力。一个更振奋人心的信息是Python 3.11是至今最快的Python版本。