# 版本

Python 的版本主要分为 2.×3.× 两个系列。

  • Python3 计划每年发布一个新的子版本,每次只增加两三种新语法。
  • 使用时当然选择越新的 Python 版本越好,版本越老的代码越难维护。
  • 维护老版本的代码时,需要了解各版本之间的主要差异。有时看到一些代码的语法特点,可以大致猜出它是什么版本。

# Python2 与 Python3

  • 从 Python2 到 Python3 是一个大版本升级,有很多不向下兼容的差异,导致很多 Python2 的代码不能被 Python3 解释器运行,或者反之。
  • 2020 年,Python 官方停止对 Python2 的维护,所有老代码都会超过保质期。
差异点 Python2 Python3
输出方式 用 print 关键字,比如 print "Hello" 用 print() 函数,比如 print("Hello")
输入方式 用 raw_input() 函数 用 input() 函数
字符串的编码格式 默认采用 ASCII 默认采用 Unicode
源文件的编码格式 默认采用 ASCII ,因此使用中文时要在源文件开头加上一行 # -*- coding: utf-8 -*- 默认采用 uft-8
格式化字符串的方式 用 % ,比如 "Hello, %s" % ("World") 用 str.format() 方法,比如 "Hello, {}".format('World')
... ... ...

# Python2 系列

# Python3 系列

# Python3.4

  • 2014 年发布:https://www.python.org/downloads/release/python-340/ (opens new window)

  • 采用 pip 作为 Python 包的默认安装方式。

  • 增加标准库 pathlib ,用于按面向对象的方式操作文件路径。如下:

    >>> from pathlib import Path
    >>> p = Path('/root/test/1.py')
    >>> p.name
    '1.py'
    >>> p.suffix
    '.py'
    >>> p.exists()
    False
    
  • 增加标准库 enum ,用于定义枚举类。如下:

    >>> from enum import Enum
    >>>
    >>> class Test(Enum):
    ...     a = 1
    ...     b = 2
    ...     c = 3
    ...
    >>> Test.a
    <Test.a: 1>
    >>> Test['a']     # 可按名字索引
    <Test.a: 1>
    >>> list(Test)    # 可迭代
    [<Test.a: 1>, <Test.b: 2>, <Test.c: 3>]
    
  • 增加标准库 asyncio ,用于实现异步 IO 。它取代了旧的 asyncore 库。

  • 增加标准库 statistics ,提供了求平均值、中位数、方差等运算的函数。

  • 增加标准库 tracemalloc ,用于跟踪内存分配的情况,方便调试。

# Python3.5

  • 2015 年发布:https://www.python.org/downloads/release/python-350/ (opens new window)

  • 扩展了迭代拆包运算符 * 、字典拆包运算符 ** 的语法。

    • 可以在元组、列表、集合、字典表达式中使用:
      >>> *range(4)
      SyntaxError: can't use starred expression here
      >>> *range(4), 4
      (0, 1, 2, 3, 4)
      >>> [*range(4), 4]
      [0, 1, 2, 3, 4]
      >>> {'a': 1, **{'b': 2}}
      {'a': 1, 'b': 2}
      
    • 可以同时多次使用:
      >>> print(*[1], *[2], *{'c': 3})
      1 2 c
      >>> dict(**{'a': 1}, **{'b': 2})
      {'a': 1, 'b': 2}
      
  • 增加语法:使用 Python2 风格的百分号 % 来格式化字符串。

    >>> '%a' % 3.14
    '3.14'
    >>> b'%a' % 3.14
    b'3.14'
    
  • 增加关键字 asyncawait ,用于定义协程:

    async def read_db(db):
        data = await db.fetch('SELECT ...')
    
  • 增加语法:类型注释(type annotations)。

    • 对于函数,可以用冒号 : 添加形参的注释,用 -> 添加返回值的注释。这些注释会存储在函数的 __annotations__ 属性中:
      >>> def fun1(a, b: "字符串或 None", c: int = 0) -> int:
      ...     pass
      ...
      >>> fun1.__annotations__
      {'b': '字符串或 None', 'c': <class 'int'>, 'return': <class 'int'>}
      
    • 对于变量,可以用冒号 : 添加注释:
      >>> x : int             # 该语句可以执行,但并不会创建变量 x
      >>> x
      NameError: name 'x' is not defined
      >>> x : int = 1         # 添加类型注释并赋值
      >>> x
      1
      
    • 类型注释可以是任意可执行的表达式。例如:
      >>> x : int             # 该注释为一个类型,不会强制类型检查,但可以供 IDE 等工具进行静态类型检查
      >>> x : 'Hello'         # 该注释为一个 str 值
      >>> x : print('Hello')  # 该注释为一个语句
      Hello
      
  • 增加标准库 typing ,定义了一些类型,常用于类型注释。

  • 增加标准库 zipapp ,用于将 Python 脚本打包成可执行的归档文件,扩展名为 .pyz 。

