大师兄的Python源码学习笔记(十二): Python虚拟机中的一般表达式(一)

大师兄的Python源码学习笔记(十一): Python的虚拟机框架
大师兄的Python源码学习笔记(十三): Python虚拟机中的一般表达式(二)

一、模拟查看.pyc文件的结构

  • 我们可以自己创建一个方法模拟读取.pyc文件。
demo.py 

>>>i = 1
>>>s = "str"
>>>d = {}
>>>l = []
>>>st = {1}
>>>tp = (1,)
pycparser.py

>>>import sys
>>>import demo
>>>import types
>>>import marshal
>>>import dis

>>>def show_code(code, indent=''):
>>>    print(f"{indent}code")
>>>    indent += '   '
>>>    print(f"{indent}argcount {code.co_argcount}")
>>>    print(f"{indent}nlocals {code.co_nlocals}")
>>>    print(f"{indent}stacksize {code.co_stacksize}")
>>>    print(f"{indent}flags {code.co_flags:04x}")
>>>    dis.disassemble(code)
>>>    print(f"{indent}consts")
>>>    for const in code.co_consts:
>>>        if isinstance(const, types.CodeType):
>>>            show_code(const, indent + '   ')
>>>        else:
>>>            print(f"   {indent}{const}")
>>>    print(f"{indent}names {code.co_names}")
>>>    print(f"{indent}varnames {code.co_varnames}")
>>>    print(f"{indent}freevars {code.co_freevars}")
>>>    print(f"{indent}cellvars {code.co_cellvars}")
>>>    print(f"{indent}filename {code.co_filename}")
>>>    print(f"{indent}name {code.co_name}")
>>>    print(f"{indent}firstlineno {code.co_firstlineno}")

>>>def show_file():
>>>    header_sizes = [(8, (0, 9, 2)), (12, (3, 6)), (16, (3, 7))]
>>>    header_size = next(s for s, v in reversed(header_sizes) if sys.version_info >= v)

>>>    print(isinstance(demo, types.ModuleType))
>>>    with open(demo.__cached__, "rb") as f:
>>>        metadata = f.read(header_size)
>>>        code = marshal.load(f)
>>>        print(code)
>>>        show_code(code)

>>>if __name__ == '__main__':
>>>    show_file()
True
<code object <module> at 0x000001ED4C1F6E40, file "D:\demo.py", line 1>
code
   argcount 0
   nlocals 0
   stacksize 1
   flags 0040
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (i)

  2           4 LOAD_CONST               1 ('str')
              6 STORE_NAME               1 (s)

  3           8 BUILD_MAP                0
             10 STORE_NAME               2 (d)

  4          12 BUILD_LIST               0
             14 STORE_NAME               3 (l)

  5          16 LOAD_CONST               0 (1)
             18 BUILD_SET                1
             20 STORE_NAME               4 (st)

  6          22 LOAD_CONST               2 ((1,))
             24 STORE_NAME               5 (tp)
             26 LOAD_CONST               3 (None)
             28 RETURN_VALUE
   consts
      1
      str
      None
   names ('i', 's', 'd', 'l', 'set', 'st', 'tuple', 'tp')
   varnames ()
   freevars ()
   cellvars ()
   filename D:\demo.py
   name <module>
   firstlineno 1

二、简单内建对象的创建

  • _PyEval_EvalFrameDefault方法中,包含了大量的宏包括了对栈的各种操作:
  • 访问tuple中的元素:
#ifndef Py_DEBUG
#define GETITEM(v, i) PyTuple_GET_ITEM((PyTupleObject *)(v), (i))
#else
#define GETITEM(v, i) PyTuple_GetItem((v), (i))
#endif
  • 调整栈顶指针:
#define BASIC_STACKADJ(n) (stack_pointer += n)
#define STACKADJ(n)     { (void)(BASIC_STACKADJ(n), \
                         lltrace && prtrace(TOP(), "stackadj")); \
                         assert(STACK_LEVEL() <= co->co_stacksize); }
  • 入栈操作:
#define BASIC_PUSH(v)     (*stack_pointer++ = (v))
#define PUSH(v)         { (void)(BASIC_PUSH(v), \
                         lltrace && prtrace(TOP(), "push")); \
                         assert(STACK_LEVEL() <= co->co_stacksize); }
  • 出栈操作:
