python-3.x 为什么列表解析在内部创建一个函数?

s4chpxco  于 2023-11-20  发布在  Python
关注(0)|答案(1)|浏览(112)

这是python 3.10中列表解析的反汇编:

Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis
>>> 
>>> dis.dis("[True for _ in ()]")
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x7fea68e0dc60, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_CONST               2 (())
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x7fea68e0dc60, file "<dis>", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 4 (to 14)
              6 STORE_FAST               1 (_)
              8 LOAD_CONST               0 (True)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            2 (to 4)
        >>   14 RETURN_VALUE

字符串
据我所知,它创建了一个名为listcomp的代码对象,它执行实际的迭代并返回结果列表,并立即调用它。我不明白为什么需要创建一个单独的函数来执行这个任务。这是一种优化技巧吗?

kjthegm6

kjthegm61#

创建函数的主要逻辑是isolate the comprehension’s iteration variablepeps.python.org。
创建一个函数:
理解迭代变量保持隔离,不会覆盖外部作用域中同名的变量,也不会在理解之后可见
但是它 * 在运行时效率很低 *。正因为如此,python-3.12实现了一个称为理解内联(PEP 709)的优化peps.python.org,它将不再创建单独的代码objectpeps.python.org。
字典、列表和集合解析现在是内联的,而不是为每次执行解析都创建一个新的一次性函数对象。这使执行解析的速度提高了两倍。有关详细信息,请参阅PEP 709。
下面是用python-3.12反汇编的相同代码的输出。

>>> import dis
>>> 
>>> dis.dis("[True for _ in ()]")
  0           0 RESUME                   0

  1           2 LOAD_CONST               0 (())
              4 GET_ITER
              6 LOAD_FAST_AND_CLEAR      0 (_)
              8 SWAP                     2
             10 BUILD_LIST               0
             12 SWAP                     2
        >>   14 FOR_ITER                 4 (to 26)
             18 STORE_FAST               0 (_)
             20 LOAD_CONST               1 (True)
             22 LIST_APPEND              2
             24 JUMP_BACKWARD            6 (to 14)
        >>   26 END_FOR
             28 SWAP                     2
             30 STORE_FAST               0 (_)
             32 RETURN_VALUE
        >>   34 SWAP                     2
             36 POP_TOP
             38 SWAP                     2
             40 STORE_FAST               0 (_)
             42 RERAISE                  0
ExceptionTable:
  10 to 26 -> 34 [2]

字符串
正如你所看到的,不再有MAKE_FUNCTION操作码。这将导致PEP 709如何为理解变量提供隔离的问题。嗯,这是通过使用LOAD_FAST_AND_CLEAR(偏移量为6)和STORE_FAST(偏移量为30)操作码来完成的。引用PEP 709的 Specification sectionpeps.python.org:
x迭代变量的隔离是通过在偏移量6处组合新的LOAD_FAST_AND_CLEAR操作码来实现的,该操作码在运行解析之前将x的任何外部值保存在堆栈上,而30STORE_FAST在运行解析之后恢复x的外部值(如果有的话)。
这里是基准resultspeps.python.org(用MacOS M2测量)。

$ python3.10 -m pyperf timeit -s 'l = [1]' '[x for x in l]'
Mean +- std dev: 108 ns +- 3 ns
$ python3.12 -m pyperf timeit -s 'l = [1]' '[x for x in l]'
Mean +- std dev: 60.9 ns +- 0.3 ns

相关问题