# Python3.6

  • 2016 年发布:https://www.python.org/downloads/release/python-360/ (opens new window)

  • dict 中的元素以前是按 key 顺序存储:

    >>> {2:'', 1:''}
    {1: '', 2: ''}
    

    现在会按插入顺序存储:

    >>> {2:'', 1:''}
    {2: '', 1: ''}
    
  • 增加语法:在数字中插入下划线作为分隔符,提高可读性。

    >>> 1_000_111_000
    1000111000
    >>> '{:_}'.format(1000000)    # 格式化字符串时也可输出下划线
    '1_000_000'
    
  • 增加语法:给字符串加上前缀 f 时,会将花括号 { } 中的内容当成 Python 代码执行。这种字符串称为 f-string ,常用于格式化字符串,比使用 str.format() 方法更方便。

    • 可以执行花括号中的代码,嵌入一个值到字符串中:
      >>> a = 1
      >>> f'a={a}'
      'a=1'
      >>> f'a + 1 = {int(a) + 1}'
      'a + 1 = 2'
      >>> f'{{ a }}'    # 如果原字符串包含花括号,则需要转义为两个花括号
      '{ a }'
      
    • 可以填充字符串到指定长度,实现 str.ljust()str.rjust()str.center() 的功能。
      >>> f'{a :5}'     # 用空格填充字符串,至少输出 5 个字符的长度(如果原字符串超过该长度,则不会填充)
      '    1'
      >>> f'{a :05}'    # 用数字 0 填充
      '00001'
      >>> f'{a :零>5}'  # 用任意单个字符填充
      '零零零零1'
      >>> f'{a :>5}'    # 右对齐(默认采用该对齐方式)
      '1    '
      >>> f'{a :<5}'    # 左对齐
      '1    '
      >>> f'{a :^5}'    # 中心对齐
      '  1  '
      
      完整语法示例:
      >>> content = 1
      >>> fill = ' '
      >>> align = '>'
      >>> width = 5
      >>> f'{content :{fill}{align}{width}}'
      '    1'
      
    • 可以截断字符串到指定长度:
      >>> f'{"helloworld" :.5}'     # 截断字符串,最多输出 5 个字符的长度
      'hello'
      >>> f'{"helloworld" :10.5}'   # 先截断,再填充
      'hello     '
      
    • 可以转换数字的输出格式:
      >>> f'{10 :+d}'     # 输出为 int 类型,并给非负数添加 + 前缀(否则默认只给负数添加 - 前缀)
      '+10'
      >>> f'{10000 :,d}'  # 输出为 int 类型,并用 , 或 _ 作为千位分隔符
      '10,000'
      >>> f'{10 :f}'      # 输出为 float 类型
      '10.000000'
      >>> f'{10 :e}'      # 输出为科学计数法
      '1.000000e+01'
      
    • 可以转换数字的进制:
      >>> f'{10 :b}'      # 输出为二进制
      '1010'
      >>> f'{10 :o}'      # 输出为八进制
      '12'
      >>> f'{10 :x}'      # 输出为小写的十六进制
      'a'
      >>> f'{10 :X}'      # 输出为大写的十六进制
      'A'
      >>> f'{10 :#X}'     # 加上 # ,则会添加这种进制类型对应的前缀
      '0XA'
      
  • 增加一种定义元类的方法:给类定义 __init_subclass__() 方法,用于初始化子类。如下:

    class TestBase:
        subclasses = []
    
        def __init_subclass__(cls, *args, **kwargs):
            super().__init_subclass__(*args, **kwargs)
            cls.subclasses.append(cls)
    
  • 增加标准库 secrets ,用于生成安全的随机数。

    • 原有的 random 模块生成的随机数可能被预测,不适合用作密码、加密密钥,否则存在安全隐患。

