读 Python behind the scenes #5: how variables are implemented in CPython

https://tenthousandmeters.com/blog/python-behind-the-scenes-5-how-variables-are-implemented-in-cpython/

通过这一章我们将尝试理解,下面这一行简短的 python 代码背后,究竟发生了什么?

a = b

初探字节码

首先将代码片段转化为字节码:

$ echo 'a = b' | python3.9 -m dis
  1           0 LOAD_NAME                0 (b)
              2 STORE_NAME               1 (a)
...

从之前的章节了解到,CPython VM 通过 value stack 执行计算:

  1. 第一步:LOAD_NAME 获取 b 变量的值,并压入 stack 中
  2. 第二步:STORE_NAME 从 stack 中弹出值,并赋值给 a

STORE_NAME

上一章的结尾提到,每个字节码对应 Python/ceval.c 大循环中的一个 switch 语句,所以我们可通过该方法快速查看 STORE_NAME 对应的源码实现:

// "2 STORE_NAME               1 (a)"
case TARGET(STORE_NAME): {
    // 1. 通过字节码的参数 oparg(index 位置),获取变量名(string 对象)
    PyObject *name = GETITEM(names, oparg);
    // 2. 从 value stack 中 pop 一个值(python 对象的引用)
    PyObject *v = POP();
    PyObject *ns = f->f_locals;
    int err;
    if (ns == NULL) {
        _PyErr_Format(tstate, PyExc_SystemError,
                      "no locals found when storing %R", name);
        Py_DECREF(v);
        goto error;
    }
    // 3. 关联 `*name` & `*v`
    // P.S. 通过 frame object 中的 f_locals 变量维护 mapping 关系 
    if (PyDict_CheckExact(ns))
        err = PyDict_SetItem(ns, name, v);
    else
        err = PyObject_SetItem(ns, name, v);
    Py_DECREF(v);
    if (err != 0)
        goto error;
    DISPATCH();
}

LOAD_NAME

LOAD_NAME 相对更加复杂一些,因为 VM 会从 f_locals 以及多个位置获取变量的值:

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论