Python 源码剖析-INT对象(上)

INT函数与对象剖析(上)

引言

很多人都说 Python 是一门很容易上手学习的语言,那是因为前人已经将复杂的过程包装好,留给你的是一个 叫做 Python 的 C 项目黑盒。 本篇带你走进Python 底层的函数调用过程 以及 Python的整型对象PyIntObject 对象,来看看前辈如何帮助后生简化大量的与操作系统交互的工作的。文表如下

深入

反汇编代码片段 [int_test.py]

a = int(111)

print a

在源码中查看

我们可以看到Python虚拟机首先通过CALL_FUNCTION 为对象a赋值了111,其中内存的分配,引用计数(垃圾回收),x86平台的堆栈模拟等等都对外隐藏了。带着问题深究Int对象的源码机制

        TARGET(CALL_FUNCTION)

        {

            PyObject **sp;

            sp = stack_pointer;

            x = call_function(&sp, oparg);

            stack_pointer = sp;

            PUSH(x);

            if (x != NULL) DISPATCH();

            break;

        }

上面的stack_pointer 就是模拟了x86平台的栈顶指针,这里介绍一下堆栈结构(函数加载调用)的重要三个指针:

- ESP 栈顶指针【指向一个先进后出的结构stack的顶部】

- EBP 栈底指针

- EIP 函数下一条的指令地址

如上 sp = stack_pointer (sp 指向*Next free slot in value stack* 的地址,也就是PyFunctionObject对象)

继续跟进 call_function[ceval.c]

static PyObject *

call_function(PyObject ***pp_stack, int oparg)

{

    int na = oparg & 0xff;  

    //高8位是位置参数个数*args

    int nk = (oparg>>8) & 0xff; 

    //低8位是键参数个数**kwargs

    int n = na + 2 * nk;

    PyObject **pfunc = (*pp_stack) - n - 1;

    PyObject *func = *pfunc;

    PyObject *x, *w;


    if (PyCFunction_Check(func) && nk == 0) {

        //如果func类型为 "builtin_function_or_method"

        int flags = PyCFunction_GET_FLAGS(func);

        PyThreadState *tstate = PyThreadState_GET();

        if (flags & (METH_NOARGS | METH_O)) {

            //[1] 如果参数为0或者的参数全部为PyObject 

            ...

        }

        else {

            PyObject *callargs;

           //调用builtin 方法如dir,input PyCFunction_Call(func,callargs,NULL);

        }

    } else {

        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {

            //[2] 如果func函数类型为"instancemethod"

            ...

        } else

            Py_INCREF(func);

        if (PyFunction_Check(func))

        // [3] 如果func函数类型为"function"

            x = fast_function(func, pp_stack, n, na, nk);

        else

            printf("default called \n");

            x = do_call(func, pp_stack, na, nk);

    }

    ...

    return x;

}

 可以发现,调用call_function 会取出模拟的栈帧环境的参数,func对象,然后分别判断该对象是 function 还是 instancemethod 或者是 builtin_function_or_method,很显然 ,通过终端 type(int) == type,可知python虚拟机在初始化的时候已经 将 int->ob_type = &PyType_Type 了,所以走在最后的分支, 添加输出,重新编译 python27.dll ,将其放在python跟路径下用来劫持/libs/python27.dll,看看结果:

int(111)

default called

111

如上, int 最终调用会进入 do_call(func,pp_stack,na,nk)

注意:

-type(inupt) == 'builtin_function_or_method'

def 定义的函数func type(func) == 'function'

跟进do_call

static PyObject *

do_call(PyObject *func, PyObject ***pp_stack, int na, int nk)

{

    ...//一系列判断省略

    result = PyObject_Call(func, callargs, kwdict);

    return result;

}

跟进 PyObject_Call

PyObject *

PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)

{

    ternaryfunc call;

    if ((call = func->ob_type->tp_call) != NULL) {

        PyObject *result;

        result = (*call)(func, arg, kw);

        return result;

    }

    PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",func->ob_type->tp_name);

    return NULL;

}

