Py学习  »  Python

@程序员:Python 3.8正式发布,重要新功能都在这里

AI科技大本营 • 4 年前 • 240 次点击  


整理 | Jane、夕颜
出品 | AI科技大本营(ID:rgznai100)
 
【导读】最新版本的Python发布了!今年夏天,Python 3.8发布beta版本,但在2019年10月14日,第一个正式版本已准备就绪。现在,我们都可以开始使用新功能并从最新改进中受益。

Python3.8有哪些你要关注的新内容?

 
Python3.8 都有哪些新功能,在文档手册中,大家可以有一个概览。这么多新内容,哪些是大家最先要关注一下的呢?下面,营长就带大家从深度和广度两方面,了解那些最大的变化,帮助大家快速上手 Python3.8.


新功能手册: 
https://docs.python.org/3.8/whatsnew/3.8.html
 
在本文中,你将了解到Python 3.8如何:
 
  • 使用赋值表达式简化一些代码结构
  • 在你自己的函数中强制执行仅位置参数
  • 指定更精确的类型提示
  • 使用f字符串进行更简单的调试

除了少数例外,Python 3.8对早期版本进行了许多小的改进。在本文结尾处,你将看到许多这些不太引人注意的更改,并讨论了一些使Python 3.8比其先前版本更快的优化。最后,你还会获得一些有关升级到新版本的建议。

一、赋值表达式(Assignment expressions)


引入赋值表达式,可以说是Python3.8 中最大的一个变化了。注意,现在已经用新的符号了(:=),形似海象侧牙,也被称为“海象运算符”。赋值表达式可以在统一表达式中赋值并返回值,比如下面的代码,执行给变量分配值,并打印这个值
>>> walrus = False>>> print(walrus)False

Python3.8中,可以使用 walrus 运算符将上面两个语句合并为一句
>>> print(walrus := True)True

赋值表达式可以把 True 分配给 walrus,并直接 print 这个值。一定要有(:= ),不然表达式也是无法正常执行的,有了新的赋值表达式符号,不仅在构造上更简便,有时也可以更清楚的传达代码意图。
 
比如,在while循环中,就体现了(:= )的优势
inputs = list()current = input("Write something: ")while current != "quit":    inputs.append(current)    current = input("Write something: ")

上面的这段代码并不够优秀,需要不断重复 input 语句,并且需要以某种方式加到 current 列表中,然后在执行后面的代码,更好的解决方案是设置一个无限 while 循环,然后用 break停止循环
inputs = list()


    
while True:    current = input("Write something: ")    if current == "quit":        break    inputs.append(current)

这段代码与上面的代码是等效的,不过,如果使用赋值表达式,还可以再进一步简化这段循环:
inputs = list()while (current := input("Write something: ")) != "quit":    inputs.append(current)

现在的代码虽然更简化了,但是可读性就变差了,所以,大家要使用赋值表达式的方法还需要结合自身进行判断。
PEP572中描述了复制表达式的所有细节,大家可以深入阅读。
https://www.python.org/dev/peps/pep-0572/ https://www.python.org/dev/peps/pep-0572/#examples

仅位置参数(Positional-Only Arguments)

 
内置函数 float()可用于将文本字符串和数字类型转换成 float 对象,如下面的代码
>>> 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 -- Notation For Positional-Only Parameters
https://www.python.org/dev/peps/pep-0457/
 
事实证明,虽然float() 调用了参数 x,但并不允许使用其名称
>>> float(x="3.8")Traceback (most recent call last):  File "", line 1, in <module>TypeError: float() takes no keyword arguments

使用 float() 时,只允许按位置指定参数,而不能使用关键字参数。Python3.8 之前,这类仅位置参数只适用于内置参数,在我们自己定义的函数中,没有简单的方法指定参数为仅位置参数。
>>> def incr(x):...     return x + 1...>>> incr(3.8)4.8
>>> incr(x=3.8)4.8

上面这段代码使用了 *args,模拟了仅位置参数,但是不够灵活,不易读,而在 Python3.8 中,可以用 / 来表示必须通过仅位置参数之前的参数,可以重写incr()接收位置参数:
>>> def incr(x, /):...     return x + 1...>>> incr(3.8)4.8
>>> incr(x=3.8)Traceback (most recent call last): File "", line 1, in TypeError: incr() got some positional-only arguments passed as keyword arguments: 'x'

通过在 x 之后加入 /,就可以指定 x 为 仅位置参数。常规参数与仅位置参数结合使用,可将常规参数放在 / 之后:
 
