Python 3.8 的超酷新功能

Python 3.8 的超酷新功能

在 Python中的最新版本发布 自夏季以来,Python 3.8已在beta版本中可用,但在 2019 10月14日, 第一个正式版本已准备就绪。 现在,我们所有人都可以开始使用新功能并从最新改进中受益。

Python 3.8带来了什么? 文档 很好地概述了新功能。 但是,本文将更深入地介绍一些最大的变化,并向您展示如何利用Python 3.8。

在本文中,您将了解:

  • 使用赋值表达式简化一些代码结构

  • 在自己的函数中强制仅位置参数

  • 指定更精确的类型提示

  • 使用f字符串进行更简单的调试

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

房间里的海象:赋值表达

Python 3.8中最大的变化是 赋值表达式 的引入 它们使用新的符号( := 编写 该运算符通常被称为 海象运算符, 因为它类似于海象的侧面的象牙和海象牙。

赋值表达式使您可以在同一表达式中赋值并返回一个值。 例如,如果要分配给变量并打印其值,则通常需要执行以下操作:

>>> walrus = False >>> print(walrus) False

在Python 3.8中,可以使用walrus运算符将这两个语句合并为一个:

>>> print(walrus := True) True

分配表达式使您可以分配 Truewalrus ,并立即打印该值。 但是请记住,如果 没有 它,海象运算符不会 做任何不可能的事情。 它只会使某些构造更加方便,并且有时可以更清楚地传达代码的意图。

一种显示海象运算符优势的模式是 while 循环,您需要在循环中初始化和更新变量。 例如,以下代码要求用户输入直到输入 quit

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)

这会将测试移回应有的 while 行。 但是,该行现在发生了几件事,因此需要花费更多的精力才能正确地阅读它。 对于海象运算符何时有助于使您的代码更具可读性,请做出最佳判断。

PEP 572 描述了赋值表达式的所有细节,包括将其引入语言的一些原理,以及 如何使用海象运算符的 几个示例 。

仅位置参数

内置函数 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-仅位置参数的符号 。

事实证明,虽然 float() 调用 了一个参数, x 但不允许使用其名称:

>>> 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之前,此类 仅位置 参数仅适用于内置函数。 没有简单的方法来指定参数在您自己的函数中应该仅位置:

>>> def incr(x): ...     return x + 1 ... >>> incr(3.8) 4.8  >>> incr(x=3.8) 4.8

这是可能的 模拟 位置-only参数 使用 *args ,但是这是不够灵活,不易阅读,并迫使你实现自己的参数解析。 在Python 3.8中,您可以 / 用来表示必须由位置指定之前的所有参数。 您可以重写 incr() 为仅接受位置参数:

>>> 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 为仅位置参数。 您可以将常规参数与仅位置参数结合使用,方法是将常规参数放在斜杠之后:

>>> 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中,都可以使用星号( * 指定仅关键字的参数 任何参数 * ,必须使用关键字来指定:

>>>

>>> 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 是具有 默认值 的仅关键字参数:

>>> 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 "<stdin>", 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", ":snake:", width=38) ':snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake: Python :snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake::snake:'  >>> headline("Python", ":snake:", 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

您可以在 PEP 570中 阅读有关仅位置参数的更多信息

更多精确类型

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

  • 文字类型

  • 打字字典

  • 最终对象

  • 通讯协定

Python支持可选的 类型提示 ,通常作为代码上的注释:

def double(number: float) -> float:     return 2 * number

在此示例中,您说 number 应为a  float ,并且 double() 函数也应返回a  float 但是,Python将这些注释视为 hints 它们不会在运行时强制执行:

>>> double(3.14) 6.28  >>> double("I'm not a float") "I'm not a floatI'm not a float"

double() "I'm not a float" 即使不是,也 愉快地接受 作为参数 float 有些 库可以在运行时使用类型 ,但这不是Python的类型系统的主要用例。

相反,类型提示允许 静态类型检查器 对Python代码进行类型检查,而无需实际运行脚本。 这让人想起编译器捕获其他语言(如 Java , Rust 和 Crystal)的 类型错误 此外,类型提示可作为代码的文档,使其更易于阅读,并 改善IDE中的自动完成功能 。

注意: 有几种可用的静态类型检查器,包括 Pyright , Pytype 和 Pyre 在本文中,您将使用 Mypy 您可以 使用 以下 方法从 PyPI 安装Mypy  pip

$ python -m pip install mypy

从某种意义上说,Mypy是Python类型检查器的参考实现,并  Jukka Lehtasalo的领导下由 Dropbox开发 Python的创建者Guido van Rossum是Mypy团队的成员。

您可以在 原始PEP 484 和 Python类型检查(指南)中 找到有关类型提示的更多信息

Python 3.8已接受并包含了四个有关类型检查的新PEP。 您将看到其中每个的简短示例。

PEP 586 介绍了该 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")

该程序将通过静态类型检查器,即使 "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")

通过将允许的值暴露 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)

