作者 | 小刘
来源 | 菜鸟学Python
继3.7版本之后Python再次发布了新版本,虽然新版本带来了不少调整,但是其中很大一部分都是对代码底层设计的修改,又或是typing、pickle等不常用的功能,对多数用户而言影响不大,今天我想重点聊一聊那些将对我们的代码编写产生较大影响的新功能。
在体验开始前先说下准备工作,由于Python3.8还没有正式发布,因此通过Anaconda的多版本管理搭建Python3.8新环境的方法是行不通的,我的做法是到官网下载对应的最新版本后单独安装。
为了避免与现有环境冲突,将其更名为Python38(下图),下文中的Python如无特殊说明均为Python3.6, Python38为Python3.8。接下来就正式开始新特性体验。
1.字典逆序
我们都知道Python中的字典是无序的,Python3.6对这一问题进行了修订,默认情况下会按照键的创建顺序进行排序,但也仅限于此,你无法像列表那样对字典直接进行排序操作。
这一情况在Python3.8中进一步得到改善,Python3.8中reversed()方法增加了对字典对象的支持,可以对字典进行逆序操作。
在下面这段代码中,对字典进行简单的迭代,将会按照顺序输出字典的键。
现在改变一下代码,加入reversed()方法:
先来看使用Python3.6的运行结果(下图),可以看到在Python3.6中,字典是不支持recersed()方法的。
然后用Python3.8运行结果如下可以看到,字典按照键创建顺序的逆序进行了输出。虽然只是非常小的一点功能提升,但是在某些场景下对于字典对象的应用可能会起到非常关键的作用。
2.位置参数
在Python3.8中的参数传递方面引入了一个新的特性:PEP 570 Positional-Only Argument——限定位置参数,下面就详细聊聊这究竟是怎么回事。
一般来说,Python中的参数传递有三种形式:位置参数、关键字参数和可变参数,为了避免不必要的麻烦,规定在可变参数之后只允许使用关键字参数。可是即便如此还是给程序员们留下了很大的自由空间,比如在可变参数之前,位置参数和关键字参数的使用几乎不受限制。这样就出现了一个问题,假如一个团队中很多人进行合作开发,函数的定义形式和调用模式是很难规范和统一的。
因此Python3.8就引入了一个“Positional-Only Argument”的概念和分隔符“/”,在分隔符“/”左侧的参数,只允许使用位置参数的形式进行传递。
举个例子来进行说明,首先建立下面这样一个函数,由于函数中使用了分隔符“/”,因此只能使用Python3.8运行。
def add_num(x, y, z=100, /, a=100): print(x + y + z + a)
print(x + y + z + a)
尝试以下面这种方式调用函数:
add_num(1, 2, z=4, a=5)
结果在运行的时候发生了报错:
接着尝试全部以位置参数的形式调用函数(如下),结果顺利执行。可见“Positional-Only Argument”对分隔符“/”右侧的参数形式并没有限制。
# 输入add_num(1, 2, 4, 5)# 输出12
add_num(1, 2, 4, 5)
# 输出
12
那么如果只给定前两个参数,后面两个参数使用默认值又如何呢?通过下面的调用可以发现,也是可以正常运行的。
# 输入add_num(1, 2)# 输出203
add_num(1, 2)
# 输出
203
通过上面这个例子我们发现Python3.8对于参数传递的限制仅仅作用于分隔符“/”的左侧,而且只是在函数调用时发生作用。
3.赋值表达式
Python3.8中新增了赋值表达式“:=”操作符,简单来说就是把运算操作和赋值操作放在了一起,有点类似于“a+=b”这种表达方式,我想赋值表达式的出现应该是python追求简洁的传统理念所致。
来看下面这段代码,在func函数的if语句中,运算、赋值、判断操作在同一条语句中完成,即使变量a原本不存在也没关系。
# 输入def func(x, y, z): if (a := x + y) != z: print(a) else: print(z)func(1, 2, 5)# 输出3
def func(x, y, z):
if (a := x + y) != z:
print(a)
else:
print(z)
func(1, 2, 5)
# 输出
3
当然,就上面这段代码本身来看,将 x+y 的结果进行赋值似乎意义不大,但是如果运算表达式的计算量非常大或者要进行大规模独写等操作的话,重复执行对代码的效率将造成大的影响;而如果事先对运算表达式赋值则需要多写一行代码。
目前来看,赋值表达式最重要的作用就是使代码变得更加简洁,至于运行效率的差异,目前还没有验证。
4.快速调试
在之前的Python版本中,“f表达式”——f'{expr}'的作用与eval()函数基本相同,例如:
f'{[1, 2, 3, 4, 5, 6]}'的结果是列表[1, 2, 3, 4, 5, 6];
f'{3 + 2}'的结果是运算后的值5。
Python3.8中对该功能进行了优化,f'{expr}'语句中增加了对等号“=”的支持,在保留原来功能的基础上,还能够同时输出运算表达式本身。
例如执行先面这段代码,除了计算并输出运算结果外,还会将“=”和其左侧的算式一并输出:
x = 3print(f'{x * 2 = }')
print(f'{x * 2 = }')
执行结果:
f'{expr}'不仅适用于基本的算术运算,还能够进行其他对象的操作,以列表为例,令lst=[1, 2, 3, 4, 5, 6],现在对其进行扩展操作:
lst = eval('[1, 2, 3, 4, 5, 6]')print(f'{lst = }')print(f'{lst + [7] = }')
print(f'{lst = }')
print(f'{lst + [7] = }')
运行结果如下:
函数运算同样适用,例如对两个列表求交集,执行下面这段代码:
lst1 = [1, 2, 3, 4, 5]lst2 = [3, 5, 7]print(f'{list(set(lst1).intersection(set(lst2))) = }')
lst2 = [3, 5, 7]
print(f'{list(set(lst1).intersection(set(lst2))) = }')
运行结果:
相比仅输出结果,连带运算表达式一起输出有助于定位检查,在调试代码的时候使用真的是快捷又方便。
5.共享内存
进程是系统进行资源分配的独立单位,在以前的python版本中,进程间的数据交互只能通过Queue、Pipes等方式来实现,数据无法直接共享。
在Python 3.8中,multiprocessing模块提供了SharedMemory类,可以在不同的Python进程之间创建共享的内存block。目前支持int、float、str、bytes、bool、None、numpy.ndarray等一部分Python对象。
还是举个例子来进行说明,在下面这段代码中建立了2个进程,在进程1中对列表中的每个元素执行+10操作,进程1结束后执行进程2,输出列表内容。
由于进程之间数据无法共享,因此进程2中输出的列表是没有进行过+10操作的内容:
现在我们对代码进行一下小小的修改,nums不是作为一个普通的list,而是作为一个共享内存对象来创建,代码如下:
由于shared_memory是Python3.8中的新增内容,因此在Python3.6下运行会出错,我们还是用Python3.8来运行这段代码(结果如下)可以看到,进程2中输出的结果与进程1中是一样的,两个进程之间通过shared_memory实现了数据共享。
当然,shared_memory在实际应用中肯定不会如此简单,
比如SharedMemory.ShareableList和SharedMemory.SharedMemory的使用本身有很多规则和限制、
比如需要考虑数据锁的问题等等,
但是共享内存确实为进程间通讯提供了一个新的解决方案,而且据说其通讯效率也是非常之高的。
Python3.8发布的新特性和新功能还有很多,对一些内置模块的改进和优化则更多,想尝鲜的同学可以点击阅读原文,了解Python3.8详情!你对Python3.8新特性怎么看,欢迎吱一声留言!
—————————————
往期精彩: