NSF Back-end Dev Engineer

python版本更新拾遗

2021-06-29
nsf

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

python3.6

新的特性

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

格式如下:

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

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__() 实现的零参数 super() 调用能正确工作,自定义元类必须保证新的 __classcell__ 命名空间入口被传播到 type.__new__ (如 创建类对象 中所描述)。

3.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成为正式规范

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语句在当前环境下执行

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'}

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'}

性能优化

1.bpo-32856:列表推导

Optimized the idiom for assignment a temporary variable in comprehensions. Now for y in [expr] in comprehensions is as fast as a simple assignment y = expr. For example:

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

Unlike the := operator this idiom does not leak a variable to the outer scope.

(Contributed by Serhiy Storchaka in bpo-32856.)

对于列表推导,会有不同的表达,举例来说,比如:[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 一样快速

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

上一篇 flask-uwsgi-gevent

下一篇 flask拾遗

Comments