# Python3.7

# Python3.8

  • 2019 年发布:https://www.python.org/downloads/release/python-380/ (opens new window)

  • 增加语法:赋值表达式,用于给表达式中的变量赋值。

    if a := input():    # 相当于先执行 a = input(); 再执行 if a:
        print(a)
    
    >>> (a := 0) + 1
    1
    >>> a
    0
    
  • 增加语法:定义函数时,在正斜杆 / 之前的参数都会被视作位置参数。

    >>> def fun1(a, b, c=0, /, *args, **kwargs):
    ...     pass
    ...
    >>> fun1(1, 2, 3)
    >>> fun1(1, 2)
    
  • 增加语法:在 f-string 中可用 {var}{var=} 的格式插入变量的值,方便调试。

    >>> f'{a}'
    NameError: name 'a' is not defined    # 读取不存在的变量时会报错
    >>> a = 1
    >>> f'{a}'
    '1'
    >>> f'{a=}'
    'a=1'
    >>> f'DEBUG: {id(1)=}'			# 也可插入函数的返回值,会自动转换成 str 类型
    'DEBUG: id(1)=140298296357120'
    
  • 支持在 finally 语句块中使用 continue 关键字。

  • multiprocessing 模块增加一个 SharedMemory 类,用于创建进程之间的共享内存。

# Python3.9

  • 2020 年发布:https://www.python.org/downloads/release/python-390/ (opens new window)

  • dict 类增加合并运算符 |、更新运算符 |=

    • 以前合并字典的方式主要有两种:
      {**d1, **d2}
      d1.update(d2)   # update() 方法会改变字典的内容
      
      例如:
      >>> d1 = {1: 'A'}
      >>> d2 = {2: 'B'}
      >>> {**d1,  **d2}
      {1: 'A', 2: 'B'}
      >>> d1.update(d2)
      >>> d1
      {1: 'A', 2: 'B'}
      
    • 现在可以通过运算符合并字典:
      >>> d1 = {1: 'A'}
      >>> d2 = {2: 'B'}
      >>> d1 | d2         # 相当于 {**d1, **d2}
      {1: 'A', 2: 'B'}
      >>> d1 |= d2        # 相当于 d1.update(d2)
      >>> d1
      {1: 'A', 2: 'B'}
      
  • str 类增加两个方法 removeprefix()removesuffix() 。如下:

    >>> s = 'Hello'
    >>> s.rstrip('lo')          # 以前的 rstrip() 方法会删除右侧所有匹配的单个字符
    'He'
    >>> s.removesuffix('lo')    # 现在的 removesuffix() 方法只会删除匹配的子字符串
    'Hel'
    

    其原理为:

    def removeprefix(self: str, prefix: str, /) -> str:
        if self.startswith(prefix):
            return self[len(prefix):]
        else:
            return self[:]
    
  • 支持将大部分内置类型的类名用作函数实参,便于声明注释。如下:

    >>> T = type[int]
    >>> T
    type[int]
    >>> list[str] == list[str]
    True
    >>> tuple[int, ...]
    tuple[int, ...]
    >>> def fun1(x: dict[str, list[int]]):
    ...     pass
    ...
    

# Python3.10

  • 2021 年发布:https://www.python.org/downloads/release/python-3100/ (opens new window)
  • 增加语法:match-case 模式匹配。
  • 增加语法:用 | 运算符连接多个类型,表示 Union 类型。
    >>> int | str               # 相当于 typing.Union[int, str]
    >>> isinstance(1, int|str)  # 相当于 isinstance(1, (int, str)) 或 isinstance(1, typing.Union[int, str])
    True
    >>> issubclass(set, int|str)
    False
    
  • 调用 open() 函数时,允许传入参数 encoding='locale' ,等价于 encoding=None ,表示采用当前平台的默认编码格式。