>>> 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 "", line 1, in TypeError: greet() got some positional-only arguments passed as           keyword arguments: 'name'

greet() 中,/ 放在 name 和 greeting 之间,表示 name 是仅位置参数,greeting 是可以通过位置或关键字传递的常规参数。
 
大家可能觉得仅位置参数的可读性似乎并不好,但是使用后会发现,很多情况下,只有仅位置参数可以优化我们的代码。此外,使用仅位置函数还有一个好处,可以更轻松地重构函数,更改函数的名称时,不必担心给其他代码带来的影响。仅位置函数还很好的补充了仅关键字参数,可以使用 * 指定仅关键字参数:
 
>>> def to_fahrenheit(*, celsius):...     return 32 + celsius * 9 / 5...>>> to_fahrenheit(40)Traceback (most recent call last):  File "", line 1, in <module>TypeError: to_fahrenheit() takes 0 positional arguments but 1 was given
>>> to_fahrenheit(celsius=40)104.0

上段代码中,celsius 是仅关键字参数。
 
还可以通过按 / 和分隔的顺序组合仅位置、常规和仅关键字参数 *,例如下段代码中,text 是仅位置参数,border 是常规参数(值为默认值),并且 width 是仅关键字参数(值为默认值):
>>> def headline(text, /, border="♦", *, width=50):...     return f" {text} ".center(width, border)...

text 是仅位置参数,因此不能使用关键字 text:



    
>>> headline("Positional-only Arguments")'♦♦♦♦♦♦♦♦♦♦♦ Positional-only Arguments ♦♦♦♦♦♦♦♦♦♦♦♦'
>>> headline(text="This doesn't work!")Traceback (most recent call last): File "", line 1, in <module>TypeError: headline() got some positional-only arguments passed as keyword arguments: 'text'

border 既可以使用关键字,也可以不使用关键字指定:
>>> 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 "", line 1, in <module>TypeError: headline() takes from 1 to 2 positional arguments           but 3 were given
关于仅位置参数的内容可以阅读 PEP 570 文档:https://www.python.org/dev/peps/pep-0570/
 

更多详细类型

 
此时,Python的类型系统已经相当成熟。但是,在Python 3.8中,键入中添加了一些新功能,以允许进行更精确的键入:

  • 文字类型
  • 打字字典
  • 最终对象
  • 协定

Python支持可选的类型提示,通常作为代码上的注释:
def double(number: float) -> float:    return 2 * number

在此示例中,数字应该是浮点数,并且double()函数也应该返回浮点数。但是,Python将这些注释视为提示。它们不会在运行时强制执行:
>>> double(3.14)6.28
>>> double("I'm not a float")"I'm not a floatI'm not a float"

double()将“我不是浮点数”作为参数,即使那不是浮点数。有些库可以在运行时使用类型,但这并不是Python类型系统的主要用例。
 
相反,类型提示允许静态类型检查器对Python代码进行类型检查,而无需实际运行脚本。这让人想起Java,Rust和Crystal等其他语言会出现的编译器捕获类型错误。此外,类型提示可作为代码的文档,使其更易于阅读,并改善了IDE中的自动完成功能。
 
注意:有几种可用的静态类型检查器,包括Pyright,Pytype和Pyre。本文中使用Mypy。你可以使用pip从PyPI安装Mypy:
$ python -m pip mypy
 
从某种意义上说,Mypy是Python类型检查器的参考实现,并在Jukka Lehtasalo的领导下由Dropbox开发。Python的创建者Guido van Rossum是Mypy团队的成员。
 
你可以在原始PEP 484和Python类型检查(指南)中找到有关类型提示的更多信息。
PEP 484
https://www.python.org/dev/peps/pep-0484/
 
Python 3.8已接受并包含四个有关类型检查的新PEP,每个都有简短示例。
 
PEP 586引入了文字类型。文字类型有点特殊,它代表一个或多个特定值。文字类型的一种用例是,当使用字符串参数描述特定行为时,能够精确地添加类型。以下为示例:
# 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")


该程序将通过静态类型检查器,即使“向上”是无效方向。类型检查器仅检查“ up”是否为字符串。在这种情况下,更准确地说方向必须是文字字符串“水平”或文字字符串“垂直”。使用文字类型,你可以完全做到这一点: 
# 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")
 
因为可以将方向的允许值暴露给类型检查器,你现在可以得到有关错误的警告:
$ mypy draw_line.pydraw_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)

基本语法是Literal []。例如,Literal [38]代表文字值38。你可以使用Union表示多个文字值之一:
Union[Literal["horizontal"], Literal["vertical"]]
 
