Python内置函数详细解析
作者: 编程学习网
前言:
Python 自带了很多的内置函数,极大地方便了我们的开发,下面就来挑几个内置函数,看看底层是怎么实现的。
内置函数位于 Python/bitlinmodule.c 中。
1.abs
abs 的功能是取一个整数的绝对值,或者取一个复数的模。
static PyObject * builtin_abs(PyObject *module, PyObject *x) { return PyNumber_Absolute(x); }
该函数调用了 PyNumber_Absolute。
//Objects/abstract.c PyObject * PyNumber_Absolute(PyObject *o) { PyNumberMethods *m; if (o == NULL) { return null_error(); } //通过类型对象获取操作簇 PyNumberMethods m = o->ob_type->tp_as_number; //调用 nb_absolute if (m && m->nb_absolute) return m->nb_absolute(o); return type_error("bad operand type for abs(): '%.200s'", o); }
我们以整型为例,它的 nb_absoulte 指向 long_absolute。
//Objects/longobject.c static PyObject * long_abs(PyLongObject *v) { if (Py_SIZE(v) < 0) //如果 v 小于 0,那么取相反数 return long_neg(v); else //否则返回本身 return long_long((PyObject *)v); }
由于 Python3 的整数是以数组的方式存储的,所以不会直接取相反数,还要做一些额外的处理,但从数学上直接理解为取相反数即可。
2.all
接收一个可迭代对象,如果里面的元素全部为真,则返回 True;只要有一个不为真,则返回 False。
static PyObject * builtin_all(PyObject *module, PyObject *iterable) { PyObject *it, *item; PyObject *(*iternext)(PyObject *); int cmp; //获取可迭代对象的迭代器 it = PyObject_GetIter(iterable); if (it == NULL) return NULL; //拿到内部的 __next__ 方法 iternext = *Py_TYPE(it)->tp_iternext; for (;;) { //迭代元素 item = iternext(it); //返回 NULL,说明出异常了 //一种是迭代完毕抛出的 StopIteration //另一种是迭代过程中出现的异常 if (item == NULL) break; //判断 item 的布尔值是否为真 //cmp > 0 表示为真 //cmp == 0表示为假 //cmp < 0 表示解释器调用出错(极少发生) cmp = PyObject_IsTrue(item); Py_DECREF(item); if (cmp < 0) { Py_DECREF(it); return NULL; } //只要有一个元素为假,就返回 False if (cmp == 0) { Py_DECREF(it); Py_RETURN_FALSE; } } Py_DECREF(it); //PyErr_Occurred() 为真表示出现异常了 if (PyErr_Occurred()) { //判断异常是不是 StopIteration if (PyErr_ExceptionMatches(PyExc_StopIteration)) //如果是,那么表示迭代正常结束 //PyErr_Clear() 负责将异常清空 PyErr_Clear(); else return NULL; } //走到这,说明所有的元素全部为真 //返回 True,等价于 return Py_True Py_RETURN_TRUE; }
因此 all 就是一层 for 循环,但它是 C 的循环,所以比我们写的 Python 代码快。
3.any
接收一个可迭代对象,只要里面有一个元素为真,则返回 True;如果全为假,则返回 False。
static PyObject * builtin_any(PyObject *module, PyObject *iterable) { //源码和 builtin_all 是类似的 PyObject *it, *item; PyObject *(*iternext)(PyObject *); int cmp; //获取可迭代对象的迭代器 it = PyObject_GetIter(iterable); if (it == NULL) return NULL; //拿到内部的 __next__ 方法 iternext = *Py_TYPE(it)->tp_iternext; for (;;) { //迭代元素 item = iternext(it); if (item == NULL) break; cmp = PyObject_IsTrue(item); Py_DECREF(item); if (cmp < 0) { Py_DECREF(it); return NULL; } //只要有一个为真,则返回 True if (cmp > 0) { Py_DECREF(it); Py_RETURN_TRUE; } } Py_DECREF(it); if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_StopIteration)) PyErr_Clear(); else return NULL; } //全部为假,则返回 False Py_RETURN_FALSE; }
4.callable
判断一个对象是否可调用。
static PyObject * builtin_callable(PyObject *module, PyObject *obj) { return PyBool_FromLong((long)PyCallable_Check(obj)); } PyBool_FromLong 是将一个整数转成布尔值,所以就看 PyCallable_Check 是返回 0,还是返回非 0。 int PyCallable_Check(PyObject *x) { if (x == NULL) return 0; return x->ob_type->tp_call != NULL; }
逻辑非常简单,一个对象是否可调用,就看它的类型对象有没有实现 __call__。
5.dir
如果不接收任何对象,返回当前的 local 空间;否则返回某个对象的所有属性的名称。
static PyObject * builtin_dir(PyObject *self, PyObject *args) { PyObject *arg = NULL; //要么不接收参数,要么接收一个参数 //如果没有接收参数,那么 arg 就是 NULL,否则就是我们传递的参数 if (!PyArg_UnpackTuple(args, "dir", 0, 1, &arg)) return NULL; return PyObject_Dir(arg); }
该函数调用了 PyObject_Dir。
//Objects/object.c PyObject * PyObject_Dir(PyObject *obj) { //当 obj 为 NULL,说明我们没有传参,那么返回 local 空间 //否则返回对象的所有属性的名称 return (obj == NULL) ? _dir_locals() : _dir_object(obj); }
先来看看 _dir_locals 函数。
//Objects/object.c static PyObject * _dir_locals(void) { PyObject *names; PyObject *locals; //获取当前的 local 空间 locals = PyEval_GetLocals(); if (locals == NULL) return NULL; //拿到所有的 key,注意:PyMapping_Keys 返回的是列表 names = PyMapping_Keys(locals); if (!names) return NULL; if (!PyList_Check(names)) { PyErr_Format(PyExc_TypeError, "dir(): expected keys() of locals to be a list, " "not '%.200s'", Py_TYPE(names)->tp_name); Py_DECREF(names); return NULL; } //排序 if (PyList_Sort(names)) { Py_DECREF(names); return NULL; } //返回 return names; }
还是比较简单的,然后是 _dir_object,它的代码比较多,这里就不看了。但是逻辑很简单,就是调用对象的 dir 方法,将得到的列表排序后返回。
6.id
查看对象的内存地址,我们知道 Python 虽然一切皆对象,但是我们拿到的都是指向对象的指针。比如 id(name) 是查看变量 name 指向对象的地址,说白了不就是 name 本身吗?所以直接将指针转成整数之后返回即可,
static PyObject * builtin_id(PyModuleDef *self, PyObject *v) { //将 v 转成整数,返回即可 PyObject *id = PyLong_FromVoidPtr(v); if (id && PySys_Audit("builtins.id", "O", id) < 0) { Py_DECREF(id); return NULL; } return id; }
7.locals 和 globals
这两者是查看当前的 local 空间和 global 空间,显然直接通过栈帧的 f_locals 和 f_globals 字段即可获取。
static PyObject * builtin_locals_impl(PyObject *module) { PyObject *d; //在内部会通过线程状态对象拿到栈帧 //再通过栈帧的 f_locals 字段拿到 local 空间 d = PyEval_GetLocals(); Py_XINCREF(d); return d; } static PyObject * builtin_globals_impl(PyObject *module) { PyObject *d; //和 PyEval_GetLocals 类似 d = PyEval_GetGlobals(); Py_XINCREF(d); return d; }
8.hash
获取对象的哈希值。
static PyObject * builtin_hash(PyObject *module, PyObject *obj) { Py_hash_t x; //在内部会调用 obj -> ob_type -> tp_hash(obj) x = PyObject_Hash(obj); if (x == -1) return NULL; return PyLong_FromSsize_t(x); }
9.sum
接收一个可迭代对象,计算它们的和。但是这里面有一个需要注意的地方。
print(sum([1, 2, 3])) # 6 try: print(sum(["1", "2", "3"])) except TypeError as e: print(e) # unsupported operand type(s) for +: 'int' and 'str'
咦,字符串明明也支持加法呀,为啥不行呢?其实 sum 还可以接收第二个参数,我们不传的话就是 0。
也就是说 sum([1, 2, 3]) 其实是 0 + 1 + 2 + 3;那么同理,sum(["a", "b", "c"]) 其实是 0 + "a" + "b" + "c";所以上面的报错信息是不支持类型为 int 和 str 的实例进行相加。
try: print(sum(["1", "2", "3"], "")) except TypeError as e: print(e) # sum() can't sum strings [use ''.join(seq) instead] # 我们看到还是报错了,只能说明理论上是可以的 # 但 Python 建议我们使用 join # 我们用列表举例吧 try: print(sum([[1], [2], [3]])) except TypeError as e: print(e) # unsupported operand type(s) for +: 'int' and 'list' # 告诉我们 int 的实例和 list 的实例不可以相加 # 将第二个参数换成空列表 print(sum([[1], [2], [3]], [])) # [1, 2, 3] # 如果不是空列表呢? print( sum([[1], [2], [3]], ["古明地觉"]) ) # ['古明地觉', 1, 2, 3]
所以 sum 是将第二个参数和第一个参数(可迭代对象)里面的元素依次相加,然后看一下底层实现。
static PyObject * builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) { //result 就是返回值,初始等于第二个参数 //如果可迭代对象为空,那么返回的就是第二个参数 //比如 sum([], 123) 得到的就是 123 PyObject *result = start; PyObject *temp, *item, *iter; //获取可迭代对象的类型对象 iter = PyObject_GetIter(iterable); if (iter == NULL) return NULL; //如果 result 为 NULL,说明我们没有传递第二个参数 if (result == NULL) { //那么 result 赋值为 0 result = PyLong_FromLong(0); if (result == NULL) { Py_DECREF(iter); return NULL; } } else { //否则的话,检测是不是 str、bytes、bytearray 类型 //如果是的话,依旧报错,并提示使用 join 方法 if (PyUnicode_Check(result)) { PyErr_SetString(PyExc_TypeError, "sum() can't sum strings [use ''.join(seq) instead]"); Py_DECREF(iter); return NULL; } if (PyBytes_Check(result)) { PyErr_SetString(PyExc_TypeError, "sum() can't sum bytes [use b''.join(seq) instead]"); Py_DECREF(iter); return NULL; } if (PyByteArray_Check(result)) { PyErr_SetString(PyExc_TypeError, "sum() can't sum bytearray [use b''.join(seq) instead]"); Py_DECREF(iter); return NULL; } Py_INCREF(result); } #ifndef SLOW_SUM //这里是快分支 //假设所有元素都是整数 if (PyLong_CheckExact(result)) { //将所有整数都迭代出来,依次相加 //... } if (PyFloat_CheckExact(result)) { //将所有浮点数都迭代出来,依次相加 //... } #endif //如果不全是整数或浮点数,执行通用逻辑 for(;;) { //迭代元素 item = PyIter_Next(iter); if (item == NULL) { /* error, or end-of-sequence */ if (PyErr_Occurred()) { Py_DECREF(result); result = NULL; } break; } //和 result 依次相加 temp = PyNumber_Add(result, item); Py_DECREF(result); Py_DECREF(item); result = temp; if (result == NULL) break; } Py_DECREF(iter); //返回 return result; }
一个小小的 sum,代码量还真不少呢,我们还省略了一部分。
10.getattr、setattr、delattr
这几个应该已经很熟悉了,先来看看 getattr,它是获取对象的某个属性,并且还可以指定默认值。
static PyObject * builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs) { PyObject *v, *name, *result; //参数个数必须是 2 或 3 //对象、属性名、可选的默认值 if (!_PyArg_CheckPositional("getattr", nargs, 2, 3)) return NULL; //获取对象和属性名 v = args[0]; name = args[1]; //name必须是字符串 if (!PyUnicode_Check(name)) { PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string"); return NULL; } //调用对象的 __getattr__,找不到返回默认值 if (nargs > 2) { if (_PyObject_LookupAttr(v, name, &result) == 0) { PyObject *dflt = args[2]; Py_INCREF(dflt); return dflt; } } else { result = PyObject_GetAttr(v, name); } return result; }
同理 setattr 是调用对象的 __setattr__,delattr 是调用对象的 __delattr__。
到此这篇关于Python内置函数详细解析的文章就介绍到这了,更多相关Python内置函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!