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

2019-02-02  本文已影响0人  敬贤icode121

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

上一篇 下一篇

猜你喜欢

热点阅读