由于这是一个相当普遍的用例,因此你可以(并且应该)使用更简单的表示法Literal [“ horizontal”,“ vertical”]]。将类型添加到draw_line()时,你已经使用了后者。如果仔细查看上面Mypy的输出,你会发现它在内部将较简单的表示法转换为Union表示法。
 
在某些情况下,函数的返回值的类型取决于输入参数。一个示例是open(),它可以根据mode的值返回文本字符串或字节数组。这可以通过重载来处理。
 
以下示例表示计算器的流程,该计算器可以将答案返回为正数(38)或罗马数字(XXXVIII):
# calculator.pyfrom 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


 
该代码具有正确的类型提示: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)

@overloaddef add(num_1: int, num_2: int, to_roman: Literal[True]) -> str: ...@overloaddef 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

添加的@overload签名将帮助你的类型检查器根据to_roman的文字值来推断str或int。请注意,省略号(...)是代码的文字部分。它们在重载签名中代表功能主体。
 
作为对Literal的补充,PEP 591引入了Final。该限定符规定不应重新分配、重新定义或覆盖变量或属性。以下是输入错误:
from typing import Final
ID: Final = 1
...
ID += 1

Mypy将突出显示行ID + = 1,并请注意你无法将其分配给最终名称“ ID”。这可以确保代码中的常量值永远不变。
 
此外,还有一个@final装饰器,可以将其应用于类和方法。用@final装饰的类不能被子类化,而@final方法不能被子类覆盖:
from typing import final
@finalclass Base: ...
class Sub(Base): ..

Mypy将使用无法从最终类“ Base”继承”来的错误消息标记此示例。要了解有关Final和@final的更多信息,请参阅PEP 591。
 
支持更具体类型提示的第三个PEP是PEP 589,它引入了TypedDict。可以使用类似于类型化NamedTuple的符号来指定dictionaries 中键和值的类型。
 
传统上,dictionaries 是使用Dict注释的。问题在于,这仅允许一种类型键和一种类型值,通常导致诸如Dict [str,Any]这样的注释。例如,一个注册Python版本信息的dictionaries :
py38 = {"version": "3.8", "release_year": 2019}
 
与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

然后,类型检查器将能够推断出py38 [“ version”]的类型为str,而py38 [“ release_year”]是一个int值。在运行时,TypedDict是常规dict,并且照常忽略类型提示。你也可以将TypedDict纯粹用作注释:



    
py38: PythonVersion = {"version": "3.8", "release_year": 2019}
 
Mypy会告知你任何值的类型错误,或者你使用的是尚未声明的键。更多示例请参见PEP 589。
 
Mypy已经支持协议已有一段时间了。但是,2019年5月才正式官方支持。
 
协议是一种规范Python对鸭子类型支持的方式: 
当我看到一只鸟走路像鸭子,游泳像鸭子,像鸭子一样嘎嘎叫时,我把它称为鸭子。

鸭式类型让你可以,比如在具有.name属性的任何对象上读取.name,而无需真正关心对象的类型。支持类型系统似乎违反直觉。通过结构子类型转化,仍然有可能了解鸭子的类型。
 
例如,你可以定义一个名为Named的协议,该协议可以标识具有.name属性的所有对象:
 
