geekdoc-python-zh/docs/realpython/python38-new-features.md

929 lines
39 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python 3.8:很酷的新特性供您尝试
> 原文:<https://realpython.com/python38-new-features/>
*立即观看**本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解:[**Python 3.8 中很酷的新特性**](/courses/cool-new-features-python-38/)
Python 最新版本发布Python 3.8 自夏季以来一直有测试版,但在 2019 年【2019 月 14 日第一个正式版本已经准备好了。现在,我们都可以开始使用新功能,并从最新的改进中受益。
Python 3.8 带来了什么?[文档](https://docs.python.org/3.8/whatsnew/3.8.html)很好地概述了新特性。然而,本文将更深入地讨论一些最大的变化,并向您展示如何利用 Python 3.8。
在这篇文章中,你将了解到:
* 使用赋值表达式简化一些代码结构
* 在您自己的函数中强制使用仅位置参数
* 指定更精确的类型提示
* 使用 f 字符串简化调试
除了少数例外Python 3.8 包含了许多对早期版本的小改进。在本文的结尾,您将看到许多不太引人注目的变化,以及关于使 Python 3.8 比其前身更快的一些优化的讨论。最后,你会得到一些关于升级到新版本的建议。
**免费下载:** [从 Python 技巧中获取一个示例章节:这本书](https://realpython.com/bonus/python-tricks-sample-pdf/)用简单的例子向您展示了 Python 的最佳实践,您可以立即应用它来编写更漂亮的+Python 代码。
## 房间里的海象:赋值表达式
Python 3.8 最大的变化是引入了**赋值表达式**。它们是用一种新的符号(`:=`)写的。这种操作者通常被称为**海象操作者**,因为它像海象侧面的眼睛和长牙。
赋值表达式允许您在同一个表达式中赋值和返回值。例如,如果你想给一个[变量赋值](https://realpython.com/python-variables/)并且[打印](https://realpython.com/python-print/)它的值,那么你通常会这样做:
>>>
```py
>>> walrus = False
>>> print(walrus)
False
```
在 Python 3.8 中,您可以使用 walrus 运算符将这两个语句合并为一个:
>>>
```py
>>> print(walrus := True)
True
```
赋值表达式允许您将`True`赋值给`walrus`,并立即打印该值。但是请记住,没有它,海象运营商不会*而不是*做任何不可能的事情。它只是使某些构造更加方便,有时可以更清楚地传达代码的意图。
显示 walrus 操作符的一些优点的一个模式是 [`while`循环](https://realpython.com/python-while-loop/),其中您需要初始化和更新一个变量。例如,下面的代码要求用户输入,直到他们键入`quit`:
```py
inputs = list()
current = input("Write something: ")
while current != "quit":
inputs.append(current)
current = input("Write something: ")
```
这段代码不太理想。您在重复`input()`语句,不知何故,您需要将`current`添加到列表*中,然后在*之前向用户请求。更好的解决方案是建立一个无限的`while`循环,并使用`break`来停止循环:
```py
inputs = list()
while True:
current = input("Write something: ")
if current == "quit":
break
inputs.append(current)
```
这段代码相当于上面的代码,但是避免了重复,并且以某种方式保持了更符合逻辑的顺序。如果使用赋值表达式,可以进一步简化这个循环:
```py
inputs = list()
while (current := input("Write something: ")) != "quit":
inputs.append(current)
```
这将测试移回到`while`行,它应该在那里。然而,现在在那一行发生了几件事,所以要正确地阅读它需要更多的努力。对于 walrus 操作符何时有助于提高代码的可读性,请做出最佳判断。
PEP 572 描述了赋值表达式的所有细节,包括将它们引入语言的一些基本原理,以及如何使用 walrus 运算符的几个例子[。](https://www.python.org/dev/peps/pep-0572/#examples)
[*Remove ads*](/account/join/)
## 仅位置参数
内置函数`float()`可用于将[文本串](https://realpython.com/python-strings/)和数字转换为`float`对象。考虑下面的例子:
>>>
```py
>>> float("3.8")
3.8
>>> help(float)
class float(object)
| float(x=0, /) |
| Convert a string or number to a floating point number, if possible.
[...]
```
仔细看`float()`的签名。注意参数后面的斜杠(`/`)。这是什么意思?
**注:**关于`/`符号的深入讨论,参见 [PEP 457 -仅位置参数符号](https://www.python.org/dev/peps/pep-0457/)。
原来,虽然`float()`的一个参数被称为`x`,但是不允许使用它的名称:
>>>
```py
>>> float(x="3.8")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: float() takes no keyword arguments
```
当使用`float()`时,你只能通过位置指定参数,而不能通过关键字。在 Python 3.8 之前,这种**仅位置的**参数只可能用于内置函数。没有简单的方法来指定参数应该是位置性的——仅在您自己的函数中:
>>>
```py
>>> def incr(x):
... return x + 1
...
>>> incr(3.8)
4.8
>>> incr(x=3.8)
4.8
```
使用`*args` 可以模拟的仅位置参数[,但是这不太灵活,可读性差,并且迫使您实现自己的参数解析。在 Python 3.8 中,可以使用`/`来表示它之前的所有参数都必须由位置指定。你可以重写`incr()`来只接受位置参数:](https://realpython.com/python-kwargs-and-args/)
>>>
```py
>>> def incr(x, /):
... return x + 1
...
>>> incr(3.8)
4.8
>>> incr(x=3.8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: incr() got some positional-only arguments passed as
keyword arguments: 'x'
```
通过在`x`后添加`/`,您可以指定`x`是一个只有位置的参数。通过将常规参数放在斜杠后,可以将常规参数与仅限位置的参数组合在一起:
>>>
```py
>>> def greet(name, /, greeting="Hello"):
... return f"{greeting}, {name}"
...
>>> greet("Łukasz")
'Hello, Łukasz'
>>> greet("Łukasz", greeting="Awesome job")
'Awesome job, Łukasz'
>>> greet(name="Łukasz", greeting="Awesome job")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: greet() got some positional-only arguments passed as
keyword arguments: 'name'
```
在`greet()`中,斜线位于`name`和`greeting`之间。这意味着`name`是一个只有位置的参数,而`greeting`是一个可以通过位置或关键字传递的常规参数。
乍一看,只有位置的参数似乎有点限制,并且违背了 Python 关于可读性重要性的口头禅。您可能会发现,仅有位置的参数改善代码的情况并不多见。
然而,在正确的情况下,只有位置的参数可以在设计函数时给你一些灵活性。首先,当参数有自然的顺序,但是很难给它们起一个好的、描述性的名字时,只有位置的参数是有意义的。
使用仅位置参数的另一个好处是可以更容易地重构函数。特别是,您可以更改参数的名称,而不必担心其他代码依赖于这些名称。
只有位置的参数很好地补充了**只有关键字的**参数。在 Python 3 的任何版本中,都可以使用星号(`*`)指定仅关键字参数。 `*`后的任何参数*必须使用关键字指定:*
>>>
```py
>>> def to_fahrenheit(*, celsius):
... return 32 + celsius * 9 / 5
...
>>> to_fahrenheit(40)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: to_fahrenheit() takes 0 positional arguments but 1 was given
>>> to_fahrenheit(celsius=40)
104.0
```
`celsius`是一个只有关键字的参数所以如果您试图在没有关键字的情况下基于位置来指定它Python 会引发一个错误。
通过以`/`和`*`分隔的顺序指定,您可以组合仅位置、常规和仅关键字参数。在下面的示例中,`text`是仅位置参数,`border`是具有默认值的常规参数,`width`是具有默认值的仅关键字参数:
>>>
```py
>>> def headline(text, /, border="♦", *, width=50):
... return f" {text} ".center(width, border)
...
```
因为`text`是位置唯一的,所以不能使用关键字`text`:
>>>
```py
>>> headline("Positional-only Arguments")
'♦♦♦♦♦♦♦♦♦♦♦ Positional-only Arguments ♦♦♦♦♦♦♦♦♦♦♦♦'
>>> headline(text="This doesn't work!")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: headline() got some positional-only arguments passed as
keyword arguments: 'text'
```
另一方面,`border`既可以用关键字指定,也可以不用关键字指定:
>>>
```py
>>> headline("Python 3.8", "=")
'=================== Python 3.8 ==================='
>>> headline("Real Python", border=":")
':::::::::::::::::: Real Python :::::::::::::::::::'
```
最后,`width`必须使用关键字指定:
>>>
```
>>> headline("Python", "🐍", width=38)
'🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍 Python 🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍'
>>> headline("Python", "🐍", 38)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: headline() takes from 1 to 2 positional arguments
but 3 were given
```py
你可以在 [PEP 570](https://www.python.org/dev/peps/pep-0570/) 中读到更多关于位置唯一参数的内容。
[*Remove ads*](/account/join/)
## 更精确的类型
Python 的类型系统在这一点上已经相当成熟了。然而,在 Python 3.8 中,一些新特性被添加到了`typing`中,以允许更精确的输入:
* 文字类型
* 打字词典
* 最终对象
* 协议
Python 支持可选的**类型提示**,通常作为代码的注释:
```
def double(number: float) -> float:
return 2 * number
```py
在这个例子中你说`number`应该是一个`float``double()`函数也应该返回一个`float`。然而Python 将这些注释视为*提示*它们不会在运行时强制执行:
>>>
```
>>> double(3.14)
6.28
>>> double("I'm not a float")
"I'm not a floatI'm not a float"
```py
`double()`愉快地接受`"I'm not a float"`作为参数尽管那不是`float`。[库可以在运行时使用类型](https://realpython.com/python-type-checking/#using-types-at-runtime),但这不是 Python 类型系统的主要用例。
相反类型提示允许[静态类型检查器](https://realpython.com/python-type-checking/#other-static-type-checkers)对您的 Python 代码进行类型检查,而无需实际运行您的脚本。这让人想起编译器捕捉其他语言中的类型错误,如 [Java](https://www.java.com) 、 [Rust](https://www.rust-lang.org/) 和 [Crystal](https://crystal-lang.org/) 。此外,类型提示充当代码的[文档](https://realpython.com/documenting-python-code/),使其更容易阅读,以及[改进 IDE](https://realpython.com/python-type-checking/#pros-and-cons) 中的自动完成。
**:**有几种静态类型的跳棋可供选择包括 [Pyright](https://github.com/Microsoft/pyright) [Pytype](https://google.github.io/pytype/) [Pyre](https://pyre-check.org/) 在本文中您将使用 [Mypy](http://mypy-lang.org/) 您可以使用 [`pip`](https://realpython.com/what-is-pip/) [PyPI](https://pypi.org/project/mypy/) 安装 Mypy:
```
$ python -m pip install mypy
```py
在某种意义上Mypy Python 的类型检查器的参考实现并且正在 Jukka Lehtasalo 的领导下由 Dropbox 开发Python 的创造者吉多··罗苏姆是 Mypy 团队的一员
你可以在[原始 PEP 484](https://www.python.org/dev/peps/pep-0484/) 以及 [Python 类型检查(指南)](https://realpython.com/python-type-checking/)中找到更多关于 Python 中类型提示的信息
Python 3.8 中已经接受并包含了四个关于类型检查的新 pep你会看到每个例子的简短例子
[PEP 586](https://www.python.org/dev/peps/pep-0586/) 介绍一下 **[`Literal`](https://docs.python.org/3.8/library/typing.html#typing.Literal)** 型。`Literal`有点特殊,代表一个或几个特定值。`Literal`的一个用例是能够精确地添加类型,当字符串参数被用来描述特定的行为时。考虑下面的例子:
```
# draw_line.py
def draw_line(direction: str) -> None:
if direction == "horizontal":
... # Draw horizontal line
elif direction == "vertical":
... # Draw vertical line
else:
raise ValueError(f"invalid direction {direction!r}")
draw_line("up")
```py
程序将通过静态类型检查即使`"up"`是一个无效的方向类型检查器只检查`"up"`是一个字符串在这种情况下更准确的说法是`direction`必须是字符串`"horizontal"`或字符串`"vertical"`。使用`Literal`您可以做到这一点:
```
# draw_line.py
from typing import Literal
def draw_line(direction: Literal["horizontal", "vertical"]) -> None:
if direction == "horizontal":
... # Draw horizontal line
elif direction == "vertical":
... # Draw vertical line
else:
raise ValueError(f"invalid direction {direction!r}")
draw_line("up")
```py
通过将允许的值`direction`暴露给类型检查器您现在可以得到关于错误的警告:
```
$ mypy draw_line.py
draw_line.py:15: error:
Argument 1 to "draw_line" has incompatible type "Literal['up']";
expected "Union[Literal['horizontal'], Literal['vertical']]"
Found 1 error in 1 file (checked 1 source file)
```py
基本语法是`Literal[<literal>]`。例如`Literal[38]`表示文字值 38您可以使用`Union`来表示几个文字值中的一个:
```
Union[Literal["horizontal"], Literal["vertical"]]
```py
由于这是一个相当常见的用例您可以(并且可能应该)使用更简单的符号`Literal["horizontal", "vertical"]`来代替在向`draw_line()`添加类型时您已经使用了后者如果仔细观察上面 Mypy 的输出可以看到它在内部将更简单的符号翻译成了`Union`符号
有些情况下函数返回值的类型取决于输入参数一个例子是`open()`它可能根据`mode`的值返回一个文本字符串或一个字节数组这可以通过[超载](https://mypy.readthedocs.io/en/latest/more_types.html#function-overloading)来处理。
下面的例子展示了一个计算器的框架它可以以普通数字(`38`)[罗马数字](http://code.activestate.com/recipes/81611-roman-numerals/) ( `XXXVIII`)的形式返回答案:
```
# calculator.py
from typing import Union
ARABIC_TO_ROMAN = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
(100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
(10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")]
def _convert_to_roman_numeral(number: int) -> str:
"""Convert number to a roman numeral string"""
result = list()
for arabic, roman in ARABIC_TO_ROMAN:
count, number = divmod(number, arabic)
result.append(roman * count)
return "".join(result)
def add(num_1: int, num_2: int, to_roman: bool = True) -> Union[str, int]:
"""Add two numbers"""
result = num_1 + num_2
if to_roman:
return _convert_to_roman_numeral(result)
else:
return result
```py
代码有正确的类型提示:`add()`的结果将是`str``int`。然而通常调用这段代码时会使用文字`True``False`作为`to_roman`的值在这种情况下您会希望类型检查器准确推断出返回的是`str`还是`int`。这可以通过使用`Literal``@overload`来完成:
```
# calculator.py
from typing import Literal, overload, Union
ARABIC_TO_ROMAN = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
(100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
(10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")]
def _convert_to_roman_numeral(number: int) -> str:
"""Convert number to a roman numeral string"""
result = list()
for arabic, roman in ARABIC_TO_ROMAN:
count, number = divmod(number, arabic)
result.append(roman * count)
return "".join(result)
@overload
def add(num_1: int, num_2: int, to_roman: Literal[True]) -> str: ...
@overload
def add(num_1: int, num_2: int, to_roman: Literal[False]) -> int: ...
def add(num_1: int, num_2: int, to_roman: bool = True) -> Union[str, int]:
"""Add two numbers"""
result = num_1 + num_2
if to_roman:
return _convert_to_roman_numeral(result)
else:
return result
```py
添加的`@overload`签名将帮助您的类型检查器根据`to_roman`的文字值推断出`str``int`。注意省略号(`...`)是代码的一部分它们代表重载签名中的函数体
作为对`Literal`、 [PEP 591](https://www.python.org/dev/peps/pep-0591/) 的补充介绍 **[`Final`](https://docs.python.org/3.8/library/typing.html#typing.Final)** 。此限定符指定变量或属性不应被重新分配、重新定义或重写。以下是一个打字错误:
```
from typing import Final
ID: Final = 1
...
ID += 1
```py
Mypy 将突出显示行`ID += 1`并注意到您`Cannot assign to final name "ID"`。这为您提供了一种方法来确保代码中的常量永远不会改变它们的值
此外还有一个可以应用于类和方法的 **[`@final`](https://docs.python.org/3.8/library/typing.html#typing.final)** 装饰器。用`@final`修饰的[类](https://realpython.com/courses/python-decorators-101/)不能被子类化,而`@final`方法不能被子类覆盖:
```
from typing import final
@final
class Base:
...
class Sub(Base):
...
```py
Mypy 将用错误消息`Cannot inherit from final class "Base"`标记这个例子。要了解更多关于`Final`和`@final`的信息,请参见 [PEP 591](https://www.python.org/dev/peps/pep-0591/) 。
第三个允许更具体类型提示的 PEP [PEP 589](https://www.python.org/dev/peps/pep-0589/) 它引入了 **[`TypedDict`](https://docs.python.org/3.8/library/typing.html#typing.TypedDict)** 。这可用于指定字典中键和值的类型,使用类似于键入的 [`NamedTuple`](https://docs.python.org/library/typing.html#typing.NamedTuple) 的符号。
传统上词典都是用 [`Dict`](https://docs.python.org/library/typing.html#typing.Dict) 来注释的。问题是这只允许一种类型的键和一种类型的值,经常导致类似于`Dict[str, Any]`的注释。例如,考虑一个注册 Python 版本信息的字典:
```
py38 = {"version": "3.8", "release_year": 2019}
```py
`version`对应的值是一个字符串`release_year`是一个整数这不能用`Dict`来精确描述使用新的`TypedDict`您可以执行以下操作:
```
from typing import TypedDict
class PythonVersion(TypedDict):
version: str
release_year: int
py38 = PythonVersion(version="3.8", release_year=2019)
```py
类型检查器将能够推断出`py38["version"]`具有类型`str``py38["release_year"]`是一个`int`。在运行时`TypedDict`是一个常规的`dict`类型提示照常被忽略您也可以将`TypedDict`纯粹用作注释:
```
py38: PythonVersion = {"version": "3.8", "release_year": 2019}
```py
Mypy 会让你知道你的值是否有错误的类型或者你是否使用了一个没有声明的键更多例子见 [PEP 589](https://www.python.org/dev/peps/pep-0589/)
Mypy 支持 [**协议**](https://realpython.com/python-type-checking/#duck-types-and-protocols) 已经有一段时间了。然而,[官方验收](https://mail.python.org/archives/list/typing-sig@python.org/message/FDO4KFYWYQEP3U2HVVBEBR3SXPHQSHYR/)却发生在 2019 年 5 月。
协议是一种形式化 Python duck 类型支持的方式:
> 当我看到一只像鸭子一样走路像鸭子一样游泳像鸭子一样嘎嘎叫的鸟时我就把那只鸟叫做鸭子([来源](https://en.wikipedia.org/wiki/Duck_test#History))
例如Duck typing 允许您读取任何具有`.name`属性的对象上的`.name`而不必真正关心对象的类型打字系统支持这一点似乎有悖常理通过[结构分型](https://en.wikipedia.org/wiki/Structural_type_system)还是有可能搞清楚鸭子分型的
例如您可以定义一个名为`Named`的协议该协议可以识别具有`.name`属性的所有对象:
```
from typing import Protocol
class Named(Protocol):
name: str
def greet(obj: Named) -> None:
print(f"Hi {obj.name}")
```py
这里`greet()`接受任何对象只要它定义了一个`.name`属性有关协议的更多信息请参见 [PEP 544](https://www.python.org/dev/peps/pep-0544/) [Mypy 文档](https://mypy.readthedocs.io/en/latest/protocols.html)
[*Remove ads*](/account/join/)
## 使用 f 弦进行更简单的调试
f 弦是在 Python 3.6 中引入的并且变得非常流行这可能是 Python 库仅在 3.6 版及更高版本中受支持的最常见原因f 字符串是格式化的字符串文字你可以通过主角`f`认出来:
>>>
```
>>> style = "formatted"
>>> f"This is a {style} string"
'This is a formatted string'
```py
当你使用 f 字符串时你可以用花括号把变量甚至表达式括起来然后它们将在运行时被计算并包含在字符串中一个 f 字符串中可以有多个表达式:
>>>
```
>>> import math
>>> r = 3.6
>>> f"A circle with radius {r} has area {math.pi * r * r:.2f}"
'A circle with radius 3.6 has area 40.72'
```py
在最后一个表达式`{math.pi * r * r:.2f}`还使用了格式说明符格式说明符用冒号与表达式分开
`.2f`表示该区域被格式化为具有 2 位小数的浮点数格式说明符同 [`.format()`](https://docs.python.org/library/stdtypes.html#str.format) 。参见[官方文档](https://docs.python.org/library/string.html#format-specification-mini-language)获得允许格式说明符的完整列表。
Python 3.8 可以在 f 字符串中使用赋值表达式只需确保用括号将赋值表达式括起来:
>>>
```
>>> import math
>>> r = 3.8
>>> f"Diameter {(diam := 2 * r)} gives circumference {math.pi * diam:.2f}"
'Diameter 7.6 gives circumference 23.88'
```py
然而Python 3.8 中真正的新闻是新的调试说明符您现在可以在表达式的末尾添加`=`它将打印表达式及其值:
>>>
```
>>> python = 3.8
>>> f"{python=}"
'python=3.8'
```py
这是一个简写通常在交互工作或添加打印语句来调试脚本时最有用 Python 的早期版本中您需要两次拼出变量或表达式才能获得相同的信息:
>>>
```
>>> python = 3.7
>>> f"python={python}"
'python=3.7'
```py
您可以在`=`周围添加空格并照常使用格式说明符:
>>>
```
>>> name = "Eric"
>>> f"{name = }"
"name = 'Eric'"
>>> f"{name = :>10}"
'name = Eric'
```py
`>10`格式说明符指出`name`应该在 10 个字符串内右对齐。`=`也适用于更复杂的表达式:
>>>
```
>>> f"{name.upper()[::-1] = }"
"name.upper()[::-1] = 'CIRE'"
```py
有关 f 字符串的更多信息请参见 [Python 3 f 字符串:改进的字符串格式化语法(指南)](https://realpython.com/python-f-strings/)
## Python 指导委员会
从技术上来说 [Python **治理**](https://www.python.org/dev/peps/pep-0013/) 并不是语言特性然而Python 3.8 是第一个不是在**仁慈的独裁统治**[吉多··罗苏姆](https://gvanrossum.github.io/)下开发的版本Python 语言现在由五个核心开发者组成的**指导委员会**管理:
* [巴里华沙](https://twitter.com/pumpichank)
* 布雷特·卡农
* [卡罗尔心甘情愿](https://twitter.com/WillingCarol)
* [圭多·范罗斯](https://twitter.com/gvanrossum)
* 尼克·科格兰
Python 的新治理模型之路是自组织中一项有趣的研究吉多··罗苏姆在 20 世纪 90 年代初创造了 Python并被亲切地称为 Python [**BDFL**](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)**这些年来越来越多关于 Python 语言的决定是通过 [**Python 增强提案** (PEPs)](https://www.python.org/dev/peps/pep-0001/) 做出的尽管如此Guido 还是对任何新的语言特性拥有最终决定权*
*在关于[任务表达](#the-walrus-in-the-room-assignment-expressions)的漫长讨论之后,圭多[于 2018 年 7 月宣布](https://mail.python.org/pipermail/python-committers/2018-July/005664.html)他将从 BDFL 的角色中退休(这次是真正的)。他故意没有指定继任者。相反,他要求核心开发人员团队找出 Python 今后应该如何治理。
幸运的是PEP 流程已经很好地建立起来了所以使用 PEP 来讨论和决定新的治理模型是很自然的 2018 年秋季[提出了几种模式](https://www.python.org/dev/peps/pep-8000/)包括[选举新的 BDFL](https://www.python.org/dev/peps/pep-8010/) (更名为亲切的裁判影响决策官:圭多)或者转向基于共识和投票的[社区模式](https://www.python.org/dev/peps/pep-8012/)没有集中的领导2018 12 [指导委员会型号](https://www.python.org/dev/peps/pep-8016/)在核心开发者中投票选出
[![The Python Steering Council at PyCon 2019](img/d7c233a4afe1c85ed9cf90a885c7cbf6.png)](https://files.realpython.com/media/steering_council.1aae31a91dad.jpg)
<figcaption class="figure-caption text-center">The Python Steering Council at PyCon 2019\. From left to right: Barry Warsaw, Brett Cannon, Carol Willing, Guido van Rossum, and Nick Coghlan (Image: Geir Arne Hjelle)</figcaption>
指导委员会由 Python 社区的五名成员组成如上所列 Python 的每一个主要版本发布后都会选举一个新的指导委员会换句话说Python 3.8 发布后会有一次选举
虽然这是一次公开选举但预计首届指导委员会的大部分成员(如果不是全部的话)将会改选指导委员会拥有广泛的权力来决定 Python 语言但是应该尽可能少的行使这些权力
你可以在 [PEP 13](https://www.python.org/dev/peps/pep-0013/) 中阅读关于新治理模式的所有信息而决定新模式的过程在 [PEP 8000](https://www.python.org/dev/peps/pep-8000/) 中描述欲了解更多信息请参见 [PyCon 2019 主题演讲](https://pyvideo.org/pycon-us-2019/python-steering-council-keynote-pycon-2019.html)并聆听 Brett Cannon [与我谈论 Python](https://talkpython.fm/episodes/show/209/inside-python-s-new-governance-model)[Changelog 播客](https://changelog.com/podcast/348)上的演讲你可以在 [GitHub](https://github.com/python/steering-council) 上关注指导委员会的更新
[*Remove ads*](/account/join/)
## 其他非常酷的功能
到目前为止您已经看到了关于 Python 3.8 新特性的头条新闻然而还有许多其他的变化也很酷在本节中您将快速浏览其中一些
### `importlib.metadata`
Python 3.8 中的标准库中新增了一个模块: [`importlib.metadata`](https://importlib-metadata.readthedocs.io) 通过此模块您可以访问 Python 安装中已安装包的相关信息与它的同伴模块[`importlib.resources`](https://realpython.com/python37-new-features/#importing-data-files-with-importlibresources)`importlib.metadata`一起,改进了老款 [`pkg_resources`](https://setuptools.readthedocs.io/en/latest/pkg_resources.html) 的功能。
举个例子你可以得到一些关于 [`pip`](https://realpython.com/courses/what-is-pip/) 的信息:
>>>
```
>>> from importlib import metadata
>>> metadata.version("pip")
'19.2.3'
>>> pip_metadata = metadata.metadata("pip")
>>> list(pip_metadata)
['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author',
'Author-email', 'License', 'Keywords', 'Platform', 'Classifier',
'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier',
'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier',
'Classifier', 'Classifier', 'Requires-Python']
>>> pip_metadata["Home-page"]
'https://pip.pypa.io/'
>>> pip_metadata["Requires-Python"]
'>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*'
>>> len(metadata.files("pip"))
668
```py
目前安装的`pip`版本是 19.2.3。`metadata()`提供您在 [PyPI](https://pypi.org/project/pip/) 上看到的大部分信息例如你可以看到这个版本的`pip`需要 Python 2.7或者 Python 3.5 或更高版本使用`files()`您将获得组成`pip`包的所有文件的列表在这种情况下有将近 700 个文件
`files()`返回一个 [`Path`](https://realpython.com/python-pathlib/) 对象的[列表](https://realpython.com/python-lists-tuples/)这些给了你一个方便的方法来查看一个包的源代码使用`read_text()`。以下示例从 [`realpython-reader`](https://pypi.org/project/realpython-reader/) 包中打印出`__init__.py`:
>>>
```
>>> [p for p in metadata.files("realpython-reader") if p.suffix == ".py"]
[PackagePath('reader/__init__.py'), PackagePath('reader/__main__.py'),
PackagePath('reader/feed.py'), PackagePath('reader/viewer.py')]
>>> init_path = _[0] # Underscore access last returned value in the REPL
>>> print(init_path.read_text()) """Real Python feed reader
Import the `feed` module to work with the Real Python feed:
>>> from reader import feed
>>> feed.get_titles()
['Logging in Python', 'The Best Python Books', ...]
See https://github.com/realpython/reader/ for more information
"""
# Version of realpython-reader package
__version__ = "1.0.0"
...
```py
您还可以访问软件包相关性:
>>>
```
>>> metadata.requires("realpython-reader")
['feedparser', 'html2text', 'importlib-resources', 'typing']
```py
列出一个包的依赖关系您可以看到`realpython-reader`在后台使用 [`feedparser`](https://pypi.org/project/feedparser/) 来读取和解析文章提要
PyPI 上有一个对早期版本 Python 有效的`importlib.metadata` [的反向移植您可以使用`pip`安装它:](https://pypi.org/project/importlib-metadata/)
```
$ python -m pip install importlib-metadata
```py
您可以在代码中使用 PyPI 反向端口如下所示:
```
try:
from importlib import metadata
except ImportError:
import importlib_metadata as metadata
...
```py
有关`importlib.metadata`的更多信息请参见[文档](https://importlib-metadata.readthedocs.io)
### 新的和改进的`math`和`statistics`功能
Python 3.8 对现有的标准库包和模块进行了许多改进。`math`在标准库中有一些新的功能。`math.prod()`的工作方式与内置的`sum()`类似但对于乘法运算:
>>>
```
>>> import math
>>> math.prod((2, 8, 7, 7))
784
>>> 2 * 8 * 7 * 7
784
```py
这两种说法是等价的当你已经将因子存储在一个 iterable 中时将会更容易使用
另一个新功能是`math.isqrt()`。可以用`isqrt()`[平方根](https://realpython.com/python-square-root-function/)的整数部分:
>>>
```
>>> import math
>>> math.isqrt(9)
3
>>> math.sqrt(9)
3.0
>>> math.isqrt(15)
3
>>> math.sqrt(15)
3.872983346207417
```py
9 的平方根是 3可以看到`isqrt()`返回一个整数结果 [`math.sqrt()`](https://realpython.com/python-square-root-function/) 总是返回一个`float`。15 的平方根差不多是 3.9请注意`isqrt()` [将答案截断到下一个整数](https://realpython.com/python-rounding/#truncation),在本例中为 3。
最后你现在可以更容易地使用标准库中的 *n* 维点和向量`math.dist()`可以求出两点之间的距离`math.hypot()`可以求出一个矢量的长度:
>>>
```
>>> import math
>>> point_1 = (16, 25, 20)
>>> point_2 = (8, 15, 14)
>>> math.dist(point_1, point_2)
14.142135623730951
>>> math.hypot(*point_1)
35.79106033634656
>>> math.hypot(*point_2)
22.02271554554524
```py
这使得使用标准库处理点和向量变得更加容易然而如果你要对点或向量做很多计算你应该检查一下 [NumPy](https://realpython.com/numpy-array-programming/)
`statistics`模块还有几个新功能:
* [`statistics.fmean()`](https://docs.python.org/3.8/library/statistics.html#statistics.fmean) 计算`float`数字的平均值。
* [`statistics.geometric_mean()`](https://docs.python.org/3.8/library/statistics.html#statistics.geometric_mean) 计算`float`个数字的几何平均值。
* [`statistics.multimode()`](https://docs.python.org/3.8/library/statistics.html#statistics.multimode) 查找序列中出现频率最高的值。
* [`statistics.quantiles()`](https://docs.python.org/3.8/library/statistics.html#statistics.quantiles) 计算分割点,将数据等概率分割成 *n 个*连续区间。
以下示例显示了正在使用的函数:
>>>
```
>>> import statistics
>>> data = [9, 3, 2, 1, 1, 2, 7, 9]
>>> statistics.fmean(data)
4.25
>>> statistics.geometric_mean(data)
3.013668912157617
>>> statistics.multimode(data)
[9, 2, 1]
>>> statistics.quantiles(data, n=4)
[1.25, 2.5, 8.5]
```py
Python 3.8 有一个新的 [`statistics.NormalDist`](https://docs.python.org/3.8/library/statistics.html#statistics.NormalDist) 类,使得[使用高斯正态分布](https://docs.python.org/3.8/library/statistics.html#normaldist-examples-and-recipes)更加方便。
要看使用`NormalDist`的例子可以试着比较一下新`statistics.fmean()`和传统`statistics.mean()`的速度:
>>>
```
>>> import random
>>> import statistics
>>> from timeit import timeit
>>> # Create 10,000 random numbers
>>> data = [random.random() for _ in range(10_000)]
>>> # Measure the time it takes to run mean() and fmean()
>>> t_mean = [timeit("statistics.mean(data)", number=100, globals=globals())
... for _ in range(30)]
>>> t_fmean = [timeit("statistics.fmean(data)", number=100, globals=globals())
... for _ in range(30)]
>>> # Create NormalDist objects based on the sampled timings
>>> n_mean = statistics.NormalDist.from_samples(t_mean)
>>> n_fmean = statistics.NormalDist.from_samples(t_fmean)
>>> # Look at sample mean and standard deviation
>>> n_mean.mean, n_mean.stdev
(0.825690647733245, 0.07788573997674526)
>>> n_fmean.mean, n_fmean.stdev
(0.010488564966666065, 0.0008572332785645231)
>>> # Calculate the lower 1 percentile of mean
>>> n_mean.quantiles(n=100)[0]
0.6445013221202459
```py
在这个例子中您使用 [`timeit`](https://docs.python.org/library/timeit.html) 来测量`mean()``fmean()`的执行时间为了获得可靠的结果您让`timeit`执行每个函数 100 并为每个函数收集 30 个这样的时间样本基于这些样本你创建两个`NormalDist`对象注意如果您自己运行代码可能需要一分钟来收集不同的时间样本
`NormalDist`有很多方便的属性和方法完整列表见[文档](https://docs.python.org/3.8/library/statistics.html#normaldist-objects)。考察`.mean`和`.stdev`,你看到老款`statistics.mean()`跑 0.826±0.078 秒,新款`statistics.fmean()`花 0.0105±0.0009 秒。换句话说,`fmean()`对于这些数据来说大约快了 80 倍。
如果您需要 Python 中比标准库提供的更高级的统计请查看 [`statsmodels`](https://www.statsmodels.org/) [`scipy.stats`](https://docs.scipy.org/doc/scipy/reference/tutorial/stats.html)
[*Remove ads*](/account/join/)
### 关于危险语法的警告
Python 有一个 [`SyntaxWarning`](https://docs.python.org/3/library/exceptions.html#SyntaxWarning) ,它可以警告可疑的语法,这通常不是一个 [`SyntaxError`](https://realpython.com/invalid-syntax-python/) 。Python 3.8 增加了一些新功能,可以在编码和调试过程中帮助你。
`is``==`的区别可能会让人混淆后者检查值是否相等而只有当对象相同时`is`才是`True`。Python 3.8 将试图警告你应该使用`==`而不是`is`的情况:
>>>
```
>>> # Python 3.7
>>> version = "3.7"
>>> version is "3.7"
False
>>> # Python 3.8
>>> version = "3.8"
>>> version is "3.8"
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="? False
>>> version == "3.8"
True
```py
当你写一个很长的列表时很容易漏掉一个逗号尤其是当它是垂直格式的时候忘记元组列表中的逗号会给出一个混乱的错误消息说明元组不可调用Python 3.8 还发出了一个警告指出了真正的问题:
>>>
```
>>> [
... (1, 3)
... (2, 4)
... ]
<stdin>:2: SyntaxWarning: 'tuple' object is not callable; perhaps
you missed a comma? Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: 'tuple' object is not callable
```py
该警告正确地将丢失的逗号识别为真正的原因
### 优化
Python 3.8 进行了多项优化一些能让代码运行得更快其他的可以减少内存占用例如 Python 3.7 相比Python 3.8 [`namedtuple`](https://realpython.com/python-namedtuple/) 中查找字段要快得多:
>>>
```
>>> import collections
>>> from timeit import timeit
>>> Person = collections.namedtuple("Person", "name twitter")
>>> raymond = Person("Raymond", "@raymondh")
>>> # Python 3.7
>>> timeit("raymond.twitter", globals=globals())
0.05876131607996285
>>> # Python 3.8
>>> timeit("raymond.twitter", globals=globals())
0.0377705999400132
```py
你可以看到在 Python 3.8 `namedtuple`上查找`.twitter`要快 30-40%当列表从已知长度的 iterables 初始化时可以节省一些空间这可以节省内存:
>>>
```
>>> import sys
>>> # Python 3.7
>>> sys.getsizeof(list(range(20191014)))
181719232
>>> # Python 3.8
>>> sys.getsizeof(list(range(20191014)))
161528168
```
在这种情况下Python 3.8 中的列表使用的内存比 Python 3.7 少 11%。
其他优化包括 [`subprocess`](https://docs.python.org/library/subprocess.html) 更好的性能、 [`shutil`](https://docs.python.org/library/shutil.html) 更快的文件复制、 [`pickle`](https://realpython.com/python-pickle-module/) 更好的默认性能、更快的 [`operator.itemgetter`](https://docs.python.org/library/operator.html#operator.itemgetter) 操作。有关优化的完整列表,请参见[官方文档](https://docs.python.org/3.8/whatsnew/3.8.html#optimizations)。
## 那么,应该升级到 Python 3.8 吗?
先说简单的答案。如果您想尝试这里看到的任何新特性,那么您确实需要能够使用 Python 3.8。像 [`pyenv`](https://realpython.com/intro-to-pyenv/) 和 [Anaconda](https://realpython.com/python-windows-machine-learning-setup/#introducing-anaconda-and-conda) 这样的工具使得并排安装几个版本的 Python 变得很容易。或者,可以运行[官方 Python 3.8 Docker 容器](https://hub.docker.com/_/python/)。亲自尝试 Python 3.8 没有任何坏处。
现在,对于更复杂的问题。您是否应该将生产环境升级到 Python 3.8?您是否应该让自己的项目依赖于 Python 3.8 来利用这些新特性?
在 Python 3.8 中运行 Python 3.7 代码应该没什么问题。因此,升级您的环境以运行 Python 3.8 是非常安全的,并且您将能够利用新版本中的[优化](#optimizations)。Python 3.8 的不同测试版本已经发布了好几个月了,所以希望大多数错误已经被解决了。然而,如果你想保守一点,你可以坚持到第一个维护版本(Python 3.8.1)发布。
一旦您升级了您的环境,您就可以开始尝试 Python 3.8 中才有的特性,比如[赋值表达式](#the-walrus-in-the-room-assignment-expressions)和[仅位置参数](#positional-only-arguments)。但是,您应该注意其他人是否依赖您的代码,因为这将迫使他们也升级他们的环境。流行的库可能会在相当长的一段时间内至少支持 Python 3.6。
有关为 Python 3.8 准备代码的更多信息,请参见[移植到 Python 3.8](https://docs.python.org/3.8/whatsnew/3.8.html#porting-to-python-3-8) 。
*立即观看**本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解:[**Python 3.8 中很酷的新特性**](/courses/cool-new-features-python-38/)*********