本文包含 Python 3.0 以来语法上的更新,主要包括类型标注系统以及一些语句语法的变动。
仅供粗略参考
本文仅仅只是粗略摘要,具体请参阅自 2.0 以来的全部新变化或下文附上的每个版本的所有变化。
t-字符串是 f-字符串 的泛化(generalization),基于后者的语法和规则构建,产生 string.templatelib.Template 类型的对象(而非 str);通过这种类型,可以访问包含原始信息的模板属性:静态字符串、插值表达式,以及来自原始作用域的值。
提醒
Template 类型早在 Python 3.0 就已经存在了,不必担心其向下兼容的能力。
except 与 except* 可不带括号 import requests
try:
resp = requests.get('https://127.0.0.1:8000/server-status/').json()
code = resp['code']
data = resp['data']['RunningTime']
except requests.exceptions.JSONDecodeError:
pass
except KeyError, TypeError:
passfinally 块中禁用 return break continue 从这个版本开始,再在 finally 块中使用 return、break 或 continue 会抛出 SyntaxWarning 异常。详见 PEP 765。
相关信息
Python 3.8 开始允许在 finally 块中使用 continue。
无语法层面变动。
类和函数现在支持通过 [] 来标注泛型。
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] # 带约束的 TypeVar1、允许重复使用引号
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♥Ascensionismexcept* 程序能够同时引发和处理多个不相关的异常。内置类型
ExceptionGroup和BaseExceptionGroup使得将异常划分成组并一起引发成为可能,新添加的except*是对except的泛化语法,这一语法能够匹配异常组的子组。
现在可以通过 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 ** 2match 会命中至多一个 case 并且只会执行该 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
):
...标注 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:
...新增了赋值表达式符号 := ,又叫海象运算符。
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.navifox.net"}, data={"author": "aixcyi"})
# 打印:
# 收到的数据 {'weblog': 'blog.navifox.net'}
# 额外的数据 {'author': 'aixcyi'}允许用 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 中此限制已被取消。
相关信息
Python 3.14 开始禁止在 finally 块中使用 continue。
意思是可以将未定义的符号作为标注。
特性从未被实现
该特性于 Python 3.7 提出支持,Python 3.11 表示无限期搁置,因此从始至终都需要 from __future__ import annotations(截至 2024 年底)。
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 又称格式化字符串字面值。添加前缀 f 的字符串字面值可以内嵌表达式,来对值进行格式化和无感拼接。
提示
嵌套的表达式内,字符串使用的引号不能与表达式外面的字符串相同。Python 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下划线不能
连续使用
print(210000___0000)在小数点两侧
print(3_.14)
print(3._14)在字面值开头、结尾。
print(_2100000000)
print(2100000000_)async 和 await 语句 PEP 492 通过添加 可等待对象 、协程函数 、异步迭代 和 异步上下文管理器 极大地改善了 Python 对异步编程的支持。
协程函数是使用新的
async def语法来声明的:pythonasync def coro(): return 'spam'在协程函数内部,新的
await表达式可用于挂起协程的执行直到其结果可用。任何对象都可以被 等待,只要它通过定义__await__()方法实现了 awaitable 协议。
提醒
async 和 await 到 Python 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'))无语法层面变动。
无语法层面变动。
提示
对于简单的迭代器,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]无语法层面变动。
无语法层面变动。
备注
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 提议对参数和返回值进行标注,不过该提案直到 Python 3.5 才有标准语义。
备注
标注存储在 __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:
...之前的写法不再支持:
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,但仍可以保留该前缀。