Python 3.8 究竟要不要升级?用过之后的小哥这样说

本文转自雷锋网,如需转载请至雷锋网官网申请授权。

距 Python 3.8 稳定版正式发布已经过去了小半个月,不少 Python 常驻用户已经将 Python 更新到了 3.8 版本,也有一些朋友担心代码运行兼容性等问题,依然坚挺在 Python3.7 中。

那么,究竟要不要更新到 Python 3.8?新版本有哪些特点?它能为程序猿们带来怎样的收益?一位外国的 python 忠实小哥哥发了一篇文章,用众多实例详细讲解了 Python 3.8 特别的新功能。雷锋网 AI 开发者也将其更多功能整理编译到后文中,希望这篇文章能帮助你更好的理解 Python 3.8。

Python 3.8 究竟要不要升级?用过之后的小哥这样说

海象(walrus )运算符

Animesh Gaitonde 是 python 的狂热爱好者,下面是他对 python 3.8 中 walrus 运算符的使用心得——

最近,python 社区发布了该语言的 3.8 版本。作为python 的超级粉丝 ,我研究了发行说明,有一个特别的操作符引起了我的注意,该运算符称为 walrus 运算符(:=)或赋值表达式运算符。

这个新运算符(:=)使我们能够将值赋给表达式中的变量。这个符号有点像海象的眼睛和獠牙(因此也称为「海象运算符」)。

Python 3.8 究竟要不要升级?用过之后的小哥这样说

  •  walrus 牛刀小试

现在让我们看看下面的代码段:

  1. countries = [「India」,「USA」,「France」,「Germany」] 
  2.  
  3. if len(countries) < 5
  4.  
  5. print ("Length of countries is " + len(countries)) 

在这个代码段中,我们将调用函数 len()两次。有什么方法可以避免重新调用以提高可读性吗?是的,在改进代码之后,我们得到了以下结果:

  1. country_size = len(countries) 
  2.  
  3. if country_size < 5
  4.  
  5. print ("Length of countries is " + country_size) 

还有进一步改进的余地吗?我们是否可以避免在单独的行中为变量「country_size」赋值?在 Python3.8 中引入的 walrus 运算符可以拯救我们,它使我们可以在 if 语句本身中声明和赋值:

  1. if country_size := len(countries) < 5 : 
  2.  
  3. print ("Length of countries is " + country_size) 

让我们进一步探讨这个运算符的能力。

Python 3.8 究竟要不要升级?用过之后的小哥这样说

  • 代码行数与复杂度的平衡

让我们看看下面的例子:

Python 3.8 究竟要不要升级?用过之后的小哥这样说

多次调用成本高昂的函数

在上面的示例中,通过多次调用运行成本高的函数来填充列表。但在 walrus 运算符的帮助下,我们可以将结果存储在一个变量中,并在进一步的计算中重用同一个变量,从而避免多次调用 get_count()函数。下面是使用 walrus 运算符后的示例:

Python 3.8 究竟要不要升级?用过之后的小哥这样说

使用 walrus 运算符避免多个函数调用

从上面的例子可以看出,walrus 运算符减少了代码行,使代码更具可读性,从而简化了审阅者的工作。此外,它在代码行数和代码复杂度之间达到了更好地平衡。

  • 理解效率低下

Python 3.8 究竟要不要升级?用过之后的小哥这样说

基于条件填充列表

在上面的例子中,我们正在执行多个操作。最初,我们创建了一个空列表,然后迭代一个 id 列表,并通过检查结果是否有效来填充该列表。

通过 walrus 运算符,我们可以简化上面的代码,并将所有内容放在一行中。

Python 3.8 究竟要不要升级?用过之后的小哥这样说

使用者需避免对 walrus 运算符的错误理解

  • 分块处理文件