#define BASIC_POP()       (*--stack_pointer)
#define POP()           ((void)(lltrace && prtrace(TOP(), "pop")), \
                        BASIC_POP())
1.1 创建整数和字符串变量
  • 截取demo.pyc中创建整数值i的字节码
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (i)
  • 其中第一行的LOAD_CONST在虚拟机的执行动作如下:
ceval.c

TARGET(LOAD_CONST) {
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
  • GETITEM(consts,oparg)实际上就是调用了宏GETITEM(v, i),即PyTuple_GET_ITEM((PyTupleObject *)(v), (i))
  • consts实际上就是PyCodeObject中的co_consts,也称为常量表。
  • PUSH(v)宏则将从co_consts中读取的对象塞入栈中。
  • 第二行STORE_NAME在虚拟机的执行动作如下:
ceval.c

...
case STORE_NAME:
        {
            PyObject *names = f->f_code->co_names;
            PyObject *name = GETITEM(names, oparg);
            PyObject *locals = f->f_locals;
            if (locals && PyDict_CheckExact(locals) &&
                PyDict_GetItem(locals, name) == v) {
                if (PyDict_DelItem(locals, name) != 0) {
                    PyErr_Clear();
                }
            }
            break;
        }
...
  • 这条指令改变了locals的名字空间,并创建了变量和值的映射关系(i=1)。
1.2 创建字典
  • 截取demo.py中创建字典的字节码:
  3           8 BUILD_MAP                0
             10 STORE_NAME               2 (d)
  • 其中第一行BUILD_MAP在虚拟机的执行动作如下:
ceval.c

TARGET(BUILD_MAP) {
            Py_ssize_t i;
            PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg);
            if (map == NULL)
                goto error;
            for (i = oparg; i > 0; i--) {
                int err;
                PyObject *key = PEEK(2*i);
                PyObject *value = PEEK(2*i - 1);
                err = PyDict_SetItem(map, key, value);
                if (err != 0) {
                    Py_DECREF(map);
                    goto error;
                }
            }

            while (oparg--) {
                Py_DECREF(POP());
                Py_DECREF(POP());
            }
            PUSH(map);
            DISPATCH();
        }
  • 这段代码首先创建了一个空的PyDictObject对象。
  • 将键值对加入到PyDictObject对象中。
  • 最后将PyDictObject压入到运行时栈中。
  • 第二行STORE_NAME与上一节相同,创建了locals的名字空间并创建了映射关系。
1.3 创建列表
  • 截取demo.py中创建列表的字节码:
  4          12 BUILD_LIST               0
             14 STORE_NAME               3 (l)
  • 其中第一行BUILD_LIST在虚拟机的执行动作如下:
ceval.c

        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();
        }
  • 这段代码首先创建了一个oparg长度的PyListObject对象。
  • 将每一个元素从运行时栈中弹出,之后塞到PyListObject对象中。
  • 最后将PyListObject对象塞入到运行时栈中。
  • 第二行STORE_NAME与上一节相同,创建了locals的名字空间并创建了映射关系。
1.4 创建set
  • 截取demo.py中创建set的字节码:
  5          16 LOAD_CONST               0 (1)
             18 BUILD_SET                1
             20 STORE_NAME               4 (st)
  • 第一行LOAD_CONST将PySetObject中的唯一一个元素1塞入栈中。
  • 第二行BUILD_SET在虚拟机的执行动作如下:
ceval.c

        TARGET(BUILD_SET) {
            PyObject *set = PySet_New(NULL);
            int err = 0;
            int i;
            if (set == NULL)
                goto error;
            for (i = oparg; i > 0; i--) {
                PyObject *item = PEEK(i);
                if (err == 0)
                    err = PySet_Add(set, item);
                Py_DECREF(item);
            }
            STACKADJ(-oparg);
            if (err != 0) {
                Py_DECREF(set);
                goto error;
            }
            PUSH(set);
            DISPATCH();
        }
  • 这段代码首先创建了一个空的PySetObject对象。
  • 将每一个元素从运行时栈中弹出,之后塞到PySetObject对象中。
  • 最后将PySetObject对象塞入到运行时栈中。
  • 第三行STORE_NAME,创建了locals的名字空间并创建了映射关系。
