Python 3.8 中的新特性

编辑:

Raymond Hettinger

本文解释了 Python 3.8 相较于 3.7 的新特性。Python 3.8 于 2019 年 10 月 14 日发布。有关完整详细信息,请参阅更新日志

摘要 – 发布亮点

新功能

赋值表达式

引入了新的语法 :=,作为更大表达式的一部分将值赋给变量。由于其与海象的眼睛和象牙的相似之处,它被亲切地称为“海象运算符”。

在此示例中,赋值表达式有助于避免两次调用 len()

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

在正则表达式匹配中也会出现类似的优势,其中匹配对象需要两次,一次用于测试是否发生匹配,另一次用于提取子组

discount = 0.0
if (mo := re.search(r'(\d+)% discount', advertisement)):
    discount = float(mo.group(1)) / 100.0

该运算符对于 while 循环也很有用,这些循环计算一个值来测试循环终止,然后又在循环体中需要该相同的值

# Loop over fixed length blocks
while (block := f.read(256)) != '':
    process(block)

另一个激励性的用例出现在列表推导中,其中过滤条件中计算的值也需要用于表达式主体

[clean_name.title() for name in names
 if (clean_name := normalize('NFC', name)) in allowed_names]

尽量将海象运算符的使用限制在减少复杂性并提高可读性的清晰场景中。

有关完整描述,请参阅PEP 572

(由 Emily Morehouse 在bpo-35224中贡献。)

仅限位置参数

引入了新的函数参数语法 /,表示某些函数参数必须按位置指定,不能用作关键字参数。这与 help() 为使用 Larry Hastings 的 Argument Clinic 工具注解的 C 函数所显示的符号相同。

在以下示例中,参数 *a* 和 *b* 仅限位置,而 *c* 或 *d* 可以是位置或关键字,*e* 或 *f* 必须是关键字

def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

以下是有效的调用

f(10, 20, 30, d=40, e=50, f=60)

然而,以下是无效的调用

f(10, b=20, c=30, d=40, e=50, f=60)   # b cannot be a keyword argument
f(10, 20, 30, 40, 50, f=60)           # e must be a keyword argument

这种符号的一个用例是它允许纯 Python 函数完全模拟现有 C 编码函数的行为。例如,内置的 divmod() 函数不接受关键字参数

