NSF Back-end Dev Engineer

python版本更新拾遗

2023-02-20
nsf

记录python版本更新需要注意的部分,一些被新版本覆盖的旧版本更改不会存在于旧版本中

python3.6

注意:这个版本已经不再受支持

新增模块

1.secrets

secrets生成加密强伪随机值

新的特性

1.PEP 498:格式化字符串字面值

格式如下:

f'{value}'
和普通字符串区别:
1.添加前缀f
2.支持{value},value为变量值(支持一些较复杂格式可以点击标题链接到api查看)
3.使用 format() 协议进行格式化

2.PEP 487:自定义类创建

现在可以在不使用元类的情况下自定义子类的创建。 当一个新的子类被创建时将在基类上调用新的__init_subclass__ 类方法,非必要时不要使用这个特性

class PluginBase:
    subclasses = []

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

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

注意是在类创建的时候不是初始化的时候,调用顺序:

  • 子类全局变量
  • 父类__init_subclass__
  • 子类__init__初始化

3.变量注释

PEP 484 引入了函数形参类型标注即类型提示的标准。 这个 PEP 为 Python 添加了标注变量类型的语法,包括类变量和实例变量

primes: List[int] = []
captain: str  # Note: no initial value!
class Starship:
    stats: Dict[str, int] = {}

4.数字支持下划线

PEP 515 增加了在数字中使用下划线,增加数字可读性

>>> 1_000_000_000_000_000
1000000000000000
>>> 0x_FF_FF_FF_FF
4294967295

5.bpo-27350:新的 dict 实现

dict实现修改,与Python 3.5相比,内存使用量减少了20%至25%。

区别大概说一下:

python 3.5及之前:

当初始化一个空字典时,底层会初始化一个8 * 3的二维数组,行内容为[key的哈希值,指向key的指针,指向v的指针]
插入数据时获取key的哈希值,然后对8取余数,插入二维数组余数索引位置
注意:
1.使用开放寻址处理hash冲突
2.字典数量超过数组容量2/3时,数组扩容,容量翻倍,扩容后就是对新的数组长度取余数,原来的数据位置需要重新计算移动,插入效率变低

python 3.5之后

当初始化一个空字典时,底层会初始化一个长度为8的一维数组和一个空的二维数组(内容和之前一样),
插入数据时获取key的哈希值,然后对8取余数,二维数组直接后面添加数据,假设数据在二维数组的索引为n,在一维数组索引为余数的地方放入n

注意:这个修改在3.6版本还持保守态度,在3.7成为正式规范

基于这个改变,保留类属性定义顺序,保留关键字参数顺序

改进的模块

1.json

json.load()现在json.loads()支持二进制输入。编码的 JSON 应使用 UTF-8、UTF-16 或 UTF-32 表示

2.concurrent.futures

ThreadPoolExecutor 构造函数现在接受一个可选的thread_name_prefix参数,以便自定义池创建的线程的名称。

python3.7

新的特性

1.PEP 553:内置的 breakpoint()

Python 3.7 包含了新的内置 breakpoint() 函数,作为一种简单方便地进入 Python 调试器的方式。

内置 breakpoint() 会调用 sys.breakpointhook()。 在默认情况下后者会导入 pdb 然后再调用 pdb.set_trace(),但是通过将 sys.breakpointhook() 绑定到你选定的函数,breakpoint() 可以进入任何调试器。 此外,环境变量 PYTHONBREAKPOINT 可被设置为你选定的调试器的可调用对象。 设置 PYTHONBREAKPOINT=0 会完全禁用内置 breakpoint()

简单来说就是包装了pdb,更加方便使用pdb,举例:

使用 pdb 调试器设置断点

def f():
    a = 1
    # 设置一个断点
    import pdb; pdb.set_trace()
    return 1

使用 breakpoint() 设置断点

def f():
    a = 1
    breakpoint(header="start")
    return 1

执行f()的效果相同,如下:

$ python.exe xxx.py
start
> /home/nsf/Desktop/workspace/leecode/wechat/test1.py(3)<module>()
-> return 1
(Pdb) print(a)
1
(Pdb) 

使用其他调试器

