Python 3.8是Python语言的最新版本,它适合用于编写脚本、自动化以及机器学习和Web开发等各种任务。现在Python 3.8已经进入官方的beta阶段,这个版本带来了许多语法改变、内存共享、更有效的序列化和反序列化、改进的字典和更多新功能。

Python 3.8还引入了许多性能改进。总的来说,我们即将拥有一个更快、更精确、更一致和更现代的Python。下面是Python 3.8的新功能和最重要的改变。

 

1.赋值表达式

海象运算符是Python 3.8最明显的变化就是赋值表达式,即:=操作符。因其 := 外观而被称为海象运算符(walrus operator)。

赋值表达式可以讲一个值赋给一个变量,即使变量不存在也可以。它可以用在表达式中,无需作为单独的语句出现。

比如说,多模式匹配的写法会从:

m = re.match(p1, line)	
if m:	
    return m.group(1)	
else:	
    m = re.match(p2, line)	
    if m:	
        return m.group(2)	
    else:	
        m = re.match(p3, line)	
        ...

变成:

if m := re.match(p1, line):	
    return m.group(1)	
elif m := re.match(p2, line):	
    return m.group(2)	
elif m := re.match(p3, line):	
    ...

而针对非可迭代对象的循环,也可以从:

ent = obj.next_entry()	
while ent:	
    ...   # process ent	
ent = obj.next_entry()

变成这样:

while ent := obj.next_entry():	
    ... # process ent

赋值表达式遵循了Python一贯简洁的传统,就像列表解析式一样。其目的在于避免在特定的Python编程模式中出现一些枯燥的样板代码。例如,上述代码用一般写法需要多写两行代码。

 

2.仅通过位置指定的参数

仅通过位置指定的参数是函数定义中的一个新语法,可以让程序员强迫某个参数只能通过位置来指定。这样可以解决Python函数定义中哪个参数是位置参数、哪个参数是关键字参数的模糊性。

仅通过位置指定的参数可以用于如下情况:某个函数接受任意关键字参数,但也能接受一个或多个未知参数。Python的内置函数通常都是这种情况,所以允许程序员这样做,能增强Python语言的一致性。

下面这个例子中,参数 a 和 b 只能是位置参数,而 c 或 d 可以是位置参数,也可以是关键字参数, e 或 f 则要求是关键字参数:

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 不可以是关键字参数	
f(10, 20, 30, 40, 50, f=60)           # e 必须是关键字参数

该语法的一个用处,是支持纯 Python 函数完整地模拟用 C 编写的函数的行为。例如,内置的 pow 函数是不接受关键字参数的:

def pow(x, y, z=None, /):	
    "Emulate the built in pow() function"	
    r = x ** y	
    return r if z is None else r%z

另外一个用处,是在参数名作用不大的情况下避免使用关键字参数。例如,内置的 len() 函数的标记是 len(obj,/),这样可以避免下面尴尬的调用方式:

len(obj='hello')  # obj 关键字降低了可读性

还有一个好处,就是支持以后在不破坏客户端代码的前提下修改参数的名称。例如,在 statistics 模块中,未来可能会调整的参数名 dist,如果像下面这样创建函数的话就可以实现:

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

由于 / 左侧的参数并没有暴露为关键字,意味着我们后续可以在 kwargds 中继续使用该关键字:

>>>	
>>> def f(a, b, /, **kwargs):	
...     print(a, b, kwargs)	
...	
>>> f(10, 20, a=1, b=2, c=3)         # a 和 b 有两种用法	
10 20 {'a': 1, 'b': 2, 'c': 3}

这样极大地简化了那些需要接受任意关键字参数的函数的实现。下面是 collections 模块的部分实现,体现了仅位置参数的优势。

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

 

3.支持f字符串调试

Python 3.6 中就加入了 f-string(也被称为格式化字符串),但是在调试输出时的代码写法会显得比较重复:

print(f'foo={foo} bar={bar}')

在 3.8 中,可以改用如下更简洁的写法:

print(f'{foo=} {bar=}')

两种写法的输出是一样的。

此外,还支持使用修饰符来改变输出的类型,比如 !s 代表使用 str() 而非 repr() 的输出:

>>> import datetime	
>>> now = datetime.datetime.now()	
>>> print(f'{now=} {now=!s}')	
now=datetime.datetime(2019, 7, 16, 16, 58, 0, 680222) now=2019-07-16 16:58:00.680222

 

4.多进程共享内存

