Python 3.5 之后的新特性

Python 自 3.5 版本起,至当前 3.11 版本为止,变化相当大,引入了众多的新特性,了解这些变化对编写兼容性代码尤为重要。本文整理的一些版本的重要变化。

Python 3.5

此版本最早发布于 2015 年,最终版本为 3.5.10 ,发布于 2020 年。

[PEP 492] 使用 async/await 语法实现协程

PEP 492

此特性是 3.5 版本最重大的变化。自此,Python 支持 asyncawait 语法,用于代替 @asyncio.coroutine 装饰器和 yield from 语句。

[PEP 448] 通用化额外的解包操作

PEP 448

解包即 *** 操作,曾在传递列表和字典类型的函数参数时使用。新特性将这一操作推广到了其他使用场景。

python*range(4), 4 # (0, 1, 2, 3, 4)
[*range(4), 4] # [0, 1, 2, 3, 4]
{*range(4), 4} # {0, 1, 2, 3, 4}
{'x': 1, **{'y': 2}} # {'x': 1, 'y': 2}

[PEP 484] 类型提示

PEP 484

支持函数和方法的参数及返回值的类型提示:

pythondef greeting(name: str) -> str:
    return 'Hello ' + name

还有对泛型的支持:

pythonfrom typing import Mapping, Set

def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None:
    pass
pythonfrom typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

其他

Python 3.6

此版本最早发布于 2016 年,最终版本为 3.6.15 ,发布于 2021 年。

[PEP 487] 魔术方法 __init_subclass____set_name__

PEP 487

当声明或创建新的子类时,基类的 __init_subclass__ 方法将会被调用:

pythonclass PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

当一个类被创建时,type.__new__() 会扫描类变量并对其中带有 __set_name__ 方法的对象执行回调。

pythonclass A:
    def __set_name__(self, owner, name):
        print(owner, name)

class B:
    a = A() # 隐含调用 a.__set_name__(B, 'a')

[PEP 498] 格式化字符串字面值

PEP 498

新增字符串前缀 f ,实现类似 str.format() 的效果。

pythonname = "Fred"
f"He said his name is {name}."

width = 10
precision = 4
value = decimal.Decimal("12.34567")
f"result: {value:{width}.{precision}}"  # nested fields

[PEP 526] 变量注解语法

PEP 526

继 PEP 484 支持函数参数和返回值的类型提示后,此 PEP 增进了对变量类型的声明支持。

pythonprimes: List[int] = []

captain: str  # Note: no initial value!

class Starship:
    stats: Dict[str, int] = {}

[PEP 468] 保持 **kwargs 传参时的顺序

PEP 468

pythonkwargs = {'z': 1, 'a': 2, 'x': 3, 'c': 4}

def ordered_kwargs(**kwargs):
    print(kwargs)

ordered_kwargs(**kwargs) # {'z': 1, 'a': 2, 'x': 3, 'c': 4}

字典对象会保持插入时的顺序

也就是说默认的 dict() 对象已经具有 OrderedDict 的特性。此外,类属性也会保持定义时的顺序。

其他

Python 3.7

此版本最早发布于 2018 年,最新版本为 3.7.15 ,发布于 2022 年。

[PEP 562] 模块的 __getattr____dir__

PEP 562

允许在模块上定义 __getattr__() 并且当以其他方式找不到某个模块属性时将会调用它。 还可以在模块上定义 __dir__()

[PEP 557] 新增模块 dataclasses

PEP 557

提供一个类装饰器,可以为类自动添加诸如 __init__()__repr__()__eq__()__hash__() 等魔术方法。

pythonimport dataclasses

@dataclasses.dataclass
class Point:
    x: float
    y: float
    z: float = 0.0

p = Point(1.5, 2.5)
print(p)   # produces "Point(x=1.5, y=2.5, z=0.0)"

[PEP 567] 新增模块 contextvars

PEP 567

用于异步和并发环境中管理上下文变量。可以替代 threading.local()

其他

Python 3.8

此版本最早发布于 2019 年,最新版本为 3.8.15 ,发布于 2022 年。

[PEP 572] 赋值表达式

PEP 572

新增 := 赋值表达式语法,表达式将右值赋予左值并返回该值。

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

[PEP 570] 仅限位置形参

PEP 570

函数参数中的 / 左边的参数为位置参数,不能作为关键字参数使用;而 * 右边的参数必须作为关键字参数使用。

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

# valid
f(10, 20, 30, d=40, e=50, f=60)
# invalid
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 3.9

此版本最早发布于 2020 年,最新版本为 3.9.15 ,发布于 2022 年。

[PEP 584] 字典合并操作符 |

PEP 584

pythond = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e # {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
e | d # {'cheese': 3, 'aardvark': 'Ethel', 'spam': 1, 'eggs': 2}

[PEP 616] 字符串新增 removeprefix()removesuffix() 方法

PEP 616

pythondef strip_quotes(text):
    return text.removeprefix('"').removesuffix('"')

其他

Python 3.10

此版本最早发布于 2021 年,最新版本为 3.10.8 ,发布于 2022 年。

[PEP 617] 带括号的上下文管理器

PEP 617

此特性在 3.9 版本时已经存在,但是在 3.10 版本时可以正式使用。with 语句括号内支持多行表达式:

pythonwith (
    open("a_really_long_foo") as foo,
    open("a_really_long_baz") as baz,
    open("a_really_long_bar") as bar
):
    pass

[PEP 604] 允许 X | Y 形式的联合类型写法

PEP 604

pythonisinstance(5, int | str)

def f(list: List[int | str], param: int | None) -> float | str:
    pass

int | str == typing.Union[int, str] # true

[PEP 634] 结构模式匹配

PEP 634PEP 635PEP 636

新增 match...case 语法。

pythoncommand = 'drop doc1 doc2 doc3'

match command.split():
    case ["quit"]: pass
    case ["go", direction]: pass
    case ["drop", *objects]: pass
    case _:
        print(f"Sorry, I couldn't understand {command!r}")

其他

Python 3.11

此版本今年刚发布 3.11.0 版本。

[PEP 654] ExceptionGroupexcept*

PEP 654

ExceptionGroup 可以包含多个异常。except* 可以用来捕获 ExceptionGroup 中包含的异常。

pythontry:
    raise ExceptionGroup('error', [TypeError(1), ValueError(2)])
except* TypeError as e:
    print(f'*TypeError: {e!r}')
except* ValueError as e:
    print(f'*ValueError: {e!r}')

[PEP 678] 新增方法 BaseException.add_note()

PEP 678

可以为异常添加字符串类型的 note ,并可以通过 __notes__ 属性读取。

pythonexc = ValueError()
exc.add_note('note1')
exc.add_note('note2')
exc.add_note('note3')
print(exc.__notes__) # ['note1', 'note2', 'note3']

其他

总结

迄今为止,asyncio 模块已经相当完善了,以后的网络开发可以完全基于异步和协程。最近几次版本升级,更新重点还是在 typing 模块。感觉 Python 的泛型还不够完善,且过于复杂。typing 模块还不够稳定,不建议深度使用。千呼万唤的模式匹配语法终于在 3.10 里实现了,以后不用再写可悲的 if 嵌套。