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 星期四
网友评论