在Python 3.8中,multiprocessing模块提供了SharedMemory类,可以在不同的Python进城之间创建共享的内存区域。

在旧版本的Python中,进程间共享数据只能通过写入文件、通过网络套接字发送,或采用Python的pickle模块进行序列化等方式。共享内存提供了进程间传递数据的更快的方式,从而使得Python的多处理器和多内核编程更有效率。

共享内存片段可以作为单纯的字节区域来分配,也可以作为不可修改的类似于列表的对象来分配,其中能保存数字类型、字符串、字节对象、None对象等一小部分Python对象。

 

5.Typing模块的改进

Python是动态类型语言,但可以通过typing模块添加类型提示,以便第三方工具验证Python代码。Python 3.8给typing添加了一些新元素,因此它能够支持更健壮的检查:

  • final修饰器和Final类型标注表明,被修饰或被标注的对象在任何时候都不应该被重写、继承,也不能被重新赋值。
  • Literal类型将表达式限定为特定的值或值的列表(不一定是同一个类型的值)。
  • TypedDict可以用来创建字典,其特定键的值被限制在一个或多个类型上。注意这些限制仅用于编译时确定值的合法性,而不能在运行时进行限制。

 

6.新版本的pickle协议

Python的pickle模块提供了一种序列化和反序列化Python数据结构或实例的方法,可以将字典原样保存下来供以后读取。不同版本的Python支持的pickle协议不同,而最新版本的支持范围更广、更强大、更有效的序列化。

Python 3.8引入的第5版pickle协议可以用一种新方法pickle对象,它能支持Python的缓冲区协议,如bytes、memoryviews或Numpy array等。新的pickle避免了许多在pickle这些对象时的内存复制操作。

NumPy、Apache Arrow等外部库在各自的Python绑定中支持新的pickle协议。新的pickle也可以作为Python 3.6和3.7的插件使用,可以从PyPI上安装。

 

7.可反转字典

Python3.8中reversed()方法增加了对字典对象的支持,可以对字典进行逆序操作。

Python3.6 在下面这段代码中,对字典进行简单的迭代,将会按照顺序输出字典的键。

python3新特性 python3.8特性_赋值

Python3.8 改变一下代码,加入reversed()方法:

python3新特性 python3.8特性_赋值_02

先来看使用Python3.6的运行结果(下图),可以看到在Python3.6中,字典是不支持recersed()方法的。

python3新特性 python3.8特性_Python_03

然后用Python3.8运行结果如下可以看到,字典按照键创建顺序的逆序进行了输出。虽然只是非常小的一点功能提升,但是在某些场景下对于字典对象的应用可能会起到非常关键的作用。

python3新特性 python3.8特性_python3新特性_04

 

8.性能改进

  • 许多内置方法和函数的速度都提高了20%~50%,因为之前许多函数都需要进行不必要的参数转换。
  • 一个新的opcode缓存可以提高解释器中特定指令的速度。但是,目前实现了速度改进的只有LOAD_GLOBAL opcode,其速度提高了40%。以后的版本中也会进行类似的优化。
  • 文件复制操作如shutil.copyfile()和shutil.copytree()现在使用平台特定的调用和其他优化措施,来提高操作速度。
  • 新创建的列表现在平均比以前小了12%,这要归功于列表构造函数如果能提前知道列表长度的情况下进行的优化。
  • Python 3.8中向新型类(如class A(object))的类变量中的写入操作变得更快。operator.itemgetter()和collections.namedtuple()也得到了速度优化。

 

9.Python C API和CPython实现

Python最近的版本在CPython(C语言编写的Python的参考实现)中使用的C API重构方面下了很大功夫。到目前为止这些工作还在不断添加,现有的成果包括:

  • Python初始化配置(Python Initialization Configuration)有了个新的C API,可以实现对Python初始化例程更紧密的控制和更详细的反馈。如此一来,将Python运行时嵌入到其他应用程序中就会更容易,也可以以编程方式给Python程序传递启动参数。新的API还确保了所有Python配置控制都有一个单一的、一致的位置,因此以后的改变(如Python的新的UTF-8模式)也更为容易。
  • CPython的另一个新的C API——"vectorcall"调用协议——可以实现针对Python内部方法更快的调用,而无需创建临时对象。该API依然不稳定,但已有了明显的改善。该API计划在Python 3.9中成熟。
  • Python运行时的审计钩子为Python运行时提供了两个API,可以用来勾住事件,从而保证测试框架、日志和审计系统等外部工具能够监视到它们。