# Python map

map 在 Python 中是一个内置类,在初始化时,第一个参数传递 function 对象,后面可以跟一个或多个序列参数

class map(object):
    """
    map(func, *iterables) --> map object
    
    Make an iterator that computes the function using arguments from
    each of the iterables.  Stops when the shortest iterable is exhausted.
    """
    def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass
    def __init__(self, func, *iterables): # real signature unknown; restored from __doc__
	# 在初始化时,必须传递一个 function object, 后面跟一个或者多个序列对象
        pass
    def __iter__(self, *args, **kwargs): # real signature unknown
        """ Implement iter(self). """
	# 支持迭代操作
        pass
    @staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass
    def __next__(self, *args, **kwargs): # real signature unknown
        """ Implement next(self). """
        pass
    def __reduce__(self, *args, **kwargs): # real signature unknown
        """ Return state information for pickling. """
        pass

代码注释说明,创建一个迭代器,使用来自每个可迭代对象的参数计算函数。当最短的迭代用完时停止,可以测试一下

def f(x, y):
    # 返回每个列表的元素相加结果
    return x + y
array1 = [1, 2, 3]
array2 = [1, 2, 3, 4, 5]
r = map(f, array1, array2)
for i in r:
    print(i)
########################## 打印结果 ##########################
>>> 2
>>> 4
>>> 6

先来看看 map 在 CPython 中的结构,位于源代码中 Python/bltinmodule.c

PyTypeObject PyMap_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "map",                              /* tp_name */
    sizeof(mapobject),                  /* tp_basicsize */
    0,                                  /* tp_itemsize */
    /* methods */
    (destructor)map_dealloc,            /* tp_dealloc */
    0,                                  /* tp_vectorcall_offset */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_as_async */
    0,                                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    0,                                  /* tp_call */
    0,                                  /* tp_str */
    PyObject_GenericGetAttr,            /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
        Py_TPFLAGS_BASETYPE,            /* tp_flags */
    map_doc,                            /* tp_doc */
    (traverseproc)map_traverse,         /* tp_traverse */
    0,                                  /* tp_clear */
    0,                                  /* tp_richcompare */
    0,                                  /* tp_weaklistoffset */
    PyObject_SelfIter,                  /* tp_iter */
    (iternextfunc)map_next,     /* tp_iternext */
    map_methods,                        /* tp_methods */
    0,                                  /* tp_members */
    0,                                  /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */
    0,                                  /* tp_init */
    PyType_GenericAlloc,                /* tp_alloc */
    map_new,                            /* tp_new */
    PyObject_GC_Del,                    /* tp_free */
};

在我们比如调用 map (func, array1, array2) 时,首先调用了 map_new 方法

static PyObject *
map_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *it, *iters, *func;
    mapobject *lz;
    Py_ssize_t numargs, i;
    if (type == &PyMap_Type && !_PyArg_NoKeywords("map", kwds))
	// 判断类型是否为 PyMap_Type 并且校验是否传递了关键字参数,如果传递了就抛异常,map 是不接受关键字参数的
        return NULL;
    numargs = PyTuple_Size(args); // 获取位置参数的个数
    if (numargs < 2) {
	// 如果位置参数小于 2,抛异常,map 必须接收一个 func,至少一个可迭代对象
        PyErr_SetString(PyExc_TypeError,
           "map() must have at least two arguments.");
        return NULL;
    }
    // 申请一个元组,容量为 numargs - 1, 用于存放传递的所有可迭代对象的 iterator
    iters = PyTuple_New(numargs-1);
    if (iters == NULL)
	// 申请失败
        return NULL;
    // 根据传递的可迭代对象个数进行 for 循环
    for (i=1 ; i<numargs ; i++) {
        /* Get iterator. */
	// PyTuple_GET_ITEM 根据下标获取对应 i 位置的可迭代对象
	// PyObject_GetIter 通过调用对象的 tp_iter 方法,返回一个 iterator
        it = PyObject_GetIter(PyTuple_GET_ITEM(args, i));
        if (it == NULL) {
	    // 当对象没有定义不是可迭代对象,或者没有定义__iter__, 或者__iter__返回空,就会抛异常
            Py_DECREF(iters);
            return NULL;
        }
	// 在 iters 指定的下标插入 iterator, 与 PyTuple_SetItem 不同,PyTuple_SET_ITEM 不会检查错误,只用于填充元组
        PyTuple_SET_ITEM(iters, i-1, it);
    }
    /* create mapobject structure */
    lz = (mapobject *)type->tp_alloc(type, 0); // 调用 PyMap_Type 的 tp_alloc, 为其实例对象申请空间
    if (lz == NULL) {
	// 申请失败
        Py_DECREF(iters); 引用计数-1
        return NULL;
    }
    lz->iters = iters; // 赋值 iters
    func = PyTuple_GET_ITEM(args, 0); // 获取第一个参数,也就是传递的 function object
    Py_INCREF(func); // 引用计数 + 1, function object 被当作参数传递给 mapobject 了
    lz->func = func; // 赋值 func
    return (PyObject *)lz; // 返回 mapobject
}

当调用 map (func, array1, array2) 时,CPython 返回了一个 mapobject 对象