from typing import Protocol
class Named(Protocol): name: str
def greet(obj: Named) -> None: print(f"Hi {obj.name}"

这里,greet()可以接受任何对象,只要它定义了.name属性即可。有关协议的更多信息,请参见PEP 544和Mypy文档。
 
使用f字符串进行更简单的调试
 
f字符串是在Python 3.6中引入的,已经非常流行。它们可能是Python库仅在3.6版及更高版本上受支持的最常见原因。f字符串是格式化的字符串文字。你可以通过前导f识别它:
>>>>>> style = "formatted">>> f"This is a {style} string"'This is a formatted string'

使用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'

在最后一个表达式{math.pi * r * r:.2f}中,还使用了格式说明符。格式说明符与表达式之间用冒号分隔。
 
.2f表示该区域被格式化为带有2个小数的浮点数。格式说明符与.format()相同。有关支持的格式说明符完整列表,请参见官方文档。
官方文档 
https://docs.python.org/3/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'

但是,Python 3.8中真正的f-news是新的调试说明符。现在,你可以在表达式的末尾添加=,它将同时打印表达式及其值:
>>> python = 3.8>>> f"{python=}"'python=3.8'

这是种简单的方法,通常在交互式工作或添加打印语句来调试脚本时最为有用。在早期版本的Python中,你需要对变量或表达式进行两次拼写才能获得相同的信息:
>>> python = 3.7>>> f"python={python}"'python=3.7'

你可以在=周围添加空格,并照常使用格式说明符:
>>> name = "Eric">>> f"{name = }""name = 'Eric'"
>>> f"{name = :>10}"'name = Eric'
 
> 10的格式说明符表示名称应在10个字符串内右对齐。=也适用于更复杂的表达式:
>>> f"{name.upper()[::-1] = }""name.upper()[::-1] = 'CIRE'"
 

指导委员会模式(The Python Steering Council)


从技术上讲,Python的管理并不是一项语言功能。但是,Python 3.8是首个不是在Guido van Rossum的仁慈独裁统治下开发的Python版本。Python语言现在由一个由五个核心开发人员组成的指导委员会管理:

  • Barry Warsaw
  • Brett Cannon
  • Carol Willing
  • Guido van Rossum
  • Nick Coghlan

通往Python新治理模型的道路是自组织方面的一次有趣的研究。吉多·范·罗苏姆(Guido van Rossum)在1990年代初创建了Python,并被亲切地称为Python的仁慈独裁者(BDFL)。多年来,Python增强建议书(PEP)越来越多地参与了关于Python语言的决策。尽管如此,Guido仍在所有新语言功能上都拥有最终决定权。
 
在对赋值表达式进行了漫长的讨论之后,Guido在2018年7月宣布退出BDFL职位(这次是真的)。他故意没有指定继任者。相反,他要求核心开发人员团队弄清楚今后应该如何管理Python。
 
幸运的是,PEP流程已经很完善,因此使用PEP讨论并决定新的治理模型顺理成章。2018年秋季,PEP提出了几种模式,包括选举新的BDFL(更名为GUIDO),或者是放弃集中领导,转向基于共识和投票的社区模式。2018年12月,核心开发人员投票选择了指导委员会的模式。
  

 PyCon 2019上的Python指导委员会。从左至右:Barry Warsaw,Brett Cannon,Carol Willing,Guido van Rossum和Nick Coghlan(图片来源:Geir Arne Hjelle)

 
指导委员会由上图中Python社区的五名成员组成。在每个主要的Python版本发布之后,将选举一个新的指导委员会。换句话说,Python 3.8发行后将进行一次选举。
 
尽管这是一次公开选举,但预计大多数(甚至全部)老一届指导委员会的成员将再次当选。指导委员会具有决定宽泛的Python语言决定权力,但应尽可能少地行使这些权力。
 
你可以在PEP 13中阅读有关新治理模式的全部信息,在PEP 8000中可以看到确定新模式的过程。有关更多信息,请参阅PyCon 2019主题演讲,并在Brett Cannon和Talk Python To Me和Changelog播客,并在GitHub上关注指导委员会更新信息。

其他酷炫的新功能

Python 3.8还有许多其他变化也很酷炫。


  • importlib.metadata


Python 3.8的标准库中提供了一个新模块:importlib.metadata。通过此模块,你可以访问有关Python安装中已安装软件包的信息。与其配套的模块importlib.resources一起,importlib.metadata改进了旧pkg_resources的功能。
 
例如,你可以获得有关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
 
当前安装的pip版本是19.2.3。metadata()可以让你可以访问PyPI上看到的大多数信息。例如,你可以看到此版本的pip需要Python 2.7或Python 3.5或更高版本。使用files(),可以获得构成pip包的所有文件的清单。本例中约有700个文件。
 
files()返回Path对象的列表。你可以使用read_text()方便地查看软件包的源代码。以下示例从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"
...
 
您还可以访问包依赖关系:
>>> metadata.requires("realpython-reader")['feedparser', 'html2text', 'importlib-resources', 'typing']

require()列出软件包的依赖关系。可以看到,例如realpython-reader在后台使用feedparser来阅读和解析文章提要。
 
PyPI上有一个importlib.metadata的反向端口,该端口在Python的早期版本上也可以用。可以使用pip安装:
try:    from importlib import metadataexcept ImportError:    import importlib_metadata as metadata
...

  • 新增和改进的数学和统计功能

Python 3.8对现有的标准库软件包和模块进行了许多改进。标准库中的数学有了一些新功能。math.prod()与内置sum()类似,但对于乘法乘积:
 
>>> import math>>> math.prod((2, 8, 7, 7))784

>>> 2 * 8 * 7 * 7784

这两个语句是等效的。当你把因素存储在可迭代对象中时,prod()将更易于使用。
 
另一个新功能是math.isqrt()。可以使用isqrt()来找到平方根的整数部分:
 
>>> import math>>> math.isqrt(9)3>>> math.sqrt(9)3.0

>>> math.isqrt(15)3

>>> math.sqrt(15)3.8729833462074
 
9的平方根是3。你可以看到isqrt()返回整数结果,而math.sqrt()始终返回浮点数。15的平方根约等于3.9。请注意,本例中,isqrt()将答案截断为下一个整数。
 
最后,现在你可以更轻松地使用标准库中的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.022715545545
 
这使得使用标准库更容易处理点和向量。但是,如果要对点或向量进行许多计算,则应签出NumPy。
 
统计模块还具有几个新功能:

  • statistics.fmean()计算浮点数的平均值。
  • statistics.geometric_mean()计算浮点数的几何平均值。
  • statistics.multimode()查找序列中最频繁出现的值。
  • 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]
 
