美文网首页
Python 源码剖析-INT对象(上)

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

作者: 敬贤icode121 | 来源:发表于2019-02-02 17:02 被阅读0次

    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 星期四

    相关文章

      网友评论

          本文标题:Python 源码剖析-INT对象(上)

          本文链接:https://www.haomeiwen.com/subject/yrnssqtx.html