Python list initialization with values--实现过程?

Python list initialization with values--implementation process?

Python列表用方括号初始化是怎么实现的?它是一个一个地调用一些预先存在的函数,例如 __setitem__,直到创建列表,还是有一个单独的函数接受可变数量的参数并创建一个列表?

l = [1, 2, 3, 4, 5]

例如,上面的列表是如何创建的?我对在源代码中构建列表的过程很感兴趣。实际代码本身,或将列表存储到内存中所执行的步骤。

您可以编译该特定代码片段并使用 dis:

检查字节码
In [1]: import dis

In [2]: code = compile('l = [1, 2, 3, 4, 5]', '', 'exec')

In [3]: dis.dis(code)
  1           0 LOAD_CONST               0 (1)
              3 LOAD_CONST               1 (2)
              6 LOAD_CONST               2 (3)
              9 LOAD_CONST               3 (4)
             12 LOAD_CONST               4 (5)
             15 BUILD_LIST               5
             18 STORE_NAME               0 (l)
             21 LOAD_CONST               5 (None)
             24 RETURN_VALUE

特别是第 15 行,BUILD_LIST,实际构建列表的地方。实际上没有调用构造函数或函数,如本例所示:

In [1108]: dis.dis(compile('l = list()', '', 'exec'))
  1           0 LOAD_NAME                0 (list)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 STORE_NAME               1 (l)
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE

在第 3 行 CALL_FUNCTION 中,调用了 list class' 构造函数。

该语言的语法会解析源代码以构建解析树,以便在运行时创建并准备好使用列表。


关于列表对象的详细实现,请看here

编辑:找到字节码的实现细节。 Here 是的。 BUILD_LIST:

TARGET(BUILD_LIST)
        x =  PyList_New(oparg);
        if (x != NULL) {
            for (; --oparg >= 0;) {
                w = POP();
                PyList_SET_ITEM(x, oparg, w);
            }
            PUSH(x);
            DISPATCH();
        }
        break;

对比CALL_FUNCTION

TARGET(CALL_FUNCTION)
        {
            PyObject **sp;
            PCALL(PCALL_ALL);
            sp = stack_pointer;
#ifdef WITH_TSC
            x = call_function(&sp, oparg, &intr0, &intr1);
#else
            x = call_function(&sp, oparg);
#endif
            stack_pointer = sp;
            PUSH(x);
            if (x != NULL)
                DISPATCH();
            break;
        }

后者进行实际的函数调用,而前者使用 PyList_New 分配对象。

就语言规范而言,它构建了一个列表,但没有关于它如何做到这一点的承诺。如果您希望使用 public 挂钩来自定义流程或其他内容,那不行。你不能那样做。

如果您想了解该实现如何适用于您所使用的 Python 版本,您可以向下挖掘。例如,在 CPython 3.6 上,使用 dis,Python 反汇编程序:

>>> import dis
>>> dis.dis(lambda: [1,2,3,4,5])
  1           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               3 (3)
              6 LOAD_CONST               4 (4)
              8 LOAD_CONST               5 (5)
             10 BUILD_LIST               5
             12 RETURN_VALUE

您可以看到带有参数 5BUILD_LIST 操作码。这会从 Python 字节码参数堆栈中弹出 5 个值,并从中构建一个列表。

查看 BUILD_LIST 的 3.6 source:

TARGET(BUILD_LIST) {
    PyObject *list =  PyList_New(oparg);
    if (list == NULL)
        goto error;
    while (--oparg >= 0) {
        PyObject *item = POP();
        PyList_SET_ITEM(list, oparg, item);
    }
    PUSH(list);
    DISPATCH();
}

我们可以看到它使用 PyList_New 分配列表对象并使用 PyList_SET_ITEM 设置项目,这些例程是列表 C API 的一部分。不涉及 Python 端 API - 没有查找 list 名称,没有 __setitem__append.