在Python 3.8中,有一个新的statistics.NormalDist类,这使得高斯正态分布更加方便。
 
要查看使用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.64450132212

在此示例中,使用timeit来衡量mean()和fmean()的执行时间。为了获得可靠的结果,你可以让timeit将每个函数执行100次,并为每个函数收集30个这样的时间样本。基于这些示例,你将创建两个NormalDist对象。请注意,如果自行运行代码,则可能需要一分钟的时间来收集不同的时间样本。
 
NormalDist具有许多方便的属性和方法,请参阅官方文档查看完整列表。检查.mean和.stdev,你会发现旧的statistics.mean()的运行时间为0.826±0.078秒,而新的statistics.fmean()则为0.0105±0.0009秒。换句话说,对于这些数据,fmean()的速度大约是前者的80倍。
 
  • 新增危险语法警告功能

Python有一个SyntaxWarning功能,可以警告不是SyntaxError的可疑语法。Python 3.8添加了一些新功能,可以在编码和调试过程中为你提供帮助。
 
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":1: SyntaxWarning: "is" with a literal. Did you mean "=="?False

>>> version == "3.8"True
 
写长列表时,尤其是垂直格式化时,很容易漏掉逗号。当忘记元组列表中的逗号时会发出让你不解的不可调用元组错误消息。Python 3.8不仅会发出警告,还会指出实际问题:
 
>>> [...   (1, 3)...   (2, 4)... ]:2: SyntaxWarning: 'tuple' object is not callable; perhaps           you missed a comma?Traceback (most recent call last):  File "", line 2, in <module>TypeError: 'tuple' object is not callable

该警告正确地将丢失的逗号标识为真正的罪魁祸首。


  • 优化


Python 3.8进行了一些优化,有的让代码运行得更快,有的优化减少了内存占用。例如,与Python 3.7相比,在Python 3.8中查找命名元组中的字段要快得多:
 
>>>>>> 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
 
可以看到,在Python 3.8中在namedtuple上查找.twitter的速度提高了30-40%。从具有已知长度的可迭代对象初始化列表时,可以节省一些空间。这样可以节省内存:
>>> 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%。
 
其他优化还包括子流程性能更高,带有shutil的文件复制速度更快,pickle中的默认性能提高以及operator.itemgetter操作更快。有关优化的完整列表,请参见官方文档。


所以,我们必须要更新到 Python3.8 吗?

 
如果你想尝鲜新功能,那是肯定要升级的。
 
实际产品的开发环境需要升级到 Python3.8 吗?首先,如果在 Python3.8 中运行 3.7 版本代码,问题应该不会很大;Python3.8 的beta版本也试用几个月了,也解决了不少问题,如果能升级到Python3.8,肯定也是安全的,还能在新版本中进行优化。
 
如果你想尝试一下Python3.8,可以阅读下的文档,以帮助更好的完成移植
https://docs.python.org/3.8/whatsnew/3.8.html#porting-to-python-3-8

还有不能遗漏官方文档:
https://www.python.org/downloads/release/python-380/


更多阅读:

https://realpython.com/python38-new-features/ https://docs.python.org/3.8/whatsnew/3.8.html

(*本文为 AI科技大本营编译文章,载请微信联系 1092722531


精彩推荐


2019 中国大数据技术大会(BDTC)再度来袭!豪华主席阵容及百位技术专家齐聚,15 场精选专题技术和行业论坛,超强干货+技术剖析+行业实践立体解读,深入解析热门技术在行业中的实践落地。

即日起,限量 5 折票开售,数量有限,扫码购买,先到先得!


推荐阅读


你点的每个“在看”,我都认真当成了AI

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/47154
 
240 次点击