语法更新摘要
发布于 | 2023-12-23 23:59 | ||
更新于 | 2024-12-27 17:54 | ||
保质期 | 2025-12 | ||
新鲜度 | 94% |
仅供粗略参考
本文仅仅只是粗略摘要,具体请参阅自 2.0 以来的全部新变化或下文附上的每个版本的所有变化。
3.13 版本
无语法层面变动。
3.12 版本
泛型标注
类和函数现在支持通过 []
来标注泛型。
def max[T](numbers: list[T]) -> T:
...
class CustomList[T]:
def __getitem__(self, index: int, /) -> T:
...
def append(self, element: T) -> None:
...
type 语句
现在可以这样创建类型别名:
type Point = tuple[float, float]
对于泛型可以这样创建:
type Point[T] = tuple[T, T]
向下兼容
Vector = list[float]
或者
from typing import TypeAlias
Vector: TypeAlias = list[float]
而 type
语句可以创建更为复杂的类型别名,比如声明 TypeVarTuple
和 ParamSpec
形参,以及带边界或约束的 TypeVar
形参:
type IntFunc[**P] = Callable[P, int] # ParamSpec
type LabeledTuple[*Ts] = tuple[str, *Ts] # TypeVarTuple
type HashableSequence[T: Hashable] = Sequence[T] # 带边界的 TypeVar
type IntOrStrSequence[T: (int, str)] = Sequence[T] # 带约束的 TypeVar
f-字符串嵌套
1、允许重复使用引号
print(f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}") # 打印 '2'
2、允许嵌入多行表达式和注释
print(f"This is the playlist: {", ".join([
'Take me back to Eden', # My, my, those eyes like fire
'Alkaline', # Not acid nor alkaline
'Ascensionism' # Take to the broken skies at last
])}")
# 打印 'This is the playlist: Take me back to Eden, Alkaline, Ascensionism'
3、允许使用反斜框和 unicode 字符
songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism']
print(f"This is the playlist: {"\n".join(songs)}")
# 打印:
# This is the playlist: Take me back to Eden
# Alkaline
# Ascensionism
print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")
# 打印:
# This is the playlist: Take me back to Eden♥Alkaline♥Ascensionism
3.11 版本
异常组与 except*
程序能够同时引发和处理多个不相关的异常。内置类型
ExceptionGroup
和BaseExceptionGroup
使得将异常划分成组并一起引发成为可能,新添加的except*
是对except
的泛化语法,这一语法能够匹配异常组的子组。
3.10 版本
类型联合
现在可以通过 X | Y
的方式代替 typing.Union[X, Y]
来表示类型联合。
比如表示参数和返回值可能是一个整数或一个浮点数,之前需要:
from typing import Union
def square(number: Union[int, float]) -> Union[int, float]:
return number ** 2
现在只需要:
def square(number: int | float) -> int | float:
return number ** 2
match-case 语句
match
会命中至多一个 case
并且只会执行该 case
的内容,如果都未命中则查找 case _
来执行,如果其未定义则不执行任何代码。
from typing import Optional
class Furry:
gender: Optional[bool]
def get_gender_display() -> str:
match self.gender:
case True:
return "雄性"
case False:
return "雌性"
case _:
return "(未知)"
case
子句如果不存在 .
会被强制作为变量名解析,因此当你需要匹配内置类型时,应当使用模块 builtins
:
import builtins
match type(obj):
case builtins.int:
print("对象是一个整数")
case builtins.float:
print("对象是一个浮点数")
case builtins.complex:
print("对象是一个复数")
case _:
print("对象类型未知")
可以像类型联合那样通过 |
同时匹配多个值:
def http_error(status):
match status:
case 401 | 403 | 404:
return "Not allowed"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
匹配模式允许解包使用,以及在 case
中定义变量:
from typing import Tuple
def show(point: Tuple[float, float]):
match point:
case (0, 0):
print("处于原点")
case (0, y):
print(f"处于Y轴上,刻度为 {y}")
case (x, 0):
print(f"处于X轴上,刻度为 {x}")
case (x, y):
print(f"坐标为:X={x},Y={y}")
case _:
raise ValueError("参数不是一个点")
带圆括号的上下文管理器
支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。
with (
CtxManager1() as example1,
CtxManager2() as example2,
CtxManager3() as example3,
):
...
with (CtxManager() as example):
...
with (
CtxManager1(),
CtxManager2()
):
...
with (CtxManager1() as example,
CtxManager2()):
...
with (CtxManager1(),
CtxManager2() as example):
...
with (
CtxManager1() as example1,
CtxManager2() as example2
):
...
3.9 版本
多项集泛型
标注 list
、dict
、set
、queue.Queue
等标准库的多项集类型时不再需要从 typing
导入对应的大写形式类型名如 List
、Dict
、Set
。
def choice(numbers: list[int]) -> int:
...
如果需要兼容旧版本,可以考虑导入 __future__
:
from __future__ import annotations
def choice(numbers: list[int]) -> int:
...
3.8 版本
赋值表达式
新增了赋值表达式符号 :=
,又叫海象运算符。
from datetime import date
def parse_date(string: str) -> date:
"""
解析六位或八位的日期。
"""
if (length := len(string)) not in (8, 6):
raise ValueError
if length == 8:
year, month, day = string[:4], string[4:6], string[6:8]
else:
year, month, day = string[:2], string[2:4], string[4:6]
year = f'19{year}' if year > '50' else f'20{year}'
return date(int(year), int(month), int(day))
parse_date('20120520')
语句比较简单时不必加括号:
string = input()
if length := len(string): # 使用 length 作为条件判断
raise ValueError("输入不能为空")
赋值后可以立即使用:
username = 'aixcyi'
password = '摸一凹喵'
if (user := check(username, password)) is None or user.delete_at is not None:
raise ValueError('用户不存在或密码错误')
仅限位置形参
新增了一个函数形参 /
用来分隔其它形参。
参数位置 | 值类型 | 拥有默认值 | 按位置传参 | 按关键字传参 |
---|---|---|---|---|
def func(arg, /, *, **): | 实参类型 | 允许 | 允许 | 不允许 |
def func(/, arg, *, **): | 实参类型 | 允许 | 允许 | 允许 |
def func(/, *args, **): | 元组 | 不允许 | 允许 | 不允许 |
def func(/, *, arg, **): | 实参类型 | 允许 | 不允许 | 允许 |
def func(/, *, **kwargs): | 字典 | 不允许 | 不允许 | 允许 |
提示
*args
和 **kwargs
是约定俗成的形参命名。
def serialize(data, /, many=False, *, raise_exception=False, **others):
pass
备注
由于 /
前面的参数不会被公开为可用关键字,这些形参名仍可在 **kwargs
中使用。
def serialize(data, /, many=False, *, raise_exception=False, **others):
print("收到的数据", data)
print("额外的数据", others["data"])
serialize({"weblog": "blog.aixcyi.cn"}, data={"author": "aixcyi"})
# 打印:
# 收到的数据 {'weblog': 'blog.aixcyi.cn'}
# 额外的数据 {'author': 'aixcyi'}
f-字符串因变量
允许用 f"{expr=}"
形式的 f-字符串 为表达式的求值结果添加因变量名称。
from datetime import date, timedelta
a = 355
b = 113
y = a / b
print(f'{y=}')
# y=3.1415929203539825
today = date(2023, 12, 22)
tomorrow = today + timedelta(days=1)
print(f'{tomorrow=:%Y-%m-%d}')
# tomorrow=2023-12-23
注意
带有作用域时,会连同作用域一起输出,比如 self
:
from dataclasses import dataclass
@dataclass
class Order:
pk: int
tracking_no: str
def __repr__(self):
return f'<Order({self.pk}) {self.tracking_no=}>'
repr(Order(pk=1, tracking_no='1703292327000'))
# "<Order(1) self.tracking_no='1703292327000'>"
finally 中使用 continue
在之前版本中
continue
语句不允许在finally
子句中使用,这是因为具体实现存在一个问题。在 Python 3.8 中此限制已被取消。
3.7 版本
类型标注延迟求值
意思是可以将未定义的符号作为标注。
from __future__ import annotations
class Book:
def copy(self) -> Book:
pass
如果不希望导入 __future__
,那么可以:
class Book:
def copy(self) -> "Book":
pass
允许过量参数
现在可以将超过 255 个的参数传递给一个函数,而现在一个函数也可以拥有超过 255 个形参。
换句话说就是现在可以放心地解包一个超长列表 foo(*big_list)
。
async 与 await
3.6 版本
f-字符串
又称格式化字符串字面值。添加前缀 f
的字符串字面值可以内嵌表达式,来对值进行格式化和无感拼接。
警告
嵌套的表达式内,字符串使用的引号不能与表达式外面的字符串相同。3.12 开始没有这个限制。
f"{obj!s}"
相当于str(obj)
;f"{obj!r}"
相当于repr(obj)
;f"{qty:x}"
相当于"{x}".format(qty)
;f"{now:%H:%M:%S}"
相当于now.strftime("%H:%M:%S")
。
from datetime import date
today = date(2023, 12, 24)
level = 'DEBUG'
message = '喵' * 9
print(f'[{level}] [{today:%Y-%m-%d}]: {message}')
# 打印
# [DEBUG] [2023-12-24]: 喵喵喵喵喵喵喵喵喵
变量标注
现在可以对当前作用域的变量进行类型标注。
备注
标注后,只要还没有赋值,都无法使用这个变量,不过可以被检测到。
全局作用域示例:
from typing import List, Set
primes: List[int] = []
factories: Set[int] # 标注但不赋值这种行为是允许的
print(__annotations__) # {'primes': typing.List[int], 'factories': typing.Set[int]}
print(factories)
# NameError: name 'factories' is not defined
类作用域示例:
from datetime import date, timedelta
class Cat:
dead: bool
birth: date = date(1935, 11, 1)
def __init__(self, today: date):
self.age: int = (today - self.birth) // timedelta(days=365)
self.height: float
print(self.__annotations__) # {'dead': <class 'bool'>, 'birth': <class 'datetime.date'>}
print(self.dead)
Cat(date.today())
# AttributeError: 'Cat' object has no attribute 'dead'
函数作用域中,未赋值但有标注的变量需要通过Signature
检测。
def calc(a: int, b: int):
result: int = a + b
summary: float
print(calc.__annotations__) # {'a': <class 'int'>, 'b': <class 'int'>}
print(summary)
return result
meow(1, 2)
# UnboundLocalError: cannot access local variable 'summary' where it is not associated with a value
数字下划线
可以在数字字面值中使用下划线,以改善阅读体验。
注意
下划线不能连续使用、不能在小数点两侧、不能在字面值开头。
assert 21_0000_0000 == 2100000000
assert 0x_0314_1592 == 0x03141592
3.5 版本
协程 async 和 await 语句
PEP 492通过添加可等待对象、协程函数、异步迭代和异步上下文管理器极大地改善了 Python 对异步编程的支持。
协程函数是使用新的
async def
语法来声明的
async def coro():
return 'spam'
在协程函数内部,新的
await
表达式可用于挂起协程的执行直到其结果可用。任何对象都可以被 等待,只要它通过定义__await__()
方法实现了awaitable协议。
注意
async
和 await
到 3.7 才成为关键字。
更多解包
可以在函数调用中使用任意多个 *
和 **
解包:
print(*[1], *[2], 3, *[4, 5])
# 打印
# 1 2 3 4 5
列表、元组、集合与字典的 字面值 表达式也分别可以使用任意多个 *
与 **
解包:
*range(4), 4
# 打印 (0, 1, 2, 3, 4)
[*range(4), 4]
# 打印 [0, 1, 2, 3, 4]
{*range(4), 4, *(5, 6, 7)}
# 打印 {0, 1, 2, 3, 4, 5, 6, 7}
{'x': 1, **{'y': 2}}
# 打印 {'x': 1, 'y': 2}
矩阵运算符
二元运算符 @
目前(截至 2024 年底)只为第三方库矩阵乘法的计算而设计,Python 内置的类型并不支持该运算,开发者可以定义对应的魔术方法 __matmul__()
、__rmatmul__()
、__imatmul__()
来为自定义对象模拟该运算。
类型标注 标准化
引入了 typing 模块提供类型标注的 标准定义 和工具,以及一些对于注释不可用的情况的约定。
提示
标注存储在__annotations__
属性中。
备注
List[str]
指列表中的元素都是 str
类型;Tuple[int, str]
指元组中第一个元素是 int
类型,第二个元素是 str
类型;Tuple[str, ...]
指元组中所有元素都是 str
类型。
from typing import List, Tuple, Union
def register(username: str, password: str, age: int, gender: bool) -> dict:
# 提示 username 和 password 应该是一个字符串
# 提示 age 应该是一个整数
# 提示 gender 应该是一个布尔值
# 提示函数返回值应该是一个字典
...
def choice(numbers: List[int]) -> int:
# 提示 numbers 是一个由整数组成的列表
# 提示函数返回值应该是一个整数
...
def login(certificate: Union[str, Tuple[int, str]]:
# 提示 certificate
# 可能是一个字符串,
# 也可能是一个由一个整数和一个字符串组成的元组
...
login('1a4384bbdb91756e66f8abdfde8a0075')
login((1, 'admin'))
3.4 版本
无语法层面变动。
3.3 版本
无语法层面变动。
委托子生成器
提示
对于简单的迭代器,yield from iterable
本质上只是 for item in iterable: yield item
的简写。
就是将自己的 yield
操作委托给自己内部的子生成器进行。
def generate(x):
yield from range(x, 0, -1)
yield from range(x)
yield x
list(generate(5))
# 打印
# [5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5]
3.2 版本
无语法层面变动。
3.1 版本
无语法层面变动。
3.0 版本
Python 3.0 是第一个故意不向后兼容的版本,更新太多,由于我没有玩过 Python 2.x,所以这一段概括得并不准确甚至很多缺漏,也欢迎提 issue 告知,或者提 pull-request 协助增补。
移除打印语句
改用 print()
函数调用,不再支持 print
语句。
一般情况的传参都可以直接加个括号,而对于输出应该通过参数传递:
print "出生年月", 1970, 1
print (1970, 1)
print >>sys.stderr, "断言:API接口缺少参数"
新版写法:
print("出生年月", 1970, 1)
print((1970, 1))
print("断言:API接口缺少参数", file=sys.stderr)
简化比较、改用新不等号
移除了内置函数 cmp()
及对魔术方法 __cmp__()
的支持,移除不等号 <>
,改用 !=
。
虽然可以通过 (a > b) - (a < b)
得到原来 cmp(a, b)
的结果,但更建议使用语义更为明确的 <
<=
!=
==
>=
>
直接比较。
定制对象时,可以通过 __lt__()
实现 <
,通过 __eq__()
实现 ==
和 !=
,两者配合可以实现 <
<=
!=
==
>=
>
;另外,通过 __hash__()
可以判断两个对象是否为同一个。
类型标注
PEP 3107提议对参数和返回值进行标注,不过该提案(截止 3.4)还没有标准语义。
提示
标注存储在__annotations__
属性中。
对参数的标注:
def foo(a: expression, b: expression = 5, c=None):
# a 标注为 expression;
# b 标注为 expression 同时默认值为 5;
# c 没有标注,默认值为 None。
...
def foo(*args: expression, **kwargs: expression):
# 元组 args 中的每个元素都标注为 expression;
# 字典 kwargs 中的每个值都标注为 expression。
...
对返回值的标注:
def foo() -> expression:
...
元类(Meta 类)
之前的写法不再支持:
class Cat:
__metaclass__ = Animal
...
class Husky(Dog):
__metaclass__ = Animal
...
现在元类的用法是:
class Cat(metaclass=Animal):
...
class Husky(Dog, metaclass=Animal):
...
列表推导式
以下推导式会产生歧义,因此不再支持:
[... for var in item1, item2, ...]
如果需要枚举元组产生列表,应当:
[... for var in (item1, item2, ...)]
而如果希望将推导出的元素嵌入列表,则:
[*(... for var in items), item2, ...]
不再允许元组参数解包
普通函数也受到影响,但对于匿名函数影响更大,比如这种方式不再可用:
births = [(1997, 7), (1999, 12)]
birthday = map(lambda (y, m): str(y) + '.' + str(m), births)
而要写成
births = [(1997, 7), (1999, 12)]
birthday = map(lambda d: str(d[0]) + '.' + str(d[1]), births)
字面值前缀与后缀
- 整数文字 不再支持 尾随
l
或者L
,现在int
支持无限长度,直至内存溢出。 - 字符串文字 不再需要 前缀
u
或者U
,但仍可以保留该前缀。