Python 3.8 的超酷新功能

栏目: IT技术 · 发布时间: 6年前

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

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

一种显示海象运算符优势的模式是 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 签名将帮助您的类型检查器进行推断 str 或 int 根据的字面值 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.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() 。 以下示例 __init__.py 从 realpython-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

新的和改进的 math 和 statistics 函数

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而不是标准库提供的高级统计信息,请查看 statsmodels 和 scipy.stats 。

关于危险语法的警告

Python的 SyntaxWarning 可以警告可疑的语法,通常不是 SyntaxError 。 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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

刘大猫的财富之旅

刘大猫的财富之旅

刘欣、刘大猫 / 新华出版社 / 2017-7-21 / 58.00元

作者刘大猫是一名90后的互联网连环创业者,26岁的他通过互联网创业收获到了财富,不仅仅是物质财富,还有认知的财富。 与其他创业类书籍不通的是,这本书非常真实,务实。书中没有任何大道理鸡汤,作者用平实的语言记录了创业以来遇到的种种事情,变化,困境,以及阶段性的成绩,记录了作者务实,鲜活的创业青春。一起来看看 《刘大猫的财富之旅》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具