>>> map(lambda x: print(x), [1,2,3])
<map object at 0x000002771D500F70>

当对 mapobject 进行迭代时,会调用 PyObject_SelfIter 返回本身,然后不断调用__next__,逐步迭代,对应的是 map_next 方法

PyObject *
PyObject_SelfIter(PyObject *obj)
{
    Py_INCREF(obj);
    return obj;
}
static PyObject *
map_next(mapobject *lz)
{
    /*
	_PY_FASTCALL_SMALL_STACK 是一个宏定义,默认为 5
	在 C 堆栈上分配的 PyObject 数组的建议大小(位置参数的数量)以避免在堆内存上分配内存
	如果参数个数小于 5,small_stack 数组会先尝试在栈区分配,就可以在栈中申请,然后通过传递位置参数的方式对函数进行调用
	通过 PyObject_Vectorcall 函数来对函数进行调用
    */
    PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
    PyObject **stack;
    PyObject *result = NULL; // 调用返回值
    PyThreadState *tstate = _PyThreadState_GET(); // 获取当前线程状态对象
    const Py_ssize_t niters = PyTuple_GET_SIZE(lz->iters); //iters 是一个元组,存放着多个迭代器,迭代器的数量
    if (niters <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
	// 如果参数小于等于 5,那么获取这些迭代器中的元素时,可以直接使用在 C 栈中申请的数组进行存储
        stack = small_stack;
    }
    else {
	// 如果大于 5,只能在堆区重新申请
        stack = PyMem_Malloc(niters * sizeof(stack[0]));
        if (stack == NULL) {
	    // 申请失败,传入线程状态对象,设置异常信息
            _PyErr_NoMemory(tstate);
            return NULL;
        }
    }
    Py_ssize_t nargs = 0;
    // 根据存储的迭代器数量进行循环操作
    for (Py_ssize_t i=0; i < niters; i++) {
        PyObject *it = PyTuple_GET_ITEM(lz->iters, i); // 获取 iters 对应下标中的迭代器
        PyObject *val = Py_TYPE(it)->tp_iternext(it); // 执行每个迭代器的__next__函数,比如它是 list 或者 tuple,就分别执行各自的__next__函数	
        if (val == NULL) {	
	    /*
		调用__next__函数返回了空,调用完毕
	        在前面说到:当最短的迭代用完时停止,也就是说,假如传递了 map (func, [1,2,3],["a","b","c","d","e"]),
		当进行第 4 次调用时,第一个迭代器会返回空,就直接结束了,第二个迭代器的 "d"."e" 就不会返回了
	    */
            goto exit;c
        }
	// 将 val 设置在数组索引为 i 的位置,继续下一次循环,获取下一个迭代器中的元素设置在数组 stack 中
        stack[i] = val;
	//nargs 与迭代器的个数相同,假如迭代器对象为 3 个,小于 5,stack 会申请在栈区,因为长度为 5,所以后面两个元素是无效的
	// 所以在调用时,需要指定有效的参数个数
        nargs++;
    }
    // 开始调用获取结果,这个函数是 Python3.9 新增的,Python3.8 调用的是_PyObject_FastCall
    result = _PyObject_VectorcallTstate(tstate, lz->func, stack, nargs, NULL);
exit:
    for (Py_ssize_t i=0; i < nargs; i++) {
	// 将 stack 中指针指向的对象引用计数 - 1
        Py_DECREF(stack[i]);
    }
    if (stack != small_stack) {
	// 不相等,说明 stack 是在堆区申请的,需要释放
        PyMem_Free(stack);
    }
    return result; // 返回调用结果
}
static inline PyObject *
_PyObject_VectorcallTstate(PyThreadState *tstate, PyObject *callable,
                           PyObject *const *args, size_t nargsf,
                           PyObject *kwnames)
{
    vectorcallfunc func;
    PyObject *res;
    //kwnames 传递为 NULL,PyTuple_Check 表示如果 kwnames 是元组对象或者子类,则返回 True
    assert(kwnames == NULL || PyTuple_Check(kwnames));
    // 位置参数不为空,PyVectorcall_NARGS 会返回参数的实际数量
    assert(args != NULL || PyVectorcall_NARGS(nargsf) == 0);
    // 返回 callable 中 vectorcall 函数指针,如果不支持 vectorcall 协议(要么类型不支持,要么具体实例不支持),返回 NULL
    func = PyVectorcall_Function(callable);
    if (func == NULL) {
        Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); // 返回参数的实际数量
	// 如果 func 为 NULL, 可能类型不支持,那么就会尝试在 callable 中获取 tp_call 调用 callback, 因为它可能是其它对象
	// 但最后还是会调用_Py_CheckFunctionResult
        return _PyObject_MakeTpCall(tstate, callable, args, nargs, kwnames);
    }
    res = func(callable, args, nargsf, kwnames); // 函数调用
    return _Py_CheckFunctionResult(tstate, callable, res, NULL); // 返回结果
}

map 的内容还是比较少的,基本比较核心的都探究完了😴

Edited on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

小芳芳 WeChat Pay

WeChat Pay

小芳芳 Alipay

Alipay