是不是感觉python3.7刚刚使用,3.8还没捂热乎,怎么3.9这么快就来了!其实版本迭代速度快,说明这门编程语言的活力强,对于我们使用者来说是件好事,而且谁也没说必须使用最新版本,我到现在仍然在用python3.6。但是呢,新版本出来了一定要体验一下,看看都有哪些改动和优化,说不定哪个新特性就能解决你手里的大问题。
1 新的特性
1).import异常
我们在进行模块的相对引用时,可能会遇到这个错误提示:ValueError: Attempted relative import beyond toplevel package。简单来说这个问题是由于引用的模块超出了顶层目录的层级结构所导致的,这个问题不理解也没关系,因为跟python3.9的特性更改关系不大。
我们只要知道,python3.9中对这类错误的raise类型进行了调整,当遇到这类错误的时候,会提示ImportError而不再是ValueError,PR如图所示:
这个改动的最大好处就是当遇到这类问题时,系统会明确告诉你是由import模块引发的。
2).__file__路径
我们知道,在python中可以使用__file__、sys.argv[0]、sys.path[0]等方法获取当前脚本的所在路径,只不过在命令行模式下使用前两个命令获取到的都是相对路径,只有第三个命令可以获取脚本的绝对路径。我们用以前版本的的python运行下面这段代码:
- import sys
- print(__file__)
- print(sys.argv[0])
- print(sys.path[0])
得到结果如下:
而如果使用python3.9来运行结果就不一样了(下图),他对__file__和sys.argv[0]都做了调整,使其返回值全部为绝对路径。
3).replace修复
replace是字符串处理中的常用函数,他的原型其实是这样的:
- str.replace(old, new[, max])
其中max是可选参数,意思是替换不超过max次。但是在之前的python版本中,如果对空值进行这种形式的替换会有一点问题,例如下面这段代码,它的输出结果仍然为空。
- s = ''
- s = s.replace('', 'python39', 1)
- print(s)
而在python3.9中对这一问题进行了修正,除非max赋值为0,否则会进行正常的替换操作。这一改动对bytes和bytearray对象同样适用。
2 模块改动
Python3.9并没有为我们带来新的内置模块,但是对一些模块进行了修改,我们挑选几个使用相对较多的进行说明。
1).ast
ast这个概念大家可能比较陌生,一般来说我们很少有机会用到它,我们只需要知道ast对象是类似一种树形的语法结构。来看下面这段代码,他的作用就是把print(3+5)转换成ast对象并打印。
- import ast
- func_def = 'print(3+5)'
- r_node = ast.parse(func_def)
- print(ast.dump(r_node))
先使用之前的版本来运行,看看下图中的输出结果。我们不用看内容,单从输出格式而言,这串代码既没有换行也没有缩进,看起来很费劲。
而python3.9则对这一问题进行了解决,它在dump()方法中新增了一个参数 indent,代表的是首行缩进的字符长度,我们对上述代码进行少许修改:
- import ast
- func_def = 'print(3+5)'
- r_node = ast.parse(func_def)
- print(ast.dump(r_node, indent=2))
然后用python3.9来运行,结果如下,这次看起来是不是舒服多了。
2).asyncio
在python3.9中,新增了一个名为shutdown_default_executor()的协程,它的作用就是等待ThreadPoolExecutor中的所有线程执行完毕,为默认线程安排关闭时间。
需要注意的是,调用此方法后,如果在默认线程中调用loop.run_in_executor()方法,将会引发RuntimeError。
此外,如果我们使用了asyncio.run()类的方法,那么shutdown_default_executor()将会自动调度。也就是说,对于asyncio的一般使用者来说,python3.9的改变并不会带来什么实质性影响。
3).threading
我们知道,在python的子解释器中是不支持守护线程的,在之前的版本中,如果一个线程是从子解释器中调用的守护线程,那么将会导致python程序的崩溃。
在python3.9中,遇到这种情况会引发RuntimeError,这相当于对整个程序加了一层保护。
4).pprint
Python3.9对pprint的修改主要体现在增加了对types.SimpleNamespace的支持。types.SimpleNamespace严格来说是一个简单的对象子类,为了便于理解我们可以简单地把他看作一个数据结构。
下面我就来看看在之前的版本和python3.9中,对types.SimpleNamespace的数据进行pprint结果有什么不同,先来看一段代码:
- import pprint
- from types import SimpleNamespace
- K = [str(i) for i in range(10)]
- L = [str(i)*20 for i in range(10)]
- D = dict(zip(K, L))
- sn = SimpleNamespace(**D)
- pp = pprint.PrettyPrinter(indent=4)
- pp.pprint(sn)
其中变量sn返回的是一个命名空间,其结构类似于一个dict字典,来看用之前版本的python运行脚本的输出结果:
再来看python3.9中使用pprint的输出结果(下图),这下知道区别在哪了吧。
此外,python3.9中还对其他几个模块进行了修改,例如venv、os等,不过有些改动只针对特定操作系统(比如linux),这里不再一一罗列了。
3 其他优化
除了上面提到的一些改动,python3.9还对一些比较底层的东西进行了优化,这部分内容我想大多数python使用者都涉及不到,大家了解下就好。
1).Build和C API
- 提供Py_EnterRecursiveCall()和Py_LeaveRecursiveCall()作为limited API的常规函数。从stable API中删除_Py_CheckRecursionLimit。
- 向C API添加一个新的公共函数PyObject_CallNoArgs(),这个函数可以调用不含参数的可调用Python对象。
- 全局变量PyStructSequence_UnnamedField在python3.9中修改为常量字符串。
- 从Py_LIMITED_API.pyfpe.h中剔除PyFPE_START_PROTECT()和PyFPE_END_PROTECT()函数。
- 删除PyMethod_ClearFreeList()和PyCFunction_ClearFreeList()函数。
2).方法调整
- 在之前的版本中,math.factorial()函数只接受非负整数值,否则将引发ValueError。在python3.9中该函数将弃用,任何参数都将引发TypeError。
- 弃用parser模块,并将在以后的Python版本中删除。
- 修改random模块的seeds类型,今后只支持None,int,float,str,bytes和bytearray类型。
- 始终允许打开GzipFile文件进行读写,即使不指定mode参数也不会发出警告。
- 推荐使用_tkinter.TkappType的splitlist()方法代替split()方法。
3).移除模块
- collection.abc 里面的抽象基类将不在常规的 collection 模块中公开。
- 删除 sys.getcheckinterval() 和 sys.setcheckinterval() 函数。
- 删除threading.Thread 的 isAlive() 方法。
- 删除 ElementTree 中的getchildren() 和 getiterator()方法。
- 删除 旧 plistlib 模块的实现,同时删除其中的use_builtin_types 参数。
小结:
总的来说,目前python3.9相对于3.8改变并不是很大,而且大多数都是一些偏底层的东西,我们一般用户很少会碰到。不过,目前推出的python3.9.0a1只是第一个迭代版本,并不是正式版本。后续也有可能会有其他变动,但是按理说变化也不会太大。基于以上原因,个人认为大家可以继续使用现有的版本学习和工作,如果你现在的版本不算很老的话。