Python 3.8稳定版于2019年10月14日发布,相信还有很多项目并没有升级到3.8版本,今天有一个项目经理问我:我们项目组要升级到Python3.8,3.8和3.7有什么区别呢?你能不能跟我讲一下。我问了他为什么要升级,然后对他说:你就当3.7用就行了。他对我说:你们肯定也要升级,你赶紧了解一下,然后给我们讲一讲。虽然python2.7的项目我们依然在开发,但是谁知道什么时候就得必须升级了呢。所以我就专门看了一下3.8的升级特性,在这里做一个记录。以下为Python3.8相比3.7的新增特性。

赋值表达式

新增的语法 := 可在表达式内部为变量赋值。 它被昵称为“海象运算符”因为它很像是海象的眼睛和长牙。

在这个示例中,赋值表达式可以避免调用 len() 两次:

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

类似的益处还可出现在正则表达式匹配中需要使用两次匹配对象的情况中,一次检测用于匹配是否发生,另一次用于提取子分组:

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

此运算符也适用于配合 while 循环计算一个值来检测循环是否终止,而同一个值又在循环体中再次被使用的情况:

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

另一个值得介绍的用例出现于列表推导式中,在筛选条件中计算一个值,而同一个值又在表达式中需要被使用:

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

不过这个运算符可读性不高,因此一定要使用在比较清晰的场景

仅限位置形参

新增了一个函数形参语法 / 用来指明某些函数形参必须使用仅限位置而非关键字参数的形式。 这种标记语法与通过 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不能是关键字参数f(10, 20, 30, 40, 50, f=60)           # e必须是关键字参数

这种标记形式的一个用例是它允许纯 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 ways10 20 {'a': 1, 'b': 2, 'c': 3}

这极大地简化了需要接受任意关键字参数的函数和方法的实现。 例如,collections 模块的一段代码:

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

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

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

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


Python3.8之前会生成__pycache__子目录


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

增加 = 说明符用于 f-string。 形式为 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

新增模块

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

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

这个在进行函数式编程的时候会用到,一般情况下使用比较少

改进的部分模块

  • asyncio

asyncio.run() 已经从暂定状态晋级为稳定 API。 此函数可被用于执行一个 coroutine 并返回结果,同时自动管理事件循环

import asyncioasync def main():    await asyncio.sleep(0)    return 42asyncio.run(main())

这大致等价于:

import asyncioasync def main():    await asyncio.sleep(0)    return 42loop = asyncio.new_event_loop()asyncio.set_event_loop(loop)try:    loop.run_until_complete(main())finally:    asyncio.set_event_loop(None)    loop.close()

精简了非常多的代码,现在可以作为首推方式使用

  • collections

collections.namedtuple() 的 _asdict() 方法现在将返回dict而不是 collections.OrderedDict。OrderedDict为有序字典,目前普通字典已经保证具有确定的元素顺序。如果还需要 OrderedDict 的额外特性,建议的解决方案是将结果转换为需要的类型: OrderedDict(nt._asdict())。

  • cProfile

cProfile.Profile 类现在可被用作上下文管理器。 在运行时对一个代码块实现性能分析:

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

还有functools、inspect、itertools、operator等模块,我们后面单独介绍,这些都是Python中非常方便的模块。