译自:https://hackaday.com/2018/07/23/hands-on-with-python-3-7-whats-new-in-the-latest-release/?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more
目录
内置 breakpoint()
注解和类型
计时
Dataclass
其他
结论
许多人使用的第一种、被称为世界上增长最快的编程语言当然是 Python,可用于通用编程、数据科学、网站后端、GUI 以及几乎所有其他功能。最新的 3.7.0 版本 刚发布不久。
任何版本 Python 的发行,无论变化多小,在任何开发开始之前都要经过细致的规划和设计。实际上,你可以阅读 Python 3.7 的PEP (Python Enhancement Proposal,Python 增强提议),该提议是在2016年创建的。
3.7 中有什么新功能?你为什么要升级?有什么新的有用的东西吗?我将通过介绍一些新特性的例子来回答这些问题。虽然这个版本对 Python 初学者来说没有什么不同,但是对于经验丰富的程序员来说有很多小的变化,还有一些你想要了解的主要特性。
任何使用过 pdb (Python debugger) 的人都知道它有多么强大。它能暂停脚本的执行,允许你在程序的内部手动浏览,并且单步执行语句。
但是,到目前为止,在编写程序时需要进行一些设置。当然,导入 pdb 和 set_trace() 几乎不需要花费任何时间,但这不如插入快速调试 print() 或 log 方便。在 Python 3.7 中,breakpoint() 是内置函数,可以非常容易地在任何时候插入调试器。同样值得注意的是,pdb 只是众多可用调试器之一,你可以通过设置新的 PYTHONBREAKPOINT 环境变量来配置想要使用的调试器。
这里有一个简单例子。用户需要输入一个字符串,判断它是否匹配一个值。
"""Test user's favourite Integrated Circuit."""
def test_ic(favourite_ic):
user_guess = input("Try to guess our favourite IC >>> ")
if user_guess == favourite_ic:
return "Yup, that's our favourite!"
else:
return "Sorry, that's not our favourite IC"
if __name__ == '__main__':
favourite_ic = 555
print(test_ic(favourite_ic))
不幸的是,无论输入什么,我们都无法匹配字符串。
$ python breakpoint_test.py
Try to guess our favourite IC >>> 555
Sorry, that's not our favourite IC
为了弄清楚发生了什么,让我们插入一个断点 —— 只需要调用 breakpoint()。
"""Test user's favourite Integrated Circuit."""
def test_ic(favourite_ic):
user_guess = input("Try to guess our favourite IC >>> ")
breakpoint()
if user_guess == favourite_ic:
return "Yup, that's our favourite!"
else:
return "Sorry, that's not our favourite IC"
if __name__ == '__main__':
favourite_ic = 555
print(test_ic(favourite_ic))
在 pdb 提示符下,我们将调用 locals() 来调出当前的本地作用域。pdb 有大量有用的命令,但是你也可以在其中运行正常的Python 语句。
$ python breakpoint_test.py
Try to guess our favourite IC >>> 555
> /home/ben/Hackaday/python37/breakpoint_test.py(8)test_ic()
-> if user_guess == favourite_ic:
(Pdb) locals()
{'favourite_ic': 555, 'user_guess': '555'}
(Pdb)
啊哈!看起来 favourite_ic 是一个整数,而 user_guess 是一个字符串。因为在 Python 中,将字符串与 int 进行比较是完全可行的,所以没有抛出异常(但是比较没有达到我们想要的效果)。favourite_ic 应该声明为字符串,这可以说是 Python 的动态类型的危险之一 —— 在运行时之前无法捕捉到这个错误。当然,除非你使用类型注解……
从 Python 3.5 开始,类型注解就越来越受欢迎。对于那些不熟悉类型提示的人来说,这是一种完全可选的注释代码的方式,以指定变量的类型。
类型提示(Type hints)只是 annotations 的一个应用。注解是什么?它们是关联元数据与变量的语法支持,可以是任意表达式,在运行时被 Python 计算但被忽略。注解可以是任何有效的 Python 表达式。这里有一个带注解的函数的例子,但这个例子中使用了一些无用的信息。
# Without annotation
def foo(bar, baz):
# Annotated
def foo(bar: 'Describe the bar', baz: print('random')) -> 'return thingy':
这一切都很酷,但有点无意义,除非以标准方式使用注解。在 Python 3.5 (PEP 484)中,使用注解进行编程的语法变得标准化,此后,Python 社区广泛使用了类型提示。它们纯粹是一种开发工具,可以使用 PyCharm 等 IDE 或 Mypy 等第三方工具进行检查。
如果我们的字符串比较程序是用类型注解编写的,它应该是这样的:
"""Test user's favourite Integrated Circuit."""
def test_ic(favourite_ic: str) -> str:
user_guess: str = input("Try to guess our favourite IC >>> ")
breakpoint()
if user_guess == favourite_ic:
return "Yup, that's our favourite!"
else:
return "Sorry, that's not our favourite IC"
if __name__ == '__main__':
favourite_ic: int = 555
print(test_ic(favourite_ic))
你可以看到 PyCharm 提醒了我这个错误,它可以防止我在运行时才注意到它。
这就是注解和类型提示的基础。Python 3.7 中有什么变化?正如官方的 Python 文档所指出的,当人们开始使用注解作为类型提示时,出现了两个主要问题:启动性能和前向引用。
- 不出意外的是,在定义时计算大量任意表达式相当影响启动性能,而且 typing 模块非常慢
- 你不能用尚未声明的类型来注解
这种缺乏前向引用的做法似乎是合理的,但在实践中却变得相当麻烦。
class User:
def __init__(self, name: str, prev_user: User) -> None:
pass
这种做法将失败,因为 User 还没有被声明,因此 prev_user 不能定义为 User 类型。
为了解决这两个问题,注解的评估被推迟。
要实现上述行为,必须导入 __future__,因为在保持与以前版本兼容的情况下无法进行此更改。
from __future__ import annotations
class User:
def __init__(self, name: str, prev_user: User) -> None:
pass
typing 模块如此缓慢的部分原因是,最初的设计目标是在不修改核心 CPython 解释器的情况下实现 typing 模块。既然类型提示变得越来越流行,这一限制已经被移除,这意味着现在有了对 typing 的核心支持,使得支持多种优化。
time 模块在定时上加入了一些新功能:现有的定时器功能达到了纳秒级,这意味着如果需要的话,可以有更高的精度。一些基准测试表明,time.time() 的分辨率是 time.time_ns() 的三倍以上。
说到时间,Python 本身在 3.7 中获得了一个小的速度提升。这是底层的内容,所以我们现在不深入讨论,这里有完整的优化列表。你只需要知道,在 Linux 上的启动时间比之前要快 10%,在 MacOS 上快 30%,大量的方法调用要快 20%。
我敢打赌,如果你曾经编写过面向对象的 Python,你就会创建一个类,最终看起来像这样:
class User:
def __init__(self, name: str, age: int, favourite_ic: str) -> None:
self.name = name
self.age = age
self.favourite_ic = favourite_ic
def is_adult(self) -> bool:
"""Return True if user is an adult, else False."""
return self.age >= 18
if __name__ == '__main__':
john = User('John', 29, '555')
print(john)
# prints "<__main__.User object at 0x0076E610>"
当类被初始化时,__init__ 会接收到大量不同的参数。这些属性直接设置为类实例的属性,供以后使用。在编写这类类时,这是一种非常常见的模式 —— 但这是Python,如果可以避免单调乏味,那么它就可以。
在 3.7 中,我们有 dataclass,这将使这类类更容易声明,也更可读。
只需用 @dataclass 装饰类,self 的赋值就会自动处理。变量的声明如下所示,类型注解是强制性的(如果你想灵活的话,你仍然可以使用 Any 类型)。
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
favourite_ic: str
def is_adult(self) -> bool:
"""Return True if user is an adult, else False."""
return self.age >= 18
if __name__ == '__main__':
john = User('John', 29, '555')
print(john)
# prints "User(name='John', age=29, favourite_ic='555')"
这使得类不仅容易设置,而且当我们创建一个实例并打印出来时,它还生成了一个优美的字符串。在与其他类实例进行比较时,它也会有适当的行为。这是因为,除了自动生成 __init__ 方法外,还生成了其他特殊方法,如 __repr__、__eq__ 和 __hash__ 等。当定义这样的类时,大大减少了所需的开销。
Dataclass 使用字段 (field) 来完成它们的工作,手动构造一个 field() 函数能够访问其他选项,从而更改默认值。例如,这里将 field 中的 default_factory 设置为一个 lambda 函数,该函数提示用户输入其名称。
from dataclasses import dataclass, field
class User:
name: str = field(default_factory=lambda: input("enter name"))
(我们不建议直接将 input 输入到属性中,这只是一个字段功能的演示。)
在这个版本中还有许多其他的变化;我们将在这里列出一些最重要的:
- 字典现在保持插入顺序。这在 3.6 中是非正式的,但现在是官方语言规范。在大多数情况下,普通的 dict 应该能够替换 collections.OrderedDict。
- 加入法文、日文和韩文文档翻译。
- 对模块属性访问的控制现在更容易了,因为 __getattr__ 现在可以在模块层次进行定义。这使得定制导入行为和实现特性,例如弃用警告,变得更加容易。
- CPython 的一种新的开发模式。
- .pyc 文件具有确定性,支持可重复构建 —— 也就是说,总是为相同的输入文件生成相同的 byte-for-byte 输出。
有一些非常简洁的语法快捷方式和性能改进,但这可能不足以鼓励每个人进行升级。总的来说,Python 3.7 实现了一些特性,这些特性将真正减少混乱的代码解决方案,并生成更干净的代码。我们当然期待使用它,也等不及 3.8 的到来!