通过 PYTHONBREAKPOINT 还可选用别的调试器。例如,当我们想要使用 PuDB (一个基于控制台的可视化调试器)时,只需修改 PYTHONBREAKPOINT

安装PuDB

pip install pudb

运行

$ PYTHONBREAKPOINT=pudb.set_trace python3.9 xxx.py

默认开启5555端口,通过浏览器可查看,类似pycharm的调试功能

调试命令

pdb命令行:

1)进入命令行Debug模式,python -m pdb xxx.py
2)h:(help)帮助
3)w:(where)打印当前执行堆栈
4)d:(down)执行跳转到在当前堆栈的深一层(个人没觉得有什么用处)
5)u:(up)执行跳转到当前堆栈的上一层
6)b:(break)添加断点
​b 列出当前所有断点,和断点执行到统计次数
​b line_no:当前脚本的line_no行添加断点
​b filename:line_no:脚本filename的line_no行添加断点
​b function:在函数function的第一条可执行语句处添加断点
7)tbreak:(temporary break)临时断点
​在第一次执行到这个断点之后,就自动删除这个断点,用法和b一样
8)cl:(clear)清除断点
​cl 清除所有断点
​cl bpnumber1 bpnumber2... 清除断点号为bpnumber1,bpnumber2...的断点
​cl lineno 清除当前脚本lineno行的断点
​cl filename:line_no 清除脚本filename的line_no行的断点
9)disable:停用断点,参数为bpnumber,和cl的区别是,断点依然存在,只是不启用
10)enable:激活断点,参数为bpnumber
11)s:(step)执行下一条命令
​如果本句是函数调用,则s会执行到函数的第一句
12)n:(next)执行下一条语句
​如果本句是函数调用,则执行函数,接着执行当前执行语句的下一条。
13)r:(return)执行当前运行函数到结束
14)c:(continue)继续执行,直到遇到下一条断点
15)l:(list)列出源码
​ l 列出当前执行语句周围11条代码
​ l first 列出first行周围11条代码
​ l first second 列出first--second范围的代码,如果second<first,second将被解析为行数
16)a:(args)列出当前执行函数的函数
17)p expression:(print)输出expression的值
18)pp expression:好看一点的p expression
19)run:重新启动debug,相当于restart
20)q:(quit)退出debug
21)j lineno:(jump)设置下条执行的语句函数
​只能在堆栈的最底层跳转,向后重新执行,向前可直接执行到行号
22)unt:(until)执行到下一行(跳出循环),或者当前堆栈结束
23)condition bpnumber conditon,给断点设置条件,当参数condition返回True的时候bpnumber断点有效,否则bpnumber断点无效

注意:

1:直接输入Enter,会执行上一条命令

2:输入PDB不认识的命令,PDB会把他当做Python语句在当前环境下执行

2.基于哈希值的 .pyc 文件

传统上 Python 检查字节码缓存文件 (即 .pyc 文件) 是否最新的方式是通过对源码元数据 (最后更改的时间戳和大小)和生成缓存时保存在其文件头中的源码元数据进行比较。 这种检查方法虽然有效,但也存在缺点。 当文件系统的时间戳太粗糙时,Python 有可能错过源码更新。

PEP 552 扩展了 pyc 格式以允许使用源文件的哈希值而非源文件的时间戳来检查有效性。 这种 .pyc 文件就称为“基于哈希值的”。 默认情况下,Python 仍然使用基于时间戳的有效性检查,不会在运行时生成基于哈希值的 .pyc 文件。 基于哈希值的 .pyc 文件可以使用 py_compilecompileall 来生成。

3.延迟标注求值

随着 PEP 3107 加入标注功能并在 PEP 526 进一步细化,Python 中类型提示的出现揭示了两个明显的可用性问题:

  • 标注只能使用在当前作用域中已经存在的名称,也就是说,它们不支持任何形式的前向引用;而且——
  • 标注源码对 Python 程序的启动时间有不利的影响。

这两个问题都可以通过延迟标注求值来解决。在定义标注的时候,编译器并不会编译执行相应表达式的代码,而是保存与相应表达式的 AST 等价的字符串形式。如果有需要,标注可以在运行时使用 typing.get_type_hints() 进行解析。在不需要这种解析的通常情况下,标注的存储成本更低(因为解析器只需处理较短的字符串)且启动时间更短。