在处理一个大文件时,我们将文件分成块并读取。每次读取块时,都会检查该值,并将其作为 while 循环中的终止条件,代码如下:

  1. chunk = file.read(256
  2.  
  3. while chunk: 
  4.  
  5. process(chunk) 
  6.  
  7. chunk = file.read(256

通过使用 walrus 运算符,我们可以在 while 循环的表达式中读取并分配所读数值,这样还能够避免在 while 循环外显式声明变量。下面是一个例子:

  1. while chunk := file.read(256) : 
  2.  
  3. process(chunk) 
  • 正则表达式匹配

正则表达式匹配是一个需要两个步骤的过程。在第一步中,我们检查是否发生匹配,在下一步中,我们提取子组:

Python 3.8 究竟要不要升级?用过之后的小哥这样说

正则表达式匹配

从上面的代码可以看出,如果匹配,我们正在重新计算 re.match(info),这会根据数据降低程序的速度。

上述代码利用 walrus 运算符可以重写如下,并且可以避免重新计算:

Python 3.8 究竟要不要升级?用过之后的小哥这样说

正则表达式匹配:=

  • 哪里不能用 walrus 运算符?

1. 给变量赋值

a = 5 #Valid

a := 5 #InValid

empty_list = [] #Valid

empty_list := [] #InValid

如上所示,我们不能将=运算符与:=运算符一起使用,walrus 运算符只能是表达式的一部分。

2. 加减运算

a += 5 #Valid

a :+=5 # Invalid

3. lambda 函数中的赋值表达式

(lambda: a:= 5) # Invalid

lambda: (a := 5) # Valid, but not useful

(var := lambda: 5) # Valid

  • PEP-572 与争议

walrus 运算符是作为 pep-572(python 增强建议)的一部分引入的。

一个面向大众的工具,必须得到发明者圭多·范·罗森(Guido van Rossum)和他所选的代表们的批准。因此,围绕 walrus 运算符的争论很多,其中部分内容如下:

1. 句法变异

开发人员提出了许多替代「:=」,例如表达式->名称、名称->表达式、{表达式} 名称等。很少有使用现有关键字的建议,而其他使用新的运算符的建议。

2. 向后兼容性

这个特性不会向后兼容,也不会在以前的 python 版本上运行。

3. 运算符名称

人们推荐的名字,比如'assignment operator'、'named expression operator'、'becomes operator'等等,而不是像 walrus operator 这样的行话,会导致混淆。

Python 3.8 究竟要不要升级?用过之后的小哥这样说

Python 3.8 究竟要不要升级?用过之后的小哥这样说

关于 walrus 运算符的争论

关于 walrus 运算符的详细介绍就是这些,除此之外,Python3.8 也有其它新功能——

仅位置参数(Positional-Only Arguments)

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

在下面的例子中,形参 a 和 b 为仅限位置形参,c 或 d 可以是位置形参或关键字形参,而 e 或 f 要求为关键字形参:

  1. def f(a, b, /, c, d, *, e, f): 
  2.  
  3.  print(a, b, c, d, e, f) 

以下均为合法的调用:

  1. f(102030, d=40, e=50, f=60

但是,以下均为不合法的调用:

  1. f(10, b=20, c=30, d=40, e=50, f=60) # b cannot be a keyword argument 
  2.  
  3. f(1020304050, f=60) # e must be a keyword argument 

这种标记形式的一个用例是它允许纯 Python 函数完整模拟现有的用 C 代码编写的函数的行为。另一个用例是在不需要形参名称时排除关键字参数。例如,内置的 len() 函数的签名为 len(obj, /)。

除了这一点,在 Python3.8 中,可以用 / 来表示必须通过仅位置参数之前的参数。这极大地方便了之前在自定义函数中,开发者没有简单的方法指定参数为仅位置参数的问题。

  1. def incr(x, /): 
  2.  
  3. return x + 1 

更多关于仅位置参数:https://www.python.org/dev/peps/pep-0570/

用于已编译字节码文件的并行文件系统缓存

新增的 PYTHONPYCACHEPREFIX 设置 (也可使用 -X pycache_prefix) 可将隐式的字节码缓存配置为使用单独的并行文件系统树,而不是默认的每个源代码目录下的 __pycache__ 子目录。

缓存的位置会在 sys.pycache_prefix 中报告 (None 表示默认位置即 __pycache__ 子目录)。

更详细内容:https://bugs.python.org/issue33499

调试构建使用与发布构建相同的 ABI

不管是在发布模式还是调试模式下构建,Python 现在都使用相同的 ABI。在   Unix 上,当 Python 以调试模式构建时,现在可以加载以发布模式构建的 C  扩展和使用稳定 ABI 构建的 C 扩展

更详细内容:https://bugs.python.org/issue36721

f 字符串支持一个方便的 = 说明符进行调试

=在 f-string 中添加了一个说明符。f 字符串(例如)f'{expr=}' 将扩展为表达式的文本、等号,然后扩展为求值表达式的表示形式。

更详细内容:https://bugs.python.org/issue36817

PEP 587:Python 初始化配置

在 PEP 587 添加了新的 C API 以配置 Python 初始化,从而提供了对整个配置的更好控制和更好的错误报告。

新的结构:

  • PyConfig

  • PyPreConfig

  • PyStatus

  • PyWideStringList

新的函数:

  • PyConfig_Clear()

  • PyConfig_InitIsolatedConfig()

  • PyConfig_InitPythonConfig()

  • PyConfig_Read()

  • PyConfig_SetArgv()

  • PyConfig_SetBytesArgv()

  • PyConfig_SetBytesString()

  • PyConfig_SetString()

  • PyPreConfig_InitIsolatedConfig()

  • PyPreConfig_InitPythonConfig()

  • PyStatus_Error()

  • PyStatus_Exception()

  • PyStatus_Exit()

  • PyStatus_IsError()

  • PyStatus_IsExit()

  • PyStatus_NoMemory()

  • PyStatus_Ok()

  • PyWideStringList_Append()

  • PyWideStringList_Insert()

  • Py_BytesMain()

  • Py_ExitStatusException()

  • Py_InitializeFromConfig()

  • Py_PreInitialize()

  • Py_PreInitializeFromArgs()

  • Py_PreInitializeFromBytesArgs()

  • Py_RunMain()

更详细内容:https://www.python.org/dev/peps/pep-0587/

Vectorcall: 用于 CPython 的快速调用协议

添加 "vectorcall" 协议到 Python/C API。它的目标是对已被应用于许多类的现有优化进行正式化。任何实现可调用对象的扩展类型均可使用此协议。

更详细内容:https://www.python.org/dev/peps/pep-0590/

具有外部数据缓冲区的 pickle 协议 5

当使用 pickle 在 Python 进程间传输大量数据以充分发挥多核或多机处理的优势时,非常重要一点是通过减少内存拷贝来优化传输效率,并可能应用一些定制技巧例如针对特定数据的压缩。

pickle 协议 5 引入了对于外部缓冲区的支持,这样 PEP 3118 兼容的数据可以与主 pickle 流分开进行传输,这是由通信层来确定的。

更详细内容:https://www.python.org/dev/peps/pep-0574/

Python 3.8 究竟要不要升级?用过之后的小哥这样说

博客地址:

http://t.cn/Ai389QHq

相关推荐