# Python3.11

  • 于 2022 年发布:https://www.python.org/downloads/release/python-3110/ (opens new window)

  • CPython 解释器优化了加载模块、调用函数等操作,使得 Python3.11 比 Python3.10 的启动速度、运行速度快了 10%~60% 。

  • 增加了标准异常类型 BaseExceptionGroup、ExceptionGroup ,用于将多个异常打包为一组。异常组只能用语法 except* 捕捉。

    • 例:
      >>> try:
      ...     raise ExceptionGroup('异常组1', ([    # 创建异常组时,需要传入组名,和一组异常对象。支持嵌套异常组
      ...         KeyError('invalid key'),
      ...         TypeError('invalid type'),
      ...         ValueError('invalid value'),
      ...     ]))
      ... except* TypeError as e:
      ...     print('A')
      ... except* (TypeError, ValueError) as e:
      ...     print('B')
      ...
      A
      B
        + Exception Group Traceback (most recent call last):
        |   File "<stdin>", line 2, in <module>
        | ExceptionGroup: 异常组1 (1 sub-exception)
        +-+---------------- 1 ----------------
          | KeyError: 'invalid key'
          +------------------------------------
      
    • try 一个异常组时,会从上往下检查各个 except* 。
      • 每个 except* 可以指定一个或多个异常,只要任一异常包含于异常组,则执行该 except* 子句。然后从异常组删除已捕捉的异常,继续匹配之后的 except* 。
      • 一个普通异常最多触发一个 except 子句,而一个异常组可能触发多个 except* 子句。
      • 如果 except* 没有捕捉异常组中的全部异常,则剩下的异常会被抛出。
  • 打印 tracebacks 异常信息时,能更准确地指出引发异常的代码位置。

    • 以前,Python 解释器在执行 Python 程序时,只知道当前执行的是哪条字节码。现在,用两个 uint8_t 数据类型记录每条字节码的起始偏移量、结束偏移量,从而能根据已执行的偏移量,判断目前执行到哪条字节码的哪个位置。
    • 例如编写一个 test.py 脚本:
      1 + 2/0 + 3
      
      然后用 python 命令执行:
      [root@CentOS ~]# python test.py
      Traceback (most recent call last):
        File "/root/test.py", line 1, in <module>
          1 + 2/0 + 3
              ~^~
      ZeroDivisionError: division by zero
      
      倒数第二行信息是 Python3.11 才能打印的,指出这行代码的哪个位置引发了异常。
    • 缺点:
      • pyc 文件、内存中的字节码体积会增大 22% 。不过一般 Python 程序的字节码总共只有几十 MB ,影响小。
      • 用 Python 解释器交互式编程时,没有该功能。

# Python3.12

  • 于 2023 年发布:https://www.python.org/downloads/release/python-3120/ (opens new window)

  • f-string 增加了几种语法。

    • 花括号 { } 内可包含担任定界符的引号:
      >>> f'array={[1, 'hello']}'
      "array=[1, 'hello']"
      
    • 花括号 { } 内可嵌套多层 f-string :
      >>> f'{ f'{1+1}'= }'
      " f'{1+1}'= '2'"
      
    • 花括号 { } 内的 Python 代码可跨越多行,可添加注释:
      >>> f'{
      ... [1,     # one
      ... 2]      # two
      ... }'
      '[1, 2]'
      
    • 花括号 { } 内可包含反斜杠 \ ,因此可包含转义字符、Unicode 字符:
      >>> f'{'\n'}'
      '\n'
      
  • 简化定义泛型的语法。

    • 旧的语法:
      from typing import TypeVar, Generic
      T = TypeVar('T')
      class Test(Generic[T]):
          def __init__(self, value: T) -> None:
              pass
      
    • 新的语法:
      class Test[T]:
          def __init__(self, value: T) -> T:
              ...
      
  • 可用 type 关键字定义类型别名:

    type Point = tuple[float, float]
    
  • 在 Python 进程中创建子 Python 解释器时,允许每个 Python 解释器不再共享一个 GIL ,而是分别创建一个 GIL 。不过启用该功能需要调用 Cpython 的 API 。