1.5 创建tuple
  • 截取demo.py中创建tuple的字节码:
  6          22 LOAD_CONST               2 ((1,))
             24 STORE_NAME               5 (tp)
  • 这里的第一行LOAD_CONST稍有不同,实际上是触发了宏:
#define GETITEM(v, i) PyTuple_GET_ITEM((PyTupleObject *)(v), (i))
  • 直接将PyTupleObject对象塞入栈中。
  • 第二行STORE_NAME,创建了locals的名字空间并创建了映射关系。
1.6 返回值
  • 截取demo.py中最后两行字节码:
             26 LOAD_CONST               3 (None)
             28 RETURN_VALUE
  • 第一行LOAD_CONST首先往栈中压入了一个空值,这里只是个过场,并没有实际价值。
  • 第二行RETURN_VALUE在虚拟机中的执行动作如下:
ceval.c

        TARGET(RETURN_VALUE) {
            retval = POP();
            why = WHY_RETURN;
            goto fast_block_end;
        }
ceval.c

fast_block_end:
        assert(why != WHY_NOT);

        /* Unwind stacks if a (pseudo) exception occurred */
        while (why != WHY_NOT && f->f_iblock > 0) {
            /* Peek at the current block. */
            PyTryBlock *b = &f->f_blockstack[f->f_iblock - 1];

            assert(why != WHY_YIELD);
            if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) {
                why = WHY_NOT;
                JUMPTO(PyLong_AS_LONG(retval));
                Py_DECREF(retval);
                break;
            }
            /* Now we have to pop the block. */
            f->f_iblock--;

            if (b->b_type == EXCEPT_HANDLER) {
                UNWIND_EXCEPT_HANDLER(b);
                continue;
            }
            UNWIND_BLOCK(b);
            if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
                why = WHY_NOT;
                JUMPTO(b->b_handler);
                break;
            }
            if (why == WHY_EXCEPTION && (b->b_type == SETUP_EXCEPT
                || b->b_type == SETUP_FINALLY)) {
                PyObject *exc, *val, *tb;
                int handler = b->b_handler;
                _PyErr_StackItem *exc_info = tstate->exc_info;
                /* Beware, this invalidates all b->b_* fields */
                PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
                PUSH(exc_info->exc_traceback);
                PUSH(exc_info->exc_value);
                if (exc_info->exc_type != NULL) {
                    PUSH(exc_info->exc_type);
                }
                else {
                    Py_INCREF(Py_None);
                    PUSH(Py_None);
                }
                PyErr_Fetch(&exc, &val, &tb);
                /* Make the raw exception data
                   available to the handler,
                   so a program can emulate the
                   Python main loop. */
                PyErr_NormalizeException(
                    &exc, &val, &tb);
                if (tb != NULL)
                    PyException_SetTraceback(val, tb);
                else
                    PyException_SetTraceback(val, Py_None);
                Py_INCREF(exc);
                exc_info->exc_type = exc;
                Py_INCREF(val);
                exc_info->exc_value = val;
                exc_info->exc_traceback = tb;
                if (tb == NULL)
                    tb = Py_None;
                Py_INCREF(tb);
                PUSH(tb);
                PUSH(val);
                PUSH(exc);
                why = WHY_NOT;
                JUMPTO(handler);
                break;
            }
            if (b->b_type == SETUP_FINALLY) {
                if (why & (WHY_RETURN | WHY_CONTINUE))
                    PUSH(retval);
                PUSH(PyLong_FromLong((long)why));
                why = WHY_NOT;
                JUMPTO(b->b_handler);
                break;
            }
        } /* unwind stack */

        /* End the loop if we still have an error (or return) */

        if (why != WHY_NOT)
            break;

        assert(!PyErr_Occurred());

    } /* main loop */

    assert(why != WHY_YIELD);
    /* Pop remaining stack entries. */
    while (!EMPTY()) {
        PyObject *o = POP();
        Py_XDECREF(o);
    }

    if (why != WHY_RETURN)
        retval = NULL;

    assert((retval != NULL) ^ (PyErr_Occurred() != NULL));
  • 这段代码进行出栈操作,清空运行时栈,并将信息装到local名字空间中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容