在可用性方面,标注现在支持向前引用,以使以下句法有效:

class C:
    @classmethod
    def from_string(cls, source: str) -> C:
        ...

    def validate_b(self, obj: B) -> bool:
        ...

class B:
    ...

由于此修改会破坏兼容性,在 Python 3.7 中此种新的行为需要在每个模块层级上使用 __future__ 导入来启用:

from __future__ import annotations

新增模块

1.dataclasses

新的 dataclass() 装饰器提供了一种声明 数据类 的方式。 数据类使用变量标注来描述其属性。 它的构造器和其他魔术方法例如 __repr__(), __eq__() 以及 __hash__() 会自动地生成。

示例:

@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)"

改进的模块

1.functools

functools.singledispatch() 现在支持使用类型标注

2.具有纳秒级精度的新时间函数

PEP 564time 模块中增加了原有计时器函数的六个新“纳秒版”变化形式

这些新函数会以整数值的形式返回纳秒数

3.concurrent.futures

添加了initializerinitargs参数。 initializer 是一个可选的可调用对象,会在每个 worker 线程启动之前调用。 initargs 是传递给 initializer 的参数元组。 如果 initializer 抛出了异常,那么当前所有等待的任务都会抛出 BrokenThreadPool 异常,继续提交 submit 任务也会抛出此异常。

4.functools

functools.singledispatch() 现在支持使用类型标注来注册实现。

将一个函数转换为 单分派 generic function

要定义一个泛型函数,应使用 @singledispatch 装饰器进行装饰。 请注意分派是作用于第一个参数的类型,要相应地创建你的函数:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

要将重载的实现添加到函数中,请使用泛型函数的 register() 属性。 它是一个装饰器。 对于带有类型标注的函数,该装饰器将自动推断第一个参数的类型:

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

对于不使用类型标注的代码,可以将适当的类型参数显式地传给装饰器本身:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

要启用注册 lambda 和现有函数,可以使用函数形式的 register() 属性:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

register() 属性将返回启用了装饰器堆栈、封存的未装饰函数,并会为每个变量单独创建单元测试:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

在调用时,泛型函数会根据第一个参数的类型进行分派:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

python3.8

新的特性

1.PEP 572:海象运算符

新增的语法 := 可在表达式内部为变量赋值,举例:

# demo1
if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")
# demo2
while (block := 1) != '':
    process(block)
# demo3
[clean_name.title() for name in names if (clean_name := normalize('NFC', name)) in allowed_names]

由上面的例子可以看出,海象运算符的作用是简化一些逻辑表达,所以请尽量将海象运算符的使用限制在清晰的场合中,以降低复杂性提升可读性

注意

1.demo2不等同于

block = 1
while block != '':
    process(block)

demo2中每次循环会重新给block赋值为1,所以当循环中使用海象运算符时需要特别注意

2.海象运算符作用域是能扩散到表达式外的

2.PEP 570:仅限位置形参

新增了一个函数形参语法 / 用来指明某些函数形参必须使用仅限位置非关键字参数的形式。 这种标记语法与通过 help() 所显示的使用 Larry Hastings 的 Argument Clinic 工具标记的 C 函数相同。

在下面的例子中,形参 ab 为仅限位置形参,cd 可以是位置形参或关键字形参,而 ef 要求为关键字形参:

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

好处

1.允许纯 Python 函数完整模拟现有的用 C 代码编写的函数的行为,例如,内置的 divmod() 函数不接受关键字参数

def divmod(a, b, /):
    "Emulate the built in divmod() function"
    return (a // b, a % b)

2.在不需要形参名称时排除关键字参数。 例如,内置的 len() 函数的签名为 len(obj, /)。 这可以排除len(obj='hello')这种笨拙的调用形式

3.将形参标记为仅限位置形参,允许在未来修改形参名而不会破坏原有的代码。 例如,在 statistics 模块中,形参名 dist 在未来可能被修改。

def quantiles(dist, /, *, n=4, method='exclusive')
    ...

4.极大地简化了需要接受任意关键字参数的函数和方法的实现。 例如,以下是一段摘自 collections 模块的代码:

class Counter(dict):
    def __init__(self, iterable=None, /, **kwds):
        # Note "iterable" is a possible keyword argument

总结:约束规范位置参数和关键字参数,让目前的一些写法更加简化和兼容,从此也能看出代码易用和规范需要寻找一个平衡

3.bpo-36817: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)"