def divmod(a, b, /):
    "Emulate the built in divmod() function"
    return (a // b, a % b)

另一个用例是在参数名称没有帮助时排除关键字参数。例如,内置的 len() 函数的签名是 len(obj, /)。这排除了诸如以下笨拙的调用

len(obj='hello')  # The "obj" keyword argument impairs readability

将参数标记为仅限位置的另一个好处是,它允许将来更改参数名称,而不会破坏客户端代码。例如,在 statistics 模块中,参数名称 *dist* 将来可能会更改。这通过以下函数规范成为可能

def quantiles(dist, /, *, n=4, method='exclusive')
    ...

由于 / 左侧的参数不会暴露为可能的关键字,因此参数名称仍可用于 **kwargs

>>> def f(a, b, /, **kwargs):
...     print(a, b, kwargs)
...
>>> f(10, 20, a=1, b=2, c=3)         # a and b are used in two ways
10 20 {'a': 1, 'b': 2, 'c': 3}

这大大简化了需要接受任意关键字参数的函数和方法的实现。例如,这是 collections 模块中的代码摘录

class Counter(dict):

    def __init__(self, iterable=None, /, **kwds):
        # Note "iterable" is a possible keyword argument

有关完整描述,请参阅PEP 570

(由 Pablo Galindo 在bpo-36540中贡献。)

已编译字节码文件的并行文件系统缓存

新的 PYTHONPYCACHEPREFIX 设置(也可用作 -X pycache_prefix)将隐式字节码缓存配置为使用单独的并行文件系统树,而不是每个源目录中默认的 __pycache__ 子目录。

缓存的位置在 sys.pycache_prefix 中报告(None 表示在 __pycache__ 子目录中的默认位置)。

(由 Carl Meyer 在bpo-33499中贡献。)

调试构建使用与发布构建相同的 ABI

无论是以发布模式还是调试模式构建,Python 现在都使用相同的 ABI。在 Unix 上,当 Python 以调试模式构建时,现在可以加载以发布模式构建的 C 扩展和使用稳定 ABI 构建的 C 扩展。

发布构建和调试构建现在是 ABI 兼容的:定义 Py_DEBUG 宏不再意味着 Py_TRACE_REFS 宏,后者引入了唯一的 ABI 不兼容性。Py_TRACE_REFS 宏添加了 sys.getobjects() 函数和 PYTHONDUMPREFS 环境变量,可以使用新的 ./configure --with-trace-refs 构建选项设置。(由 Victor Stinner 在bpo-36465中贡献。)

在 Unix 上,C 扩展不再链接到 libpython,除了 Android 和 Cygwin。现在,静态链接的 Python 可以加载使用共享库 Python 构建的 C 扩展。(由 Victor Stinner 在bpo-21536中贡献。)

在 Unix 上,当 Python 以调试模式构建时,导入现在还会查找以发布模式编译的 C 扩展和以稳定 ABI 编译的 C 扩展。(由 Victor Stinner 在bpo-36722中贡献。)

要将 Python 嵌入到应用程序中,必须将新的 --embed 选项传递给 python3-config --libs --embed 以获取 -lpython3.8(将应用程序链接到 libpython)。为了支持 3.8 及更早版本,请首先尝试 python3-config --libs --embed,如果前一个命令失败,则回退到 python3-config --libs(不带 --embed)。

添加一个 pkg-config python-3.8-embed 模块,用于将 Python 嵌入到应用程序中:pkg-config python-3.8-embed --libs 包括 -lpython3.8。为了支持 3.8 及更早版本,请首先尝试 pkg-config python-X.Y-embed --libs,如果前一个命令失败,则回退到 pkg-config python-X.Y --libs(不带 --embed)(将 X.Y 替换为 Python 版本)。

另一方面,pkg-config python3.8 --libs 不再包含 -lpython3.8。C 扩展不得链接到 libpython(Android 和 Cygwin 除外,它们的情况由脚本处理);此更改是故意向后不兼容的。(由 Victor Stinner 在bpo-36721中贡献。)

f-字符串支持 = 用于自文档化表达式和调试

f-字符串添加了 = 指定符。诸如 f'{expr=}' 这样的 f-字符串将扩展为表达式的文本、等号,然后是求值表达式的表示。例如

>>> user = 'eric_idle'
>>> member_since = date(1975, 7, 31)
>>> f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

通常的f-字符串格式说明符允许对表达式结果的显示方式进行更多控制

>>> delta = date.today() - member_since
>>> f'{user=!s}  {delta.days=:,d}'
'user=eric_idle  delta.days=16,075'

= 指定符将显示整个表达式,以便可以显示计算结果

>>> print(f'{theta=}  {cos(radians(theta))=:.3f}')
theta=30  cos(radians(theta))=0.866

(由 Eric V. Smith 和 Larry Hastings 在bpo-36817中贡献。)

PEP 578: Python 运行时审计钩子

该 PEP 添加了一个审计钩子和验证开放钩子。两者都可从 Python 和本机代码访问,允许用纯 Python 代码编写的应用程序和框架利用额外的通知,同时还允许嵌入器或系统管理员部署始终启用审计的 Python 构建。

有关完整详细信息,请参阅PEP 578

PEP 587: Python 初始化配置

PEP 587 添加了一个新的 C API 来配置 Python 初始化,提供对整个配置的更精细控制和更好的错误报告。

新结构

新函数

此 PEP 还将 _PyRuntimeState.preconfig (PyPreConfig 类型) 和 PyInterpreterState.config (PyConfig 类型) 字段添加到这些内部结构中。PyInterpreterState.config 成为新的参考配置,取代了全局配置变量和其他私有变量。

有关文档,请参阅Python 初始化配置

有关完整描述,请参阅PEP 587

(由 Victor Stinner 在bpo-36763中贡献。)

PEP 590: Vectorcall: CPython 的快速调用协议

Vectorcall 协议已添加到 Python/C API 中。它旨在规范化已经针对各种类进行的现有优化。任何实现可调用对象的静态类型都可以使用此协议。

这目前是临时性的。目标是在 Python 3.9 中使其完全公开。

有关完整描述,请参阅PEP 590

(由 Jeroen Demeyer, Mark Shannon 和 Petr Viktorin 在bpo-36974中贡献。)

Pickle 协议 5,带有带外数据缓冲区

当使用 pickle 在 Python 进程之间传输大量数据以利用多核或多机处理时,通过减少内存复制以及可能应用数据相关压缩等自定义技术来优化传输至关重要。

pickle 协议 5 引入了对带外缓冲区的支持,其中PEP 3118 兼容数据可以根据通信层的判断与主 pickle 流分开传输。

有关完整描述,请参阅PEP 574

(由 Antoine Pitrou 在bpo-36785中贡献。)

其他语言更改

  • 由于实现问题,continue 语句在 finally 子句中是非法的。在 Python 3.8 中,此限制被解除。(由 Serhiy Storchaka 在bpo-32489中贡献。)

  • boolintfractions.Fraction 类型现在拥有 as_integer_ratio() 方法,与 floatdecimal.Decimal 中发现的类似。此次小的 API 扩展使得可以编写 numerator, denominator = x.as_integer_ratio(),并使其适用于多种数字类型。(由 Lisa Roach 在bpo-33073和 Raymond Hettinger 在bpo-37819中贡献。)

  • intfloatcomplex 的构造函数现在将使用特殊的 __index__() 方法,如果可用且相应的 __int__()__float__()__complex__() 方法不可用。(由 Serhiy Storchaka 在bpo-20092中贡献。)

  • 正则表达式中添加了对 \N{name} 转义序列的支持

    >>> notice = 'Copyright © 2019'
    >>> copyright_year_pattern = re.compile(r'\N{copyright sign}\s*(\d{4})')
    >>> int(copyright_year_pattern.search(notice).group(1))
    2019
    

    (由 Jonathan Eunice 和 Serhiy Storchaka 在bpo-30688中贡献。)

  • Dict 和 dictviews 现在可以使用 reversed() 按反向插入顺序进行迭代。(由 Rémi Lapeyre 在bpo-33462中贡献。)

  • 函数调用中关键字名称允许的语法被进一步限制。特别是,不再允许 f((keyword)=arg)。它从未打算允许关键字参数赋值项的左侧出现裸名以外的更多内容。(由 Benjamin Peterson 在bpo-34641中贡献。)

  • yieldreturn 语句中的通用可迭代解包不再需要用括号括起来。这使得 *yield* 和 *return* 语法与普通赋值语法更好地保持一致

    >>> def parse(family):
    ...     lastname, *members = family.split()
    ...     return lastname.upper(), *members
    ...
    >>> parse('simpsons homer marge bart lisa maggie')
    ('SIMPSONS', 'homer', 'marge', 'bart', 'lisa', 'maggie')
    

    (由 David Cuthbert 和 Jordan Chapman 在bpo-32117中贡献。)

  • 当代码中遗漏逗号时,例如 [(10, 20) (30, 40)],编译器会显示一个带有有用建议的 SyntaxWarning。这比仅显示一个指示第一个元组不可调用的 TypeError 更好。(由 Serhiy Storchaka 在bpo-15248中贡献。)

  • datetime.datedatetime.datetime 的子类与 datetime.timedelta 对象之间的算术运算现在返回子类的实例,而不是基类。这也影响了其实现(直接或间接)使用 datetime.timedelta 算术运算的操作的返回类型,例如 astimezone()。(由 Paul Ganssle 在bpo-32417中贡献。)

  • 当 Python 解释器被 Ctrl-C (SIGINT) 中断并且由此产生的 KeyboardInterrupt 异常未被捕获时,Python 进程现在通过 SIGINT 信号或以正确的退出代码退出,以便调用进程可以检测到它因 Ctrl-C 而终止。POSIX 和 Windows 上的 shell 使用此方法在交互式会话中正确终止脚本。(由 Google 经 Gregory P. Smith 在bpo-1054041中贡献。)

  • 一些高级编程风格需要更新现有函数的 types.CodeType 对象。由于代码对象是不可变的,因此需要创建一个新的代码对象,该对象基于现有代码对象建模。有 19 个参数,这有些繁琐。现在,新的 replace() 方法使得可以创建一个具有少量更改参数的克隆。

    这是一个示例,它修改了 statistics.mean() 函数以防止 *data* 参数用作关键字参数

    >>> from statistics import mean
    >>> mean(data=[10, 20, 90])
    40
    >>> mean.__code__ = mean.__code__.replace(co_posonlyargcount=1)
    >>> mean(data=[10, 20, 90])
    Traceback (most recent call last):
      ...
    TypeError: mean() got some positional-only arguments passed as keyword arguments: 'data'
    

    (由 Victor Stinner 在bpo-37032中贡献。)

  • 对于整数,pow() 函数的三参数形式现在允许在基数与模数互质的情况下指数为负数。然后,当指数为 -1 时,它计算基数的模逆,对于其他负指数,则计算该逆的适当幂。例如,要计算 38 模 137 的模乘逆,请编写

    >>> pow(38, -1, 137)
    119
    >>> 119 * 38 % 137
    1
    

    模逆出现在线性丢番图方程的解中。例如,要找到 4258𝑥 + 147𝑦 = 369 的整数解,首先改写为 4258𝑥 369 (mod 147),然后求解

    >>> x = 369 * pow(4258, -1, 147) % 147
    >>> y = (4258 * x - 369) // -147
    >>> 4258 * x + 147 * y
    369
    

    (由 Mark Dickinson 在bpo-36027中贡献。)

  • 字典推导式已与字典字面量同步,以便首先计算键,然后计算值

    >>> # Dict comprehension
    >>> cast = {input('role? '): input('actor? ') for i in range(2)}
    role? King Arthur
    actor? Chapman
    role? Black Knight
    actor? Cleese
    
    >>> # Dict literal
    >>> cast = {input('role? '): input('actor? ')}
    role? Sir Robin
    actor? Eric Idle
    

    保证的执行顺序对于赋值表达式很有帮助,因为在键表达式中赋值的变量将在值表达式中可用

    >>> names = ['Martin von Löwis', 'Łukasz Langa', 'Walter Dörwald']
    >>> {(n := normalize('NFC', name)).casefold() : n for name in names}
    {'martin von löwis': 'Martin von Löwis',
     'łukasz langa': 'Łukasz Langa',
     'walter dörwald': 'Walter Dörwald'}
    

    (由 Jörn Heissler 在bpo-35224中贡献。)

  • object.__reduce__() 方法现在可以返回一个包含两到六个元素的元组。以前,限制是五个。新的可选第六个元素是一个可调用对象,其签名是 (obj, state)。这允许直接控制特定对象的状态更新行为。如果不是 *None*,这个可调用对象将优先于对象的 __setstate__() 方法。(由 Pierre Glaser 和 Olivier Grisel 在bpo-35900中贡献。)

新模块

  • 新的 importlib.metadata 模块提供了(临时)支持从第三方包读取元数据。例如,它可以提取已安装包的版本号、入口点列表等

    >>> # Note following example requires that the popular "requests"
    >>> # package has been installed.
    >>>
    >>> from importlib.metadata import version, requires, files
    >>> version('requests')
    '2.22.0'
    >>> list(requires('requests'))
    ['chardet (<3.1.0,>=3.0.2)']
    >>> list(files('requests'))[:5]
    [PackagePath('requests-2.22.0.dist-info/INSTALLER'),
     PackagePath('requests-2.22.0.dist-info/LICENSE'),
     PackagePath('requests-2.22.0.dist-info/METADATA'),
     PackagePath('requests-2.22.0.dist-info/RECORD'),
     PackagePath('requests-2.22.0.dist-info/WHEEL')]
    

    (由 Barry Warsaw 和 Jason R. Coombs 在bpo-34632中贡献。)

改进的模块

ast

AST 节点现在具有 end_linenoend_col_offset 属性,它们提供节点结束的精确位置。(这仅适用于具有 linenocol_offset 属性的节点。)

新函数 ast.get_source_segment() 返回特定 AST 节点的源代码。

(由 Ivan Levkivskyi 在bpo-33416中贡献。)

ast.parse() 函数有一些新标志

  • type_comments=True 使其返回与某些 AST 节点关联的PEP 484PEP 526 类型注释的文本;

  • mode='func_type' 可用于解析PEP 484“签名类型注释”(返回给函数定义 AST 节点);

  • feature_version=(3, N) 允许指定较早的 Python 3 版本。例如,feature_version=(3, 4) 将把 asyncawait 视为非保留字。

(由 Guido van Rossum 在bpo-35766中贡献。)

asyncio

asyncio.run() 已从临时 API 升级为稳定 API。此函数可用于执行协程并返回结果,同时自动管理事件循环。例如

import asyncio

async def main():
    await asyncio.sleep(0)
    return 42

asyncio.run(main())

这 *大致* 等同于

import asyncio

async def main():
    await asyncio.sleep(0)
    return 42

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    asyncio.set_event_loop(None)
    loop.close()

实际实现要复杂得多。因此,asyncio.run() 应该是运行 asyncio 程序的首选方式。

(由 Yury Selivanov 在bpo-32314中贡献。)

运行 python -m asyncio 启动一个本地异步 REPL。这允许快速试验具有顶级 await 的代码。不再需要直接调用 asyncio.run(),后者会在每次调用时生成一个新的事件循环

$ python -m asyncio
asyncio REPL 3.8.0
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> await asyncio.sleep(10, result='hello')
hello

(由 Yury Selivanov 在bpo-37028中贡献。)

异常 asyncio.CancelledError 现在继承自 BaseException 而不是 Exception,并且不再继承自 concurrent.futures.CancelledError。(由 Yury Selivanov 在bpo-32528中贡献。)

在 Windows 上,默认事件循环现在是 ProactorEventLoop。(由 Victor Stinner 在bpo-34687中贡献。)

ProactorEventLoop 现在也支持 UDP。(由 Adam Meily 和 Andrew Svetlov 在bpo-29883中贡献。)

ProactorEventLoop 现在可以被 KeyboardInterrupt (“CTRL+C”) 中断。(由 Vladimir Matveev 在bpo-23057中贡献。)

添加了 asyncio.Task.get_coro() 以获取 asyncio.Task 中包装的协程。(由 Alex Grönholm 在bpo-36999中贡献。)

Asyncio 任务现在可以命名,可以通过将 name 关键字参数传递给 asyncio.create_task()create_task() 事件循环方法,或者通过在任务对象上调用 set_name() 方法。任务名称在 asyncio.Taskrepr() 输出中可见,也可以使用 get_name() 方法检索。(由 Alex Grönholm 在bpo-34270中贡献。)

增加了对 Happy Eyeballsasyncio.loop.create_connection() 的支持。为了指定行为,添加了两个新参数:*happy_eyeballs_delay* 和 *interleave*。Happy Eyeballs 算法通过尝试同时使用 IPv4 和 IPv6 进行连接来提高支持它们的应用程序的响应能力。(由 twisteroid ambassador 在bpo-33530中贡献。)

内置函数

compile() 内置函数已得到改进,可接受 ast.PyCF_ALLOW_TOP_LEVEL_AWAIT 标志。通过传递此新标志,compile() 将允许顶层 awaitasync forasync with 结构,这些结构通常被认为是无效语法。然后可以返回标有 CO_COROUTINE 标志的异步代码对象。(由 Matthias Bussonnier 在bpo-34616中贡献)

collections

collections.namedtuple()_asdict() 方法现在返回一个 dict 而不是 collections.OrderedDict。这之所以有效,是因为自 Python 3.7 以来,常规字典保证了顺序。如果需要 OrderedDict 的额外功能,建议的补救措施是将结果强制转换为所需类型:OrderedDict(nt._asdict())。(由 Raymond Hettinger 在bpo-35864中贡献。)

cProfile

cProfile.Profile 类现在可以用作上下文管理器。通过运行以下代码分析代码块

import cProfile

with cProfile.Profile() as profiler:
      # code to be profiled
      ...

(由 Scott Sanderson 在bpo-29235中贡献。)

csv

csv.DictReader 现在返回 dict 实例而不是 collections.OrderedDict。该工具现在更快,使用的内存更少,同时仍保留字段顺序。(由 Michael Selik 在bpo-34003中贡献。)

curses

添加了一个新的变量,用于保存底层 ncurses 库的结构化版本信息:ncurses_version。(由 Serhiy Storchaka 在bpo-31680中贡献。)

ctypes

在 Windows 上,CDLL 及其子类现在接受 *winmode* 参数来指定底层 LoadLibraryEx 调用的标志。默认标志设置为仅从受信任的位置加载 DLL 依赖项,包括 DLL 存储的路径(如果使用完整或部分路径加载初始 DLL)和通过 add_dll_directory() 添加的路径。(由 Steve Dower 在bpo-36085中贡献。)

datetime

添加了新的备用构造函数 datetime.date.fromisocalendar()datetime.datetime.fromisocalendar(),它们分别从 ISO 年、周数和工作日构造 datedatetime 对象;这些是每个类的 isocalendar 方法的逆操作。(由 Paul Ganssle 在bpo-36004中贡献。)

functools

functools.lru_cache() 现在可以直接用作装饰器,而不再是返回装饰器的函数。因此,以下两种方式现在都受支持

@lru_cache
def f(x):
    ...

@lru_cache(maxsize=256)
def f(x):
    ...

(由 Raymond Hettinger 在bpo-36772中贡献。)

新增 functools.cached_property() 装饰器,用于实例生命周期内缓存的计算属性。

import functools
import statistics

class Dataset:
   def __init__(self, sequence_of_numbers):
      self.data = sequence_of_numbers

   @functools.cached_property
   def variance(self):
      return statistics.variance(self.data)

(由 Carl Meyer 在bpo-21145中贡献)

添加了一个新的 functools.singledispatchmethod() 装饰器,它使用单一分派将方法转换为泛型函数

from functools import singledispatchmethod
from contextlib import suppress

class TaskManager:

    def __init__(self, tasks):
        self.tasks = list(tasks)

    @singledispatchmethod
    def discard(self, value):
        with suppress(ValueError):
            self.tasks.remove(value)

    @discard.register(list)
    def _(self, tasks):
        targets = set(tasks)
        self.tasks = [x for x in self.tasks if x not in targets]

(由 Ethan Smith 在bpo-32380中贡献)

gc

get_objects() 现在可以接收一个可选的 *generation* 参数,指示要获取对象的代。(由 Pablo Galindo 在bpo-36016中贡献。)

gettext

添加了 pgettext() 及其变体。(由 Franz Glasner、Éric Araujo 和 Cheryl Sabella 在bpo-2504中贡献。)

gzip

gzip.compress() 添加了 *mtime* 参数,用于可重现的输出。(由 Guo Ci Teo 在bpo-34898中贡献。)

对于某些类型的无效或损坏的 gzip 文件,现在会引发 BadGzipFile 异常,而不是 OSError。(由 Filip Gruszczyński、Michele Orrù 和 Zackery Spytz 在bpo-6584中贡献。)

IDLE 和 idlelib

超出 N 行(默认为 50 行)的输出会被压缩成一个按钮。N 可以在“设置”对话框的“通用”页面的“PyShell”部分更改。通过右键单击输出,可以压缩更少但可能过长的行。压缩后的输出可以通过双击按钮在原地展开,或通过右键单击按钮展开到剪贴板或单独的窗口中。(由 Tal Einat 在bpo-1529353中贡献。)

在“运行”菜单中添加了“运行自定义”,以自定义设置运行模块。输入的任何命令行参数都会添加到 sys.argv 中。它们也会在下次自定义运行的框中重新出现。还可以取消正常的 Shell 主模块重启。(由 Cheryl Sabella、Terry Jan Reedy 和其他人在bpo-5680bpo-37627中贡献。)

为 IDLE 编辑器窗口添加了可选的行号。除非在配置对话框的“常规”选项卡中另行设置,否则窗口在打开时不会显示行号。现有窗口的行号在“选项”菜单中显示和隐藏。(由 Tal Einat 和 Saimadhav Heblikar 在bpo-17535中贡献。)

现在使用 OS 本机编码在 Python 字符串和 Tcl 对象之间进行转换。这使得 IDLE 可以处理表情符号和其他非 BMP 字符。这些字符可以显示或复制粘贴到剪贴板。从 Tcl 转换为 Python 再转换回 Python 现在永远不会失败。(许多人为此努力了八年,但 Serhiy Storchaka 最终在bpo-13153中解决了这个问题。)

3.8.1 中的新特性

添加了关闭光标闪烁的选项。(由 Zackery Spytz 在bpo-4603中贡献。)

Esc 键现在关闭 IDLE 补全窗口。(由 Johnny Najera 在bpo-38944中贡献。)

上述更改已回溯到 3.7 维护版本。

将关键字添加到模块名称补全列表。(由 Terry J. Reedy 在bpo-37765中贡献。)

inspect

inspect.getdoc() 函数现在可以为 __slots__ 查找文档字符串,如果该属性是 dict,其中值是文档字符串。这提供了与我们已经拥有的 property()classmethod()staticmethod() 类似的文档选项

class AudioClip:
    __slots__ = {'bit_rate': 'expressed in kilohertz to one decimal place',
                 'duration': 'in seconds, rounded up to an integer'}
    def __init__(self, bit_rate, duration):
        self.bit_rate = round(bit_rate / 1000.0, 1)
        self.duration = ceil(duration)

(由 Raymond Hettinger 在bpo-36326中贡献。)

io

在开发模式 (-X env) 和调试构建中,io.IOBase 终结器现在会记录异常,如果 close() 方法失败。在发布构建中,异常默认会被静默忽略。(由 Victor Stinner 在bpo-18748中贡献。)

itertools

itertools.accumulate() 函数添加了一个可选的 *initial* 关键字参数来指定初始值

>>> from itertools import accumulate
>>> list(accumulate([10, 5, 30, 15], initial=1000))
[1000, 1010, 1015, 1045, 1060]

(由 Lisa Roach 在bpo-34659中贡献。)

json.tool

添加选项 --json-lines 以将每个输入行解析为单独的 JSON 对象。(由 Weipeng Hong 在bpo-31553中贡献。)

logging

logging.basicConfig() 添加了一个 *force* 关键字参数。当设置为 true 时,在执行其他参数指定的配置之前,会移除并关闭附加到根记录器上的任何现有处理程序。

这解决了一个长期存在的问题。一旦调用了记录器或 *basicConfig()*,对 *basicConfig()* 的后续调用就会被静默忽略。这使得使用交互式提示符或 Jupyter Notebook 更新、试验或教授各种日志记录配置选项变得困难。

(由 Raymond Hettinger 建议,由 Donghee Na 实现,由 Vinay Sajip 审查,在bpo-33897中。)

math

添加了新函数 math.dist(),用于计算两点之间的欧几里得距离。(由 Raymond Hettinger 在bpo-33089中贡献。)

扩展了 math.hypot() 函数以处理多个维度。以前,它只支持二维情况。(由 Raymond Hettinger 在bpo-33089中贡献。)

添加了新函数 math.prod(),作为 sum() 的类似函数,返回“起始”值(默认:1)乘以数字可迭代对象的乘积

>>> prior = 0.8
>>> likelihoods = [0.625, 0.84, 0.30]
>>> math.prod(likelihoods, start=prior)
0.126

(由 Pablo Galindo 在bpo-35606中贡献。)

添加了两个新的组合函数 math.perm()math.comb()

>>> math.perm(10, 3)    # Permutations of 10 things taken 3 at a time
720
>>> math.comb(10, 3)    # Combinations of 10 things taken 3 at a time
120

(由 Yash Aggarwal、Keller Fuchs、Serhiy Storchaka 和 Raymond Hettinger 在bpo-37128bpo-37178bpo-35431中贡献。)

添加了一个新函数 math.isqrt(),用于计算精确的整数平方根,而无需转换为浮点数。新函数支持任意大整数。它比 floor(sqrt(n)) 快,但比 math.sqrt()

>>> r = 650320427
>>> s = r ** 2
>>> isqrt(s - 1)         # correct
650320426
>>> floor(sqrt(s - 1))   # incorrect
650320427

(由 Mark Dickinson 在bpo-36887中贡献。)

函数 math.factorial() 不再接受非整数类的参数。(由 Pablo Galindo 在bpo-33083中贡献。)

mmap

mmap.mmap 类现在有一个 madvise() 方法来访问 madvise() 系统调用。(由 Zackery Spytz 在bpo-32941中贡献。)

multiprocessing

添加了新的 multiprocessing.shared_memory 模块。(由 Davin Potts 在bpo-35813中贡献。)

在 macOS 上,默认使用 *spawn* 启动方法。(由 Victor Stinner 在bpo-33725中贡献。)

os

在 Windows 上,添加了新函数 add_dll_directory(),用于在导入扩展模块或使用 ctypes 加载 DLL 时提供额外的本机依赖项搜索路径。(由 Steve Dower 在bpo-36085中贡献。)

添加了一个新的 os.memfd_create() 函数来封装 memfd_create() 系统调用。(由 Zackery Spytz 和 Christian Heimes 在bpo-26836中贡献。)

在 Windows 上,大部分用于处理重新解析点(包括符号链接和目录连接)的手动逻辑已委托给操作系统。具体来说,os.stat() 现在将遍历操作系统支持的任何内容,而 os.lstat() 只会打开标识为“名称代理”的重新解析点,而其他重新解析点则像 os.stat() 一样打开。在所有情况下,os.stat_result.st_mode 将只为符号链接设置 S_IFLNK,而不为其他类型的重新解析点设置。要识别其他类型的重新解析点,请检查新的 os.stat_result.st_reparse_tag 属性。

在 Windows 上,os.readlink() 现在能够读取目录连接。请注意,islink() 对于目录连接将返回 False,因此首先检查 islink 的代码将继续将连接视为目录,而处理来自 os.readlink() 的错误的代码现在可能会将连接视为链接。

(由 Steve Dower 在bpo-37834中贡献。)

os.path

os.path 函数(例如 exists()lexists()isdir()isfile()islink()ismount() 等返回布尔结果的函数)现在返回 False,而不是对于包含操作系统级别不可表示字符或字节的路径引发 ValueError 或其子类 UnicodeEncodeErrorUnicodeDecodeError。(由 Serhiy Storchaka 在bpo-33721中贡献。)

在 Windows 上,expanduser() 现在优先使用 USERPROFILE 环境变量,并且不使用 HOME,后者通常未为普通用户帐户设置。(由 Anthony Sottile 在bpo-36264中贡献。)

在 Windows 上,isdir() 不再对指向不存在目录的链接返回 True

在 Windows 上,realpath() 现在解析重新解析点,包括符号链接和目录连接。

(由 Steve Dower 在bpo-37834中贡献。)

pathlib

pathlib.Path 返回布尔结果的方法(如 exists()is_dir()is_file()is_mount()is_symlink()is_block_device()is_char_device()is_fifo()is_socket())现在返回 False,而不是对于包含操作系统级别不可表示字符的路径引发 ValueError 或其子类 UnicodeEncodeError。(由 Serhiy Storchaka 在bpo-33721中贡献。)

添加了 pathlib.Path.link_to(),它创建一个指向路径的硬链接。(由 Joannah Nanjekye 在bpo-26978中贡献)请注意,link_to 在 3.10 中被弃用,并在 3.12 中被删除,取而代之的是 3.10 中添加的 hardlink_to 方法,它与现有 symlink_to 方法的语义匹配。

pickle

子类化 C 优化 Picklerpickle 扩展现在可以通过定义特殊的 reducer_override() 方法来重写函数和类的 pickle 逻辑。(由 Pierre Glaser 和 Olivier Grisel 在bpo-35900中贡献。)

plistlib

添加了新的 plistlib.UID 并启用了对读取和写入 NSKeyedArchiver 编码的二进制 plist 的支持。(由 Jon Janzen 在bpo-26707中贡献。)

pprint

pprint 模块为几个函数添加了 *sort_dicts* 参数。默认情况下,这些函数在渲染或打印之前继续对字典进行排序。但是,如果将 *sort_dicts* 设置为 false,字典将保留键的插入顺序。这在调试期间与 JSON 输入进行比较时很有用。

此外,还新增了一个便捷功能 pprint.pp(),它类似于 pprint.pprint(),但默认将 sort_dicts 设置为 False

>>> from pprint import pprint, pp
>>> d = dict(source='input.txt', operation='filter', destination='output.txt')
>>> pp(d, width=40)                  # Original order
{'source': 'input.txt',
 'operation': 'filter',
 'destination': 'output.txt'}
>>> pprint(d, width=40)              # Keys sorted alphabetically
{'destination': 'output.txt',
 'operation': 'filter',
 'source': 'input.txt'}

(由 Rémi Lapeyre 在 bpo-30670 中贡献。)

py_compile

py_compile.compile() 现在支持静默模式。(由 Joannah Nanjekye 在 bpo-22640 中贡献。)

shlex

新的 shlex.join() 函数作为 shlex.split() 的逆操作。(由 Bo Bayles 在 bpo-32102 中贡献。)

shutil

shutil.copytree() 现在接受一个新的 dirs_exist_ok 关键字参数。(由 Josh Bronson 在 bpo-20849 中贡献。)

shutil.make_archive() 现在默认使用现代的 pax (POSIX.1-2001) 格式创建新档案,以提高可移植性和符合标准,这继承自 tarfile 模块的相应更改。(由 C.A.M. Gerlach 在 bpo-30661 中贡献。)

shutil.rmtree() 在 Windows 上现在会直接删除目录连接点,而不会先递归删除其内容。(由 Steve Dower 在 bpo-37834 中贡献。)

socket

添加了 create_server()has_dualstack_ipv6() 便捷函数,用于自动化创建服务器套接字时通常涉及的必要任务,包括在同一套接字上接受 IPv4 和 IPv6 连接。(由 Giampaolo Rodolà 在 bpo-17561 中贡献。)

socket.if_nameindex(), socket.if_nametoindex(), 和 socket.if_indextoname() 函数已在 Windows 上实现。(由 Zackery Spytz 在 bpo-37007 中贡献。)

ssl

添加了 post_handshake_auth 以启用,并添加了 verify_client_post_handshake() 以启动 TLS 1.3 握手后认证。(由 Christian Heimes 在 bpo-34670 中贡献。)

statistics

添加了 statistics.fmean() 作为 statistics.mean() 的更快浮点变体。(由 Raymond Hettinger 和 Steven D’Aprano 在 bpo-35904 中贡献。)

添加了 statistics.geometric_mean() (由 Raymond Hettinger 在 bpo-27181 中贡献。)

添加了 statistics.multimode(),它返回最常见值的列表。(由 Raymond Hettinger 在 bpo-35892 中贡献。)

添加了 statistics.quantiles(),它将数据或分布划分为等概率区间(例如四分位数、十分位数或百分位数)。(由 Raymond Hettinger 在 bpo-36546 中贡献。)

添加了 statistics.NormalDist,这是一个用于创建和操纵随机变量正态分布的工具。(由 Raymond Hettinger 在 bpo-36018 中贡献。)

>>> temperature_feb = NormalDist.from_samples([4, 12, -3, 2, 7, 14])
>>> temperature_feb.mean
6.0
>>> temperature_feb.stdev
6.356099432828281

>>> temperature_feb.cdf(3)            # Chance of being under 3 degrees
0.3184678262814532
>>> # Relative chance of being 7 degrees versus 10 degrees
>>> temperature_feb.pdf(7) / temperature_feb.pdf(10)
1.2039930378537762

>>> el_niño = NormalDist(4, 2.5)
>>> temperature_feb += el_niño        # Add in a climate effect
>>> temperature_feb
NormalDist(mu=10.0, sigma=6.830080526611674)

>>> temperature_feb * (9/5) + 32      # Convert to Fahrenheit
NormalDist(mu=50.0, sigma=12.294144947901014)
>>> temperature_feb.samples(3)        # Generate random samples
[7.672102882379219, 12.000027119750287, 4.647488369766392]

sys

添加了新的 sys.unraisablehook() 函数,可以重写该函数来控制如何处理“无法引发的异常”。当发生异常但 Python 无法处理时,例如在析构函数引发异常或垃圾回收 (gc.collect()) 期间,就会调用它。(由 Victor Stinner 在 bpo-36829 中贡献。)

tarfile

tarfile 模块现在默认使用现代的 pax (POSIX.1-2001) 格式创建新档案,而不是以前的 GNU 特有格式。这通过标准化和可扩展格式中的一致编码 (UTF-8) 提高了跨平台可移植性,并提供了其他多项优点。(由 C.A.M. Gerlach 在 bpo-36268 中贡献。)

threading

添加了一个新的 threading.excepthook() 函数,用于处理未捕获的 threading.Thread.run() 异常。它可以被覆盖以控制如何处理未捕获的 threading.Thread.run() 异常。(由 Victor Stinner 在 bpo-1230540 中贡献。)

threading.Thread 类添加了一个新的 threading.get_native_id() 函数和一个 native_id 属性。这些返回由内核分配的当前线程的本地整数线程 ID。此功能仅在某些平台可用,详情请参见 get_native_id。(由 Jake Tesler 在 bpo-36084 中贡献。)

tokenize

tokenize 模块现在在提供的输入没有尾随换行符时,会隐式发出一个 NEWLINE 标记。此行为现在与 C 词法分析器内部的行为一致。(由 Ammar Askar 在 bpo-33899 中贡献。)

tkinter

tkinter.Spinbox 类中添加了方法 selection_from(), selection_present(), selection_range()selection_to()。(由 Juliette Monsel 在 bpo-34829 中贡献。)

tkinter.Canvas 类中添加了方法 moveto()。(由 Juliette Monsel 在 bpo-23831 中贡献。)

tkinter.PhotoImage 类现在具有 transparency_get()transparency_set() 方法。(由 Zackery Spytz 在 bpo-25451 中贡献。)

时间

为 macOS 10.12 添加了新的时钟 CLOCK_UPTIME_RAW。(由 Joannah Nanjekye 在 bpo-35702 中贡献。)

typing

typing 模块包含几个新功能

unicodedata

unicodedata 模块已升级,以使用 Unicode 12.1.0 版本。

新的函数 is_normalized() 可用于验证字符串是否处于特定范式,通常比实际规范化字符串快得多。(由 Max Belanger、David Euresti 和 Greg Price 在 bpo-32285bpo-37966 中贡献)。

unittest

添加了 AsyncMock 以支持 Mock 的异步版本。还添加了适当的新断言函数用于测试。(由 Lisa Roach 在 bpo-26467 中贡献)。

在 unittest 中添加了 addModuleCleanup()addClassCleanup() 以支持 setUpModule()setUpClass() 的清理。(由 Lisa Roach 在 bpo-24412 中贡献。)

几个 mock 断言函数在失败时也会打印实际调用列表。(由 Petter Strandmark 在 bpo-35047 中贡献。)

unittest 模块获得了对协程作为测试用例与 unittest.IsolatedAsyncioTestCase 一起使用的支持。(由 Andrew Svetlov 在 bpo-32972 中贡献。)

示例

import unittest


class TestRequest(unittest.IsolatedAsyncioTestCase):

    async def asyncSetUp(self):
        self.connection = await AsyncConnection()

    async def test_get(self):
        response = await self.connection.get("https://example.com")
        self.assertEqual(response.status_code, 200)

    async def asyncTearDown(self):
        await self.connection.close()


if __name__ == "__main__":
    unittest.main()

venv

venv 现在在所有平台上都包含一个 Activate.ps1 脚本,用于在 PowerShell Core 6.1 下激活虚拟环境。(由 Brett Cannon 在 bpo-32718 中贡献。)

weakref

weakref.proxy() 返回的代理对象现在除了其他数字运算符外,还支持矩阵乘法运算符 @@=。(由 Mark Dickinson 在 bpo-36669 中贡献。)

xml

作为对 DTD 和外部实体检索的缓解措施,xml.dom.minidomxml.sax 模块默认不再处理外部实体。(由 Christian Heimes 在 bpo-17239 中贡献。)

xml.etree.ElementTree 模块中的 .find*() 方法支持通配符搜索,例如 {*}tag 会忽略命名空间,{namespace}* 会返回给定命名空间中的所有标签。(由 Stefan Behnel 在 bpo-28238 中贡献。)

xml.etree.ElementTree 模块提供了一个新函数 canonicalize(),它实现了 C14N 2.0。(由 Stefan Behnel 在 bpo-13611 中贡献。)

xml.etree.ElementTree.XMLParser 的目标对象可以通过新的回调方法 start_ns()end_ns() 接收命名空间声明事件。此外,可以配置 xml.etree.ElementTree.TreeBuilder 目标以处理有关注释和处理指令的事件,以将它们包含在生成的树中。(由 Stefan Behnel 在 bpo-36676bpo-36673 中贡献。)

xmlrpc

xmlrpc.client.ServerProxy 现在支持可选的 headers 关键字参数,用于在每个请求中发送一系列 HTTP 标头。除此之外,这使得从默认基本认证升级到更快的会话认证成为可能。(由 Cédric Krier 在 bpo-35153 中贡献。)

优化

  • subprocess 模块现在在某些情况下可以使用 os.posix_spawn() 函数以获得更好的性能。目前,它仅在 macOS 和 Linux(使用 glibc 2.24 或更高版本)上使用,如果满足所有这些条件:

    • close_fds 为假;

    • 未设置 preexec_fnpass_fdscwdstart_new_session 参数;

    • executable 路径包含一个目录。

    (由 Joannah Nanjekye 和 Victor Stinner 在 bpo-35537 中贡献。)

  • shutil.copyfile()shutil.copy()shutil.copy2()shutil.copytree()shutil.move() 在 Linux 和 macOS 上使用平台特定的“快速复制”系统调用,以更高效地复制文件。“快速复制”意味着复制操作发生在内核内部,避免了像“outfd.write(infd.read())”那样在 Python 中使用用户空间缓冲区。在 Windows 上,shutil.copyfile() 使用更大的默认缓冲区大小(1 MiB 而不是 16 KiB),并使用基于 memoryview()shutil.copyfileobj() 变体。在同一分区内复制 512 MiB 文件的速度在 Linux 上提高了约 +26%,在 macOS 上提高了 +50%,在 Windows 上提高了 +40%。此外,消耗的 CPU 周期也大大减少。参见 平台相关高效复制操作 部分。(由 Giampaolo Rodolà 在 bpo-33671 中贡献。)

  • shutil.copytree() 使用 os.scandir() 函数,所有依赖它的复制函数都使用缓存的 os.stat() 值。复制包含 8000 个文件的目录的速度在 Linux 上提高了约 +9%,在 Windows 上提高了 +20%,在 Windows SMB 共享上提高了 +30%。此外,os.stat() 系统调用的数量减少了 38%,使得 shutil.copytree() 在网络文件系统上特别快。(由 Giampaolo Rodolà 在 bpo-33695 中贡献。)

  • pickle 模块中的默认协议现在是协议 4,该协议首次在 Python 3.4 中引入。与 Python 3.0 中提供的协议 3 相比,它提供了更好的性能和更小的尺寸。

  • PyGC_Head 中删除了一个 Py_ssize_t 成员。所有 GC 跟踪对象(例如元组、列表、字典)的大小都减少了 4 或 8 字节。(由 Inada Naoki 在 bpo-33597 中贡献。)

  • uuid.UUID 现在使用 __slots__ 来减少其内存占用。(由 Wouter Bolsterlee 和 Tal Einat 在 bpo-30977 中贡献)

  • operator.itemgetter() 的性能提高了 33%。优化了参数处理,并为元组中单个非负整数索引的常见情况(这是标准库中的典型用例)添加了快速路径。(由 Raymond Hettinger 在 bpo-35664 中贡献。)

  • 加快了 collections.namedtuple() 中的字段查找。它们现在速度快了两倍以上,使其成为 Python 中最快的实例变量查找形式。(由 Raymond Hettinger, Pablo Galindo, Joe Jevnik, Serhiy Storchaka 在 bpo-32492 中贡献。)

  • 如果输入可迭代对象具有已知长度(输入实现了 __len__),则 list 构造函数不会过度分配内部项目缓冲区。这使得创建的列表平均小 12%。(由 Raymond Hettinger 和 Pablo Galindo 在 bpo-33234 中贡献。)

  • 类变量写入速度翻倍。当更新非双下划线属性时,会不必要地调用更新槽。(由 Stefan Behnel、Pablo Galindo Salgado、Raymond Hettinger、Neil Schemenauer 和 Serhiy Storchaka 在 bpo-36012 中贡献。)

  • 减少了许多内置函数和方法参数转换的开销。这使得调用一些简单的内置函数和方法的速度提高了 20-50%。(由 Serhiy Storchaka 在 bpo-23867bpo-35582bpo-36127 中贡献。)

  • LOAD_GLOBAL 指令现在使用新的“逐操作码缓存”机制。它现在快了大约 40%。(由 Yury Selivanov 和 Inada Naoki 在 bpo-26219 中贡献。)

构建和 C API 更改

  • 默认 sys.abiflags 变为空字符串:用于 pymalloc 的 m 标志变得无用(使用和不使用 pymalloc 的构建都是 ABI 兼容的),因此已被移除。(由 Victor Stinner 在 bpo-36707 中贡献。)

    更改示例

    • 只安装 python3.8 程序,python3.8m 程序已移除。

    • 只安装 python3.8-config 脚本,python3.8m-config 脚本已移除。

    • 动态库文件名的后缀中已移除 m 标志:标准库中的扩展模块以及由第三方包(例如从 PyPI 下载的包)生成和安装的扩展模块。例如,在 Linux 上,Python 3.7 的后缀 .cpython-37m-x86_64-linux-gnu.so 在 Python 3.8 中变为 .cpython-38-x86_64-linux-gnu.so

  • 头文件已重新组织,以更好地分离不同种类的 API

    • Include/*.h 应该是可移植的公共稳定 C API。

    • Include/cpython/*.h 应该是特定于 CPython 的不稳定 C API;公共 API,其中一些私有 API 以 _Py_PY 为前缀。

    • Include/internal/*.h 是特定于 CPython 的私有内部 C API。此 API 不提供向后兼容性保证,不应在 CPython 之外使用。它仅适用于非常特殊的需求,例如调试器和分析器,它们必须访问 CPython 内部而无需调用函数。此 API 现在由 make install 安装。

    (由 Victor Stinner 在 bpo-35134bpo-35081 中贡献,Eric Snow 在 Python 3.7 中启动了这项工作。)

  • 一些宏已被转换为静态内联函数:参数类型和返回类型明确定义,它们没有宏特有的问题,变量具有局部作用域。示例

    (由 Victor Stinner 在 bpo-35059 中贡献。)

  • PyByteArray_Init()PyByteArray_Fini() 函数已被移除。自 Python 2.7.4 和 Python 3.2.0 以来,它们什么也没做,被排除在有限的 API(稳定 ABI)之外,并且没有被记录。(由 Victor Stinner 在 bpo-35713 中贡献。)

  • PyExceptionClass_Name() 的结果现在是 const char * 类型,而不是 char * 类型。(由 Serhiy Storchaka 在 bpo-33818 中贡献。)

  • Modules/Setup.distModules/Setup 的二元性已被移除。以前,在更新 CPython 源代码树时,为了反映任何上游更改,必须手动将 Modules/Setup.dist(在源代码树内)复制到 Modules/Setup(在构建树内)。这给打包人员带来了微不足道的便利,却给跟踪 CPython 开发的开发人员带来了频繁的烦恼,因为忘记复制文件可能会导致构建失败。

    现在构建系统始终从源代码树中的 Modules/Setup 读取。希望自定义该文件的人被鼓励将他们的更改保留在 CPython 的 git 分支中或作为补丁文件,就像他们对源代码树的任何其他更改所做的那样。

    (由 Antoine Pitrou 在 bpo-32430 中贡献。)

  • 将 Python 数字转换为 C 整数的函数,例如 PyLong_AsLong(),以及带整数转换格式单元(如 'i')的参数解析函数,例如 PyArg_ParseTuple(),现在将使用 __index__() 特殊方法而不是 __int__()(如果可用)。对于具有 __int__() 方法但没有 __index__() 方法的对象(如 DecimalFraction),将发出弃用警告。PyNumber_Check() 现在将为实现 __index__() 的对象返回 1PyNumber_Long()PyNumber_Float()PyFloat_AsDouble() 现在也使用 __index__() 方法(如果可用)。(由 Serhiy Storchaka 在 bpo-36048bpo-20092 中贡献。)

  • 堆分配的类型对象现在将在 PyObject_Init() (及其并行宏 PyObject_INIT) 中增加其引用计数,而不是在 PyType_GenericAlloc() 中。修改实例分配或解除分配的类型可能需要进行调整。(由 Eddie Elizondo 在 bpo-35810 中贡献。)

  • 新函数 PyCode_NewWithPosOnlyArgs() 允许像 PyCode_New() 一样创建代码对象,但带有一个额外的 posonlyargcount 参数,用于指示仅限位置参数的数量。(由 Pablo Galindo 在 bpo-37221 中贡献。)

  • Py_SetPath() 现在将 sys.executable 设置为程序的完整路径 (Py_GetProgramFullPath()) 而不是程序名称 (Py_GetProgramName())。(由 Victor Stinner 在 bpo-38234 中贡献。)

已弃用

API 和功能移除

以下功能和 API 已从 Python 3.8 中移除

  • 从 Python 3.3 开始,从 collections 导入 ABC 已被弃用,应从 collections.abc 导入。从 collections 导入的功能原定于 3.8 中移除,但已推迟到 3.9。(参见 gh-81134。)

  • 在 Python 3.7 中已弃用的 macpath 模块已移除。(由 Victor Stinner 在 bpo-35471 中贡献。)

  • 函数 platform.popen() 已移除,自 Python 3.3 起已被弃用:请改用 os.popen()。(由 Victor Stinner 在 bpo-35345 中贡献。)

  • 函数 time.clock() 已被移除,自 Python 3.3 起已被弃用:请根据您的要求改用 time.perf_counter()time.process_time(),以获得明确定义的行为。(由 Matthias Bussonnier 在 bpo-36895 中贡献。)

  • 已移除 pyvenv 脚本,转而使用 python3.8 -m venv,以帮助消除关于 pyvenv 脚本与哪个 Python 解释器绑定的困惑。(由 Brett Cannon 在 bpo-25427 中贡献。)

  • parse_qsparse_qslescape 已从 cgi 模块中移除。它们在 Python 3.2 或更早版本中已弃用。应从 urllib.parsehtml 模块中导入它们。

  • filemode 函数已从 tarfile 模块中移除。它没有被文档记录,并且自 Python 3.3 以来已被弃用。

  • XMLParser 构造函数不再接受 html 参数。它从未生效,并且在 Python 3.4 中已被弃用。所有其他参数现在都是 仅限关键字参数。(由 Serhiy Storchaka 在 bpo-29209 中贡献。)

  • 已移除 XMLParserdoctype() 方法。(由 Serhiy Storchaka 在 bpo-29209 中贡献。)

  • “unicode_internal” 编解码器已移除。(由 Inada Naoki 在 bpo-36297 中贡献。)

  • sqlite3 模块的 CacheStatement 对象不会暴露给用户。(由 Aviv Palivoda 在 bpo-30262 中贡献。)

  • fileinput.input()fileinput.FileInput()bufsize 关键字参数已被移除,该参数自 Python 3.6 以来已被忽略和弃用。bpo-36952 (由 Matthias Bussonnier 贡献。)

  • 在 Python 3.7 中已弃用的函数 sys.set_coroutine_wrapper()sys.get_coroutine_wrapper() 已移除;bpo-36933 (由 Matthias Bussonnier 贡献。)

迁移到 Python 3.8

本节列出了前面描述过的变更以及其他可能需要修改代码的 bug 修复。

Python 行为的变更

  • 生成器表达式和推导式(除了最左侧 for 子句中的可迭代表达式)现在禁止使用 yield 表达式(包括 yieldyield from 子句)。(由 Serhiy Storchaka 在 bpo-10544 中贡献。)

  • 当身份检查(isis not)与某些类型的字面量(例如字符串、数字)一起使用时,编译器现在会生成 SyntaxWarning。这些在 CPython 中通常会偶然工作,但语言规范不保证。该警告建议用户改用相等性测试(==!=)。(由 Serhiy Storchaka 在 bpo-34850 中贡献。)

  • CPython 解释器在某些情况下可能会吞噬异常。在 Python 3.8 中,这种情况发生得更少。特别是,从类型字典获取属性时引发的异常不再被忽略。(由 Serhiy Storchaka 在 bpo-35459 中贡献。)

  • 移除了内置类型 boolintfloatcomplex 以及标准库中少数几个类的 __str__ 实现。它们现在继承了 object__str__()。因此,在这些类的子类中定义 __repr__() 方法将影响它们的字符串表示。(由 Serhiy Storchaka 在 bpo-36793 中贡献。)

  • 在 AIX 上,sys.platform 不再包含主版本号。它始终是 'aix',而不是 'aix3' .. 'aix7'。由于较旧的 Python 版本包含版本号,因此建议始终使用 sys.platform.startswith('aix')。(由 M. Felt 在 bpo-36588 中贡献。)

  • PyEval_AcquireLock()PyEval_AcquireThread() 现在在解释器正在终止时被调用会终止当前线程,使其与 PyEval_RestoreThread()Py_END_ALLOW_THREADS()PyGILState_Ensure() 保持一致。如果不需要此行为,请通过检查 _Py_IsFinalizing()sys.is_finalizing() 来防护调用。(由 Joannah Nanjekye 在 bpo-36475 中贡献。)

Python API 的变化

  • 在 Windows 上,扩展模块和用 ctypes 加载的 DLL 的 DLL 依赖项现在解析更安全。只有系统路径、包含 DLL 或 PYD 文件的目录以及通过 add_dll_directory() 添加的目录才会被搜索以查找加载时依赖项。具体来说,PATH 和当前工作目录不再使用,并且对它们的修改将不再对正常的 DLL 解析产生任何影响。如果您的应用程序依赖于这些机制,您应该检查 add_dll_directory(),如果它存在,则在加载库时使用它来添加您的 DLL 目录。请注意,Windows 7 用户需要确保已安装 Windows 更新 KB2533623(安装程序也会验证这一点)。(由 Steve Dower 在 bpo-36085 中贡献。)

  • 在 pgen 被纯 Python 实现取代后,相关的头文件和函数已被删除。(由 Pablo Galindo 在 bpo-36623 中贡献。)

  • types.CodeType 在构造函数的第二个位置(posonlyargcount)有一个新参数,以支持 PEP 570 中定义的仅限位置参数。第一个参数(argcount)现在表示位置参数的总数(包括仅限位置参数)。types.CodeType 的新 replace() 方法可用于使代码面向未来。

  • hmac.new() 的参数 digestmod 不再默认使用 MD5 摘要。

C API 的变化

  • PyCompilerFlags 结构有一个新的 cf_feature_version 字段。它应初始化为 PY_MINOR_VERSION。此字段默认被忽略,并且仅当 cf_flags 中设置了 PyCF_ONLY_AST 标志时才使用。(由 Guido van Rossum 在 bpo-35766 中贡献。)

  • PyEval_ReInitThreads() 函数已从 C API 中删除。它不应被显式调用:请改用 PyOS_AfterFork_Child()。(由 Victor Stinner 在 bpo-36728 中贡献。)

  • 在 Unix 上,C 扩展不再链接到 libpython,除了 Android 和 Cygwin。当 Python 被嵌入时,libpython 不得使用 RTLD_LOCAL 加载,而应使用 RTLD_GLOBAL。以前,使用 RTLD_LOCAL 时,已经无法加载未链接到 libpython 的 C 扩展,例如由 Modules/Setup*shared* 部分构建的标准库 C 扩展。(由 Victor Stinner 在 bpo-21536 中贡献。)

  • 在未定义 PY_SSIZE_T_CLEAN 的情况下,在解析或构建值时使用格式的 # 变体(例如 PyArg_ParseTuple()Py_BuildValue()PyObject_CallFunction() 等)现在会引发 DeprecationWarning。它将在 3.10 或 4.0 中删除。详情请阅读 解析参数和构建值。(由 Inada Naoki 在 bpo-36381 中贡献。)

  • 堆分配类型(例如使用 PyType_FromSpec() 创建的类型)的实例持有对其类型对象的引用。这些类型对象的引用计数增加已从 PyType_GenericAlloc() 移至更低级的函数 PyObject_Init()PyObject_INIT。这使得通过 PyType_FromSpec() 创建的类型在托管代码中与其他类一样。

    静态分配的类型 不受影响。

    在绝大多数情况下,应该没有副作用。然而,手动在分配实例后增加引用计数(可能为了解决错误)的类型现在可能会变成永久性。为了避免这种情况,这些类需要在实例释放期间调用 Py_DECREF 类型对象。

    为了将这些类型正确移植到 3.8,请应用以下更改:

    • 在分配实例后(如果有的话),删除类型对象上的 Py_INCREF。这可能发生在调用 PyObject_NewPyObject_NewVarPyObject_GC_New()PyObject_GC_NewVar() 或任何其他使用 PyObject_Init()PyObject_INIT 的自定义分配器之后。

      示例

      static foo_struct *
      foo_new(PyObject *type) {
          foo_struct *foo = PyObject_GC_New(foo_struct, (PyTypeObject *) type);
          if (foo == NULL)
              return NULL;
      #if PY_VERSION_HEX < 0x03080000
          // Workaround for Python issue 35810; no longer necessary in Python 3.8
          PY_INCREF(type)
      #endif
          return foo;
      }
      
    • 确保堆分配类型的自定义 tp_dealloc 函数会减少类型的引用计数。

      示例

      static void
      foo_dealloc(foo_struct *instance) {
          PyObject *type = Py_TYPE(instance);
          PyObject_GC_Del(instance);
      #if PY_VERSION_HEX >= 0x03080000
          // This was not needed before Python 3.8 (Python issue 35810)
          Py_DECREF(type);
      #endif
      }
      

    (由 Eddie Elizondo 在 bpo-35810 中贡献。)

  • Py_DEPRECATED() 宏已为 MSVC 实现。该宏现在必须放置在符号名称之前。

    示例

    Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
    

    (由 Zackery Spytz 在 bpo-33407 中贡献。)

  • 解释器不再假装支持扩展类型在功能版本之间的二进制兼容性。PyTypeObject 由第三方扩展模块导出时,应该具有当前 Python 版本中预期的所有槽,包括 tp_finalize(不再检查 Py_TPFLAGS_HAVE_FINALIZE,然后再读取 tp_finalize)。

    (由 Antoine Pitrou 在 bpo-32388 中贡献。)

  • 函数 PyNode_AddChild()PyParser_AddToken() 现在接受两个额外的 int 参数 end_linenoend_col_offset

  • libpython38.a 文件(允许 MinGW 工具直接链接到 python38.dll)不再包含在常规 Windows 分发中。如果需要此文件,可以使用 MinGW binutils 包中的 gendefdlltool 工具生成。

    gendef - python38.dll > tmp.def
    dlltool --dllname python38.dll --def tmp.def --output-lib libpython38.a
    

    已安装的 pythonXY.dll 的位置将取决于安装选项以及 Windows 的版本和语言。有关更多信息,请参阅 在 Windows 上使用 Python。生成的库应放置在与 pythonXY.lib 相同的目录中,通常是 Python 安装目录下的 libs 目录。

    (由 Steve Dower 在 bpo-37351 中贡献。)

CPython 字节码变更

  • 解释器循环通过将块堆栈展开的逻辑移到编译器中而得到了简化。编译器现在发出明确的指令,用于调整值堆栈并调用 breakcontinuereturn 的清理代码。

    删除了操作码 BREAK_LOOPCONTINUE_LOOPSETUP_LOOPSETUP_EXCEPT。添加了新操作码 ROT_FOURBEGIN_FINALLYCALL_FINALLYPOP_FINALLY。更改了 END_FINALLYWITH_CLEANUP_START 的行为。

    (由 Mark Shannon、Antoine Pitrou 和 Serhiy Storchaka 在 bpo-17611 中贡献。)

  • 添加了新的操作码 END_ASYNC_FOR,用于处理在 async for 循环中等待下一个项时引发的异常。(由 Serhiy Storchaka 在 bpo-33041 中贡献。)

  • MAP_ADD 现在期望值作为堆栈中的第一个元素,键作为第二个元素。进行此更改是为了使键在字典推导式中始终在值之前进行评估,如 PEP 572 所提议的。(由 Jörn Heissler 在 bpo-35224 中贡献。)

演示和工具

添加了一个用于计时各种变量访问方式的基准脚本:Tools/scripts/var_access_benchmark.py。(由 Raymond Hettinger 在 bpo-35884 中贡献。)

以下是自 Python 3.3 以来性能改进的总结

Python version                       3.3     3.4     3.5     3.6     3.7     3.8
--------------                       ---     ---     ---     ---     ---     ---

Variable and attribute read access:
    read_local                       4.0     7.1     7.1     5.4     5.1     3.9
    read_nonlocal                    5.3     7.1     8.1     5.8     5.4     4.4
    read_global                     13.3    15.5    19.0    14.3    13.6     7.6
    read_builtin                    20.0    21.1    21.6    18.5    19.0     7.5
    read_classvar_from_class        20.5    25.6    26.5    20.7    19.5    18.4
    read_classvar_from_instance     18.5    22.8    23.5    18.8    17.1    16.4
    read_instancevar                26.8    32.4    33.1    28.0    26.3    25.4
    read_instancevar_slots          23.7    27.8    31.3    20.8    20.8    20.2
    read_namedtuple                 68.5    73.8    57.5    45.0    46.8    18.4
    read_boundmethod                29.8    37.6    37.9    29.6    26.9    27.7

Variable and attribute write access:
    write_local                      4.6     8.7     9.3     5.5     5.3     4.3
    write_nonlocal                   7.3    10.5    11.1     5.6     5.5     4.7
    write_global                    15.9    19.7    21.2    18.0    18.0    15.8
    write_classvar                  81.9    92.9    96.0   104.6   102.1    39.2
    write_instancevar               36.4    44.6    45.8    40.0    38.9    35.5
    write_instancevar_slots         28.7    35.6    36.1    27.3    26.6    25.7

Data structure read access:
    read_list                       19.2    24.2    24.5    20.8    20.8    19.0
    read_deque                      19.9    24.7    25.5    20.2    20.6    19.8
    read_dict                       19.7    24.3    25.7    22.3    23.0    21.0
    read_strdict                    17.9    22.6    24.3    19.5    21.2    18.9

Data structure write access:
    write_list                      21.2    27.1    28.5    22.5    21.6    20.0
    write_deque                     23.8    28.7    30.1    22.7    21.8    23.5
    write_dict                      25.9    31.4    33.3    29.3    29.2    24.7
    write_strdict                   22.9    28.4    29.9    27.5    25.2    23.1

Stack (or queue) operations:
    list_append_pop                144.2    93.4   112.7    75.4    74.2    50.8
    deque_append_pop                30.4    43.5    57.0    49.4    49.2    42.5
    deque_append_popleft            30.8    43.7    57.3    49.7    49.7    42.8

Timing loop:
    loop_overhead                    0.3     0.5     0.6     0.4     0.3     0.3

这些基准测试是在运行 macOS 64 位构建的 Intel® Core™ i7-4960HQ 处理器 上测量的,可在 python.org 找到。基准脚本以纳秒显示计时。

Python 3.8.1 的显著变化

由于重大的安全问题,不再支持 asyncio.loop.create_datagram_endpoint()reuse_address 参数。这是由于 SO_REUSEADDR 套接字选项在 UDP 中的行为。有关更多详细信息,请参见 loop.create_datagram_endpoint() 的文档。(由 Kyle Stanley、Antoine Pitrou 和 Yury Selivanov 在 bpo-37228 中贡献。)

Python 3.8.2 的显著变化

修复了 shutil.copytree()ignore 回调中的回归。参数类型现在又变回 str 和 List[str]。(由 Manuel Barkhau 和 Giampaolo Rodola 在 gh-83571 中贡献。)

Python 3.8.3 的显著变化

为了防止与编译器标志冲突,__future__ 模块中未来标志的常量值已更新。以前 PyCF_ALLOW_TOP_LEVEL_AWAITCO_FUTURE_DIVISION 冲突。(由 Batuhan Taskaya 在 gh-83743 中贡献。)

Python 3.8.8 的显著变化

早期 Python 版本允许在 urllib.parse.parse_qs()urllib.parse.parse_qsl() 中同时使用 ;& 作为查询参数分隔符。出于安全考虑,并为了符合最新的 W3C 建议,这已更改为只允许使用单个分隔符键,默认值为 &。此更改也影响 cgi.parse()cgi.parse_multipart(),因为它们在内部使用了受影响的函数。有关更多详细信息,请参阅它们各自的文档。(由 Adam Goldschmidt、Senthil Kumaran 和 Ken Jin 在 bpo-42967 中贡献。)

Python 3.8.9 中的显著变化

一项安全修复程序改变了 ftplib.FTP 的行为,使其在建立被动数据通道时不再信任远程服务器发送的 IPv4 地址。我们改为重用 FTP 服务器的 IP 地址。对于需要旧行为的特殊代码,请将 FTP 实例上的 trust_server_pasv_ipv4_address 属性设置为 True。(参见 gh-87451)

Python 3.8.10 的显著变化

macOS 11.0 (Big Sur) 和 Apple Silicon Mac 支持

自 3.8.10 起,Python 现在支持在 macOS 11 (Big Sur) 和 Apple Silicon Mac (基于 ARM64 架构) 上构建和运行。现在提供了一个新的通用构建变体 universal2,可以原生支持 ARM64Intel 64 的一套可执行文件。请注意,Python 3.9 的此向后移植版本不包含对“弱链接”的支持,即构建针对较新 macOS 版本的二进制文件,这些文件通过运行时测试缺失功能也能在旧版本上正确运行;为了支持一系列 macOS 版本,请继续针对并构建在该范围内的最旧版本上。

(最初由 Ronald Oussoren 和 Lawrence D’Anna 在 gh-85272 中贡献,并由 FX Coudert 和 Eli Rykoff 修复,由 Maxime Bélanger 和 Ned Deily 向后移植到 3.8。)

Python 3.8.10 的显著变化

urllib.parse

URL 部分中存在换行符或制表符可能导致某些形式的攻击。根据更新 RFC 3986 的 WHATWG 规范,urllib.parse 中的解析器会从 URL 中去除 ASCII 换行符 \n\r 和制表符 \t,从而防止此类攻击。删除字符由新的模块级变量 urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE 控制。(参见 bpo-43882)

Python 3.8.12 的显著变化

Python API 的变化

从 Python 3.8.12 开始,ipaddress 模块不再接受 IPv4 地址字符串中的任何前导零。前导零是模糊的,并且被某些库解释为八进制表示法。例如,旧式函数 socket.inet_aton() 将前导零视为八进制表示法。现代 inet_pton() 的 glibc 实现不接受任何前导零。

(最初由 Christian Heimes 在 bpo-36384 中贡献,并由 Achraf Merzouki 向后移植到 3.8。)

3.8.14 中值得注意的安全特性

在基数不为 2(二进制)、4、8(八进制)、16(十六进制)或 32(例如基数 10(十进制))的情况下,在 intstr 之间进行转换时,如果字符串形式的位数超过限制,现在会引发 ValueError,以避免由于算法复杂性而导致的潜在拒绝服务攻击。这是针对 CVE 2020-10735 的缓解措施。此限制可以通过环境变量、命令行标志或 sys API 进行配置或禁用。请参见 整数字符串转换长度限制 文档。默认限制为字符串形式的 4300 位。

3.8.17 中的显著变化

tarfile

  • tarfile 中的提取方法和 shutil.unpack_archive() 具有新的 filter 参数,允许限制可能令人惊讶或危险的 tar 功能,例如在目标目录之外创建文件。有关详细信息,请参见 提取过滤器。在 Python 3.12 中,不带 filter 参数的使用将显示 DeprecationWarning。在 Python 3.14 中,默认将切换为 'data'。(由 Petr Viktorin 在 PEP 706 中贡献。)