由上述 call = func->ob_type->tp_call 可知:

func 为 int, int->ob_type == 'type', type->tp_call 就是PyTypeObject 的 调用:

tp_call [typeobject.c]

static PyObject *

type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)

{

    PyObject *obj;

    obj = type->tp_new(type, args, kwds);

    if (obj != NULL) {

        ...

        type = obj->ob_type;

        if (tp_init(obj, args, kwds) < 0) {

            Py_DECREF(obj);

            obj = NULL;

        }

    }

    return obj;

}

可以清楚的看到 调用了 type->tp_new, type->tp_init 分别进行了实例的生成和实例的初始化, (详情类比参考python __new__, __init__ 方法)

也就是分别走向 PyIntObject 的 int_new, int_init 分支。

走进 intobject.c -> int_new()

static PyObject *

int_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

{

    printf("int_new called");

    PyObject *x = NULL;

    int base = -909;

    static char *kwlist[] = {"x", "base", 0}; 

    if (type != &PyInt_Type)

        return int_subtype_new(type, args, kwds); /* Wimp out */

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:int", kwlist,

                                     &x, &base))

        //PyArg_ParseTupleAndKeywords 将参数赋值到 x, base 上,详情参考Python官方文档

        return NULL;

    if (x == NULL) {

        if (base != -909) {

            PyErr_SetString(PyExc_TypeError,

                            "int() missing string argument");

            return NULL;

        }

        return PyInt_FromLong(0L);

    }

    if (base == -909)

        return PyNumber_Int(x);

    if (PyString_Check(x)) {

        //判断x 是否为 字符串

        char *string = PyString_AS_STRING(x);

        ...

        return PyInt_FromString(string, NULL, base);

    }

    if (PyUnicode_Check(x))

        //判断 X 是否为 unicode 类型

        return PyInt_FromUnicode(PyUnicode_AS_UNICODE(x),

                                 PyUnicode_GET_SIZE(x),

                                 base);

    PyErr_SetString(PyExc_TypeError,

                    "int() can't convert non-string with explicit base");

    return NULL;

}

从上面的源代码我们可以看到 PyArg_ParseTupleAndKeywords 支持的 int参数可以为键参数 ,键值必须为 x 或者 base.

这里的base有什么作用? 来看以下几个案例

>>int(x=1)

>>1

>>int(base=-909,x=2333)

>>2333

>>int(x=u'111')

>>111

>>>int(base=2,x='10')     #2进制

>>>int(base=10,x='10')    #10

>>>int(base=16,x='10')    #16

>>>int(base=0xff,x=u'10')

>>>ValueError:int() base must be >= 2 and <= 36

从上可以得出结论,base参数控制着进制

通过修改源码输出可以观察到:

创建Int对象的方式:

- PyNumber_Int(x);

- PyInt_FromString(string, NULL, base);

- PyInt_FromUnicode(PyUnicode_AS_UNICODE(x),PyUnicode_GET_SIZE(x), base);

- PyInt_FromLong(0L);

在下一篇中将会详细介绍这几个函数~


总结

一个小小的int() 调用过程到此还未结束,说编程容易实属未看透事实的本质,本章暂且不谈 .py 文件 是如何 编译为 .pyc 文件的,加载的 CALL_FUNCTION 其实就是一个二进制符号等等。

总之,想要变得不一样,就要让自己变得更深邃,不要停留在表象里。

2018-06-14 00:51 星期四

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

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,798评论 0 38
  • 包(lib)、模块(module) 在Python中,存在包和模块两个常见概念。 模块:编写Python代码的py...
    清清子衿木子水心阅读 3,807评论 0 27
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,698评论 0 3
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,031评论 0 2
  • 今天boy来青,有点紧张,希望我们能好好谈一谈,做出个了断吧。我的打算是这样的,但愿不要再有纠缠。今天要学习的短语...
    徐徐Junly阅读 245评论 0 1