其他语言特性修改

1.bpo-35224:字典推导式

字典推导式已与字典字面值实现同步,会先计算键再计算值,举例:

def f1():
    print(1)

def f2():
    print(2)

dd = {f1(): f2() for _ in range(2)}

""" 
python3.8输出:
1
2
1
2
python3.6输出:
2
1
2
1
"""

此次修改是因为海象运算符,在字典推导式中使用海象运算符有时需要保证顺序,举例:

>>> names = ['Martin von Löwis', 'Łukasz Langa', 'Walter Dörwald']
>>> {(n := normalize('NFC', name)).casefold() : n for name in names}
{'martin von löwis': 'Martin von Löwis',
 'łukasz langa': 'Łukasz Langa',
 'walter dörwald': 'Walter Dörwald'}

改进的模块

functools

functools.lru_cache() 现在可直接作为装饰器而不是作为返回装饰器的函数。 因此这两种写法现在都被支持:

@lru_cache
def f(x):
    ...

@lru_cache(maxsize=256)
def f(x):
    ...

(由 Raymond Hettinger 在 bpo-36772 中贡献。)

添加了新的 functools.cached_property() 装饰器,用于在实例生命周期内缓存的已计算特征属性。

import functools
import statistics

class Dataset:
   def __init__(self, sequence_of_numbers):
      self.data = sequence_of_numbers

   @functools.cached_property
   def variance(self):
      return statistics.variance(self.data)

添加了新的 functools.singledispatchmethod() 装饰器可使用 single dispatch 将方法转换为 泛型函数:

from functools import singledispatchmethod
from contextlib import suppress

class TaskManager:

    def __init__(self, tasks):
        self.tasks = list(tasks)

    @singledispatchmethod
    def discard(self, value):
        with suppress(ValueError):
            self.tasks.remove(value)

    @discard.register(list)
    def _(self, tasks):
        targets = set(tasks)
        self.tasks = [x for x in self.tasks if x not in targets]

python3.9

新的特性

1.PEP 584:字典合并与更新运算符

合并 (|) 与更新 (|=) 运算符已被加入内置的 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'}

2.新增用于移除前缀和后缀的字符串方法

增加了 str.removeprefix(prefix)str.removesuffix(suffix) 用于方便地从字符串移除不需要的前缀或后缀。 也增加了 bytes, bytearray 以及 collections.UserString 的对应方法。

改进的模块

1.concurrent.futures

将新的 cancel_futures 形参添加到 concurrent.futures.Executor.shutdown(),可以取消尚未开始运行的所有挂起的 Future,而不必等待它们完成运行再关闭执行器

性能优化

1.bpo-32856:列表推导

优化了在推导式中为临时变量赋值的惯用方式。 现在推导式中的 for y in [expr] 会与简单赋值语句 y = expr 一样快速。 例如:

sums = [s for s in [0] for x in data for s in [s + x]]

不同于 := 运算符,这个惯用方式不会使变量泄露到外部作用域中

对于列表推导,会有不同的表达,举例来说,比如:[f(x) + g(f(x)) for x in range(10)]可能有若干种写法:
1.[f(x) + g(f(x)) for x in range(10)]
2.f_samples = (f(x) for x in range(10))
    [y+g(y) for y in f_samples]
3.def func(x):
        y = f(x)
        return y + g(y)
    [func(x) for x in range(10)]
4.[y + g(y) for x in range(10) for y in [f(x)]]
相比较而言第四种更加能够让人接受,但是缺点是你构造了一个[f(x)]临时变量,速度比较慢,
本次性能优化就是让for y in [expr] 与简单赋值语句 y = expr 一样快速

2.从 Python 3.4 到 Python 3.9 的性能提升

