3.7
PEP 563:延迟的标注求值
- 标注只能使用在当前作用域中已经存在的名称,也就是说,它们不支持任何形式的前向引用;而且——
- 标注源码对 Python 程序的启动时间有不利的影响。
- 在可用性方面,标注现在支持向前引用,以使以下句法有效:
class C:
@classmethod
def from_string(cls, source: str) -> C:
...
def validate_b(self, obj: B) -> bool:
...
class B:
...
此项功能仅仅是标注作用并不能起到限制的作用。
3.8
赋值表达式
新增的语法 :=
可在表达式内部为变量赋值。 它被昵称为“海象运算符”因为它很像是 海象的眼睛和长牙。
在这个示例中,赋值表达式可以避免调用 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 了解详情。
仅限位置形参
新增了一个函数形参语法 /
用来指明某些函数形参必须使用仅限位置而非关键字参数的形式。 这种标记语法与通过 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 了解详情。
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
其他语言特性修改
for i in range(10):
try:
if i == 5:
1/0
finally:
continue
>>> def parse(family):
lastname, *members = family.split()
return lastname.upper(), *members
>>> parse('simpsons homer marge bart lisa sally')
('SIMPSONS', 'homer', 'marge', 'bart', 'lisa', 'sally')
3.9
字典合并与更新运算符
合并 (|
) 与更新 (|=
) 运算符已被加入内置的 dict 类。 它们为现有的 dict.update
和 {**d1, **d2}
字典合并方法提供了补充。
示例:
>>> x = {"key1": "value1 from x", "key2": "value2 from x"}
>>> y = {"key2": "value2 from y", "key3": "value3 from y"}
>>> x | y
{'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
>>> y | x
{'key2': 'value2 from x', 'key3': 'value3 from y', 'key1': 'value1 from x'}
新增用于移除前缀和后缀的字符串方法
增加了 str.removeprefix(prefix) 和 str.removesuffix(suffix) 用于方便地从字符串移除不需要的前缀或后缀。 也增加了 bytes
, bytearray
以及 collections.UserString
的对应方法。
>>> a = "123skjsdfprod"
>>> a.removeprefix("123")
'skjsdfprod'
>>> a.removeprefix("123").removesuffix("prod")
'skjsdf'
标准多项集中的类型标注泛型
在类型标注中现在你可以使用内置多项集类型例如 list
和 dict
作为通用类型而不必从 typing
导入对应的大写形式类型名 (例如 List
和 Dict
)。 标准库中的其他一些类型现在同样也是通用的,例如 queue.Queue
。
示例:
def greet_all(names: list[str]) -> None:
for name in names:
print("Hello", name)