基本语法为 Literal[<literal>] 例如, Literal[38] 表示文字值38。您可以使用来表示多个文字值之一 Union

Union[Literal["horizontal"], Literal["vertical"]]

由于这是一个相当普遍的用例,因此您可以(并且应该应该)使用更简单的表示法 Literal["horizontal", "vertical"] 在向中添加类型时,您已经使用了后者 draw_line() 如果您仔细查看上面Mypy的输出,您会发现它在 Union 内部 将较简单的表示法转换为 表示法。

在某些情况下,函数的返回值的类型取决于输入参数。 一个示例 open() 可能根据的值返回文本字符串或字节数组 mode 这可以通过 重载 来处理

以下示例显示了计算器的骨架,该计算器可以将答案返回为正数( 38 )或 罗马数字 ( 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

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

添加的 @overload 签名将帮助您的类型检查器进行推断 strint 根据的字面值 to_roman 注意,省略号( ... )是代码的文字部分。 它们在重载签名中代表功能主体。

作为补充 Literal , PEP 591 引入了 Final 该限定符指定不应重新分配,重新定义或覆盖变量或属性。 以下是输入错误:

from typing import Final  ID: Final = 1  ...  ID += 1

Mypy将突出显示该行 ID += 1 ,并注意您 Cannot assign to final name "ID" 这为您提供了一种确保代码中的常量永远不会更改其值的方法。

另外,还有一个 @final 装饰器可以应用于类和方法。 装饰 了的 @final 不能被子类化,而 @final 方法不能被子类覆盖:

from typing import final  @final class Base:     ...  class Sub(Base):     ...

Mypy将使用错误消息标记此示例 Cannot inherit from final class "Base" 要了解有关 Final 和的 更多信息 @final ,请参阅 PEP 591 。

第三PEP允许更多的特定类型的提示是 PEP 589 ,其引入 TypedDict 可以使用类似于typed的符号来指定字典中键和值的类型 NamedTuple

传统上,字典使用注释 Dict 问题在于,这仅允许键的一种类型和值的一种类型,通常导致诸如的注释 Dict[str, Any] 例如,考虑一个字典,该字典注册有关Python版本的信息:

py38 = {"version": "3.8", "release_year": 2019}

对应的值为 version 字符串,而 release_year 为整数。 无法使用精确表示 Dict 使用new  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 在运行时,a  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中引入的,已经非常流行。 它们可能是仅在3.6版及更高版本上支持Python库的最常见原因。 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() 有关 允许的格式说明符的完整列表, 请参见 官方文档 。

在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 格式说明称, name 10字符串中应右对齐。 = 同样适用于更复杂的表达式:

>>> f"{name.upper()[::-1] = }" "name.upper()[::-1] = 'CIRE'"

有关f字符串的更多信息,请参见 Python 3的f字符串:改进的字符串格式语法(指南) 。

Python指导委员会

从技术上讲, Python的 治理 不是语言功能。 然而,Python的3.8是Python的第一个版本下没有发展 仁慈的独裁 的 吉多·范罗苏姆 Python语言现在由一个 由五个核心开发人员组成的 指导委员会管理 :

  • 巴里华沙

  • 布雷特·坎农

  • 卡罗尔·威林

  • 吉多·范·罗苏姆(Guido van Rossum)

  • 尼克·科格兰

通往Python新治理模型的道路是自组织方面的有趣研究。 吉多·范·罗苏姆(Guido van Rossum)在1990年代初期创立了Python,并被亲切地称为Python的 仁慈生命独裁者 (BDFL) 多年来,通过 Python增强提案 (PEP) 做出了关于Python语言的越来越多的决定 尽管如此,Guido仍在所有新语言功能上都拥有最终决定权。

在对 赋值表达式 进行了漫长而漫长的讨论之后 ,Guido  在2018年7月 宣布 他将退出BDFL职位( 这次是真实的 )。 他故意没有指定继任者。 相反,他要求核心开发人员团队弄清楚今后应该如何管理Python。

幸运的是,PEP流程已经很完善,因此使用PEP讨论并决定新的治理模型是很自然的。 到2018年秋季, 人们提出 了几种模式 ,包括 选举新的BDFL (更名为 审判官重大 影响决策官:GUIDO),或 在没有集中领导的情况下 转向 基于共识和投票 社区模式 2018年12月, 在核心开发人员投票后选择 指导委员会模型 。

Python 3.8 的超酷新功能

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主题演讲 ,并在 Talk Python To Me 和 Changelog播客 上收听Brett Cannon  您可以在 GitHub上 关注指导委员会的更新

其他很酷的功能

到目前为止,您已经看到有关Python 3.8新增功能的头条新闻。 但是,还有许多其他变化也很酷。 在本节中,您将快速了解其中的一些。

importlib.metadata

Python 3.8的标准库中提供了一个新模块: importlib.metadata 通过此模块,您可以访问有关Python安装中已安装软件包的信息。 连同其配套模块一起 importlib.resourcesimportlib.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() 以下示例 __init__.pyrealpython-reader 包装中 打印出

>>> [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']

requires() 列出软件包的依赖关系。 您可以看到, realpython-reader 例如,它 feedparser 在后台用于阅读和解析文章提要。

importlib.metadata PyPI上 有一个 可用的反向端口 ,可在早期版本的Python上运行。 您可以使用安装它 pip

$ python -m pip install importlib-metadata
try:     from importlib import metadata except ImportError:     import importlib_metadata as metadata  ...

有关 更多信息, 请参阅 文档 importlib.metadata

新的和改进的 mathstatistics 函数

Python 3.8对现有的标准库软件包和模块进行了许多改进。 math 在标准库中有一些新功能。 math.prod() 与内置相似 sum() ,但对于乘法:

>>> import math >>> math.prod((2, 8, 7, 7)) 784  >>> 2 * 8 * 7 * 7 784

这两个语句是等效的。 prod() 当您已经将因子存储在迭代器中时,将更易于使用。

另一个新功能是 math.isqrt() 您可以 isqrt() 用来查找 平方根 的整数部分

>>> import math >>> math.isqrt(9) 3  >>> math.sqrt(9) 3.0  >>> math.isqrt(15) 3  >>> math.sqrt(15) 3.872983346207417

9的平方根是3。您可以看到它 isqrt() 返回整数结果,而 math.sqrt() 始终返回a  float 15的平方根几乎是3.9。 请注意, 将答案 isqrt() 截断 为下一个整数,在这种情况下为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

这使得使用标准库更容易处理点和向量。 但是,如果要对点或向量进行许多计算,则应签出 NumPy 。

statistics 模块还具有几个新功能:

statistics.fmean() 计算 float 数字 的平均值

statistics.geometric_mean() 计算 float 数字 的几何平均值

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.6445013221202459

在这个例子中,你使用 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而不是标准库提供的高级统计信息,请查看 statsmodelsscipy.stats

关于危险语法的警告

Python的 SyntaxWarning 可以警告可疑的语法,通常不是 SyntaxError Python 3.8添加了一些新功能,可以在编码和调试过程中为您提供帮助。

is 之间的区别 == 可能会造成混淆。 为相等的值,后者检查,而 isTrue 仅当对象是相同的。 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

写长列表时,尤其是垂直格式化时,很容易错过逗号。 忘记元组列表中的逗号将给出有关元组不可调用的混乱错误消息。 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

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

最佳化

Python 3.8进行了一些优化。 一些使代码运行更快。 其他减少了内存占用。 例如, namedtuple 与Python 3.7相比,在Python 3.8中 查找a中的字段 要快得多:

>>> 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

你可以看到,查找 .twitter namedtuple 30-40%,在Python 3.8快。 从具有已知长度的可迭代对象初始化列表时,可以节省一些空间。 这样可以节省内存:

>>> import sys  >>> # Python 3.7 >>> sys.getsizeof(list(range(20191014))) 181719232  >>> # Python 3.8 >>> sys.getsizeof(list(range(20191014))) 161528168

在这种情况下,与3.7相比,该列表在Python 3.8中使用的内存减少了约11%。

其他优化包括的更高性能 subprocess ,更快的文件复制 shutil ,更高的默认性能 pickle 以及更快的 operator.itemgetter 操作。 有关 优化的完整列表, 请参见 官方文档 。

那么,您是否应该升级到Python 3.8?

让我们从简单的答案开始。 如果您想尝试这里看到的任何新功能,那么您确实需要能够使用Python 3.8。 pyenv 和 Anaconda 这样的 工具 可以很容易地并排安装多个版本的Python。 或者,您可以运行 官方的Python 3.8 Docker容器 自己尝试使用Python 3.8没有任何弊端。

现在,对于更复杂的问题。 您是否应该将生产环境升级到Python 3.8? 您是否应该使自己的项目依赖于Python 3.8来利用这些新功能?

在Python 3.8中运行Python 3.7代码的问题应该很少。 因此,升级环境以运行Python 3.8是非常安全的,并且您将能够利用 新版本中 进行的 优化 Python 3.8的不同beta版本已经可用了几个月,因此希望可以解决大多数错误。 但是,如果您想保守一点,可以坚持到第一个维护版本(Python 3.8.1)可用。

升级环境后,就可以开始尝试仅在Python 3.8中使用的功能,例如 赋值表达式 和 仅位置参数 但是,您应该注意其他人是否依赖您的代码,因为这也会迫使他们也升级他们的环境。 流行的库可能至少会在更长的一段时间内至少支持Python 3.6。

有关 为Python 3.8 准备代码的更多信息, 请参见 移植到  Python 3.8。

引用自

https://realpython.com/python38-new-features

Python 3.8 的超酷新功能

更多互联网行业动态,请大家继续关注营销星球,每天更新互联网相关的各类信息,让你掌握最新动态。

未经允许不得转载:营销星球 » Python 3.8 的超酷新功能

赞 (0)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址