Python version                       3.4     3.5     3.6     3.7     3.8    3.9
--------------                       ---     ---     ---     ---     ---    ---

Variable and attribute read access:
    read_local                       7.1     7.1     5.4     5.1     3.9    3.9
    read_nonlocal                    7.1     8.1     5.8     5.4     4.4    4.5
    read_global                     15.5    19.0    14.3    13.6     7.6    7.8
    read_builtin                    21.1    21.6    18.5    19.0     7.5    7.8
    read_classvar_from_class        25.6    26.5    20.7    19.5    18.4   17.9
    read_classvar_from_instance     22.8    23.5    18.8    17.1    16.4   16.9
    read_instancevar                32.4    33.1    28.0    26.3    25.4   25.3
    read_instancevar_slots          27.8    31.3    20.8    20.8    20.2   20.5
    read_namedtuple                 73.8    57.5    45.0    46.8    18.4   18.7
    read_boundmethod                37.6    37.9    29.6    26.9    27.7   41.1

Variable and attribute write access:
    write_local                      8.7     9.3     5.5     5.3     4.3    4.3
    write_nonlocal                  10.5    11.1     5.6     5.5     4.7    4.8
    write_global                    19.7    21.2    18.0    18.0    15.8   16.7
    write_classvar                  92.9    96.0   104.6   102.1    39.2   39.8
    write_instancevar               44.6    45.8    40.0    38.9    35.5   37.4
    write_instancevar_slots         35.6    36.1    27.3    26.6    25.7   25.8

Data structure read access:
    read_list                       24.2    24.5    20.8    20.8    19.0   19.5
    read_deque                      24.7    25.5    20.2    20.6    19.8   20.2
    read_dict                       24.3    25.7    22.3    23.0    21.0   22.4
    read_strdict                    22.6    24.3    19.5    21.2    18.9   21.5

Data structure write access:
    write_list                      27.1    28.5    22.5    21.6    20.0   20.0
    write_deque                     28.7    30.1    22.7    21.8    23.5   21.7
    write_dict                      31.4    33.3    29.3    29.2    24.7   25.4
    write_strdict                   28.4    29.9    27.5    25.2    23.1   24.5

Stack (or queue) operations:
    list_append_pop                 93.4   112.7    75.4    74.2    50.8   50.6
    deque_append_pop                43.5    57.0    49.4    49.2    42.5   44.2
    deque_append_popleft            43.7    57.3    49.7    49.7    42.8   46.4

Timing loop:
    loop_overhead                    0.5     0.6     0.4     0.3     0.3    0.3

以上结果是由以下变量访问基准测试脚本所生成的: Tools/scripts/var_access_benchmark.py。 该基准测试脚本以纳秒为单位显示时间。 基准测试数据是在一块 Intel® Core™ i7-4960HQ 处理器 运行从 python.org 获取的 macOS 64 位编译版本所得到的。

python3.10

新的特性

1.PEP 634:结构模式匹配

相当于c语言中的case,举例:

def http_error(status):
    match status:
    	#简单变量
        case 400: 
            return "Bad request"
        # 支持其他变量
        case (0, 0): 
            print("Origin")
        # 类对象支持参数
        case Point(x=0, y=0): 
            print("Origin is the point's location.")
        # 支持if子句
        case Point(x, y) if x == y:
            print(f"The point is located on the diagonal Y=X at {x}.")
        # 支持"或"语句
        case 401 | 403 | 404:
    		return "Not allowed"
        # 其他情况
        case _: 
            return "Something's wrong with the Internet"

2.PEP 604:新型联合运算符

旧版:

def square(number: Union[int, float]) -> Union[int, float]:
    return number ** 2

新版:

def square(number: int | float) -> int | float:
    return number ** 2

这种新语法支持isinstance()issubclass()

>>> isinstance(1, int | str)
True

其他语言特性修改

1.bpo-29882 : int

新增int.bit_count()`,返回给定整数的二进制展开式中 1 的数量

python3.11

改进的模块

1.bpo-44357:math

添加math.cbrt():返回 x 的立方根


Similar Posts

上一篇 django-urls

Comments