《深度剖析CPython解释器》30. 源码解密内置函数 iter、next

楔子

这次我们来看看 iter 和 next 这两个内置函数的用法,我们知道 iter 是将一个可迭代对象变成一个迭代器,next 是将迭代器里的值一步一步迭代出来。

lst = [1, 2, 3]
it = iter(lst)
print(it)  # <list_iterator object at 0x000001DC6E898640>

# 调用next, 可以对迭代器进行迭代
print(next(it))  # 1

注意:iter 还有一个鲜为人知的用法,我们来看一下:

val = 0

def foo():
    global val
    val += 1
    return val


# iter可以接收一个参数: iter(可迭代对象)
# iter可以接收两个参数: iter(可调用对象, value)
for i in iter(foo, 5):
    print(i)
"""
1
2
3
4
"""

# 进行迭代的时候, 会不停的调用内部接收的 可调用对象
# 直到返回值等于传递第二个参数 value(在底层被称为哨兵), 然后终止迭代

当然 next 函数也有一个特殊用法,就是它在接收一个迭代器的时候,还可以指定一个默认值;如果元素迭代完毕之后再次迭代的话,不会抛出StopIteration,而是会返回默认值。

it = iter([1, 2, 3])
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
print(next(it, "xxx"))  # xxx
print(next(it, "yyy"))  # yyy

注意:iter 内部接收可迭代对象的类型不同,那么得到的迭代器种类也不同。

print(iter("xyz"))  # <str_iterator object at 0x00000234493B8640>
print(iter((1, 2, 3)))  # <tuple_iterator object at 0x00000234493B8640>
print(iter([1, 2, 3]))  # <list_iterator object at 0x00000234493B8640>

iter 函数底层实现

我们看一下 iter 函数底层是如何实现的,其实之前已经见识过了,还想的起来吗?

static PyObject *
builtin_iter(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
    PyObject *v;
	
    // iter函数要么接收一个参数, 要么接收两个参数
    if (!_PyArg_CheckPositional("iter", nargs, 1, 2))
        return NULL;
    v = args[0];
    // 如果接收一个参数, 那么直接使用 PyObject_GetIter 获取对应的迭代器即可
    // 在这个函数中, 可迭代对象的类型不同, 那么得到的迭代器也不同
    if (nargs == 1)
        return PyObject_GetIter(v);
    // 如果接收的不是一个参数, 那么一定是两个参数
    // 如果是两个参数, 那么第一个参数一定是可调用对象
    if (!PyCallable_Check(v)) {
        PyErr_SetString(PyExc_TypeError,
                        "iter(v, w): v must be callable");
        return NULL;
    }
    // 获取value(哨兵)
    PyObject *sentinel = args[1];
    // 调用PyCallIter_New, 得到一个可调用的迭代器, calliterobject 对象
    /*
    位于 Objects/iterobject.c 中
    typedef struct {
        PyObject_HEAD
        PyObject *it_callable; 
        PyObject *it_sentinel; 
	} calliterobject;
    */
    return PyCallIter_New(v, sentinel);
}

所以核心就在于 PyObject_GetIter 中,它是根据可迭代对象生成迭代器的关键,那么它都做了哪些事情呢?不用想肯定是执行:obj.__iter__(),当然更准确的说应该是:type(obj).__iter__(obj),我们来看一下。该函数定义在 Objects/abstract.c 中:

PyObject *
PyObject_GetIter(PyObject *o)
{	
    // 获取该可迭代对象的类型对象
    PyTypeObject *t = Py_TYPE(o);
    // 我们说类型对象的方法和属性 决定了 实例对象的行为, 实例对象调用的那些方法都是属于类型对象的
    // 还是那句话 obj.func()  等价于  type(obj).func(obj)
    getiterfunc f;
    // 所以这里是获取类型对象的 tp_iter成员, 也就是Python中的 __iter__
    f = t->tp_iter;
    // 如果 f 为 NULL, 说明该类型对象内部的 tp_iter 成员被初始化为NULL, 即内部没有定义 __iter__ 方法
    // 像str、tuple、list等类型对象, 它们的 tp_iter 成员都是不为NULL的
    if (f == NULL) {
        // 如果 tp_iter 为 NULL, 那么解释器会退而求其次, 检测该类型对象中是否定义了 __getitem__, 我们后面会介绍
        // 如果定义了, 那么直接调用PySeqIter_New, 得到一个 seqiterobject 对象, 我们在上一篇博客介绍过的
        // PySequence_Check 里面的逻辑就是检测类型对象是否初始化了 tp_as_sequence->sq_item 成员, 它对应 __getitem__
        if (PySequence_Check(o))
            return PySeqIter_New(o);
        // 走到这里说明该类型对象的实例对象不具备可迭代的性质, 抛出异常
        return type_error("'%.200s' object is not iterable", o);
    }
    else {
        // 否则的话, 直接进行调用, Py_TYPE(o)->tp_iter(o)
        PyObject *res = (*f)(o);
        // 如果返回值 res 不为NULL, 并且它还不是一个迭代器, 抛出异常
        if (res != NULL && !PyIter_Check(res)) {
            PyErr_Format(PyExc_TypeError,
                         "iter() returned non-iterator "
                         "of type '%.100s'",
                         Py_TYPE(res)->tp_name);
            Py_DECREF(res);
            res = NULL;
        }
        // 返回 res
        return res;
    }
}

所以我们看到这便是 iter 函数的底层实现,里面我们提到了 __getitem__。我们说如果类型对象内部没有定义 __iter__,那么解释器会退而求其次检测内部是否定义了 __getitem__。

class A:

    def __getitem__(self, item):
        return f"参数item: {item}"


a = A()
# 内部定义了 __getitem__, 首先可以让实例对象像字典一样
print(a["name"])  # 参数item: name
print(a["夏色祭"])  # 参数item: 夏色祭

# 此外还可以像迭代器一个被for循环
for idx, val in enumerate(a):
    print(val)
    if idx == 5:
        break
"""
参数item: 0
参数item: 1
参数item: 2
参数item: 3
参数item: 4
参数item: 5
"""

# 我们看到循环的时候会自动给item传值, 这个值是0 1 2 3...., 因此这个循环是无限的

class Girl:
    def __init__(self):
        self.names = ["夏色祭", "神乐七奈", "夜空梅露", "雫_るる"]

    def __getitem__(self, item):
        try:
            val = self.names[item]
            return val
        except IndexError:
            raise StopIteration  # 让for循环结束

g = Girl()
for _ in g:
    print(_)
"""
夏色祭
神乐七奈
夜空梅露
雫_るる
"""
# 当出现 StopIteration 异常的时候, 再次循环时传递的item会被重置为0
print(list(g))  # ['夏色祭', '神乐七奈', '夜空梅露', '雫_るる']

lst = []
lst.extend(g)
print(lst)  # ['夏色祭', '神乐七奈', '夜空梅露', '雫_るる']

以上被称为解释器的退化功能,就是在找不到某个实现的时候,会进行退化、尝试寻找其它实现。类似的做法还有其它,比如:

class A:

    def __len__(self):
        return 0

# 当进行布尔判断的时候, 会尝试获取内部 __bool__ 方法的返回值
# 如果没有定义 __bool__, 那么解释器会退化尝试寻找 __len__ 方法
print(bool(A()))  # False

如果内部定义了 __iter__,则直接调用即可。

# 如果type(obj)内部定义了__iter__, 那么iter(obj)  <==>  type(obj).__iter__(obj)
print(str.__iter__("123"))  # <str_iterator object at 0x00000213CC2A8640>
print(list.__iter__([1, 2, 3]))  # <list_iterator object at 0x00000213CC2A8640>
print(tuple.__iter__((1, 2, 3)))  # <tuple_iterator object at 0x00000213CC2A8640>
print(set.__iter__({1, 2, 3}))  # <set_iterator object at 0x00000213CC478E80>

next函数底层实现

了解 iter 之后,我们再来看看 next 函数;如果内部定义了 __next__ 函数,那么不用想,结果肯定是type(obj).__next__(obj),我们看一下底层实现。

static PyObject *
builtin_next(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
    PyObject *it, *res;
	
    // 同样接收一个参数或者两个参数
    if (!_PyArg_CheckPositional("next", nargs, 1, 2))
        return NULL;

    it = args[0];
    // 第一个参数必须是一个迭代器, 也就是类型对象的tp_iternext成员不能为NULL
    if (!PyIter_Check(it)) {
        // 否则的话, 抛出TypeError, 表示第一个参数传递的不是一个迭代器
        PyErr_Format(PyExc_TypeError,
            "'%.200s' object is not an iterator",
            it->ob_type->tp_name);
        return NULL;
    }
	
    /*
    列表对应的迭代器是: listiterobject, 其类型为: PyListIter_Type
    元组对应的迭代器是: tupleiterobject, 其类型为: PyTupleIter_Type
    字符串对应的迭代器是: unicodeiterobject, 其类型为: PyUnicodeIter_Type
    */
    // 通过 ob_type 获取对应的类型对象, 然后获取成员 tp_iternext 的值, 相当于__next__
    // 再传入迭代器进行迭代
    res = (*it->ob_type->tp_iternext)(it);
    
    // 如果 res 不为 NULL, 那么证明迭代到值了, 直接返回
    if (res != NULL) {
        return res;
    } else if (nargs > 1) {
        // 否则的话, 看nargs是否大于1, 如果大于1, 说明设置了默认值
        PyObject *def = args[1];
        // 出现异常的话, 将异常清空
        if (PyErr_Occurred()) {
            if(!PyErr_ExceptionMatches(PyExc_StopIteration))
                return NULL;
            PyErr_Clear();
        }
        // 增加引用计数, 并返回
        Py_INCREF(def);
        return def;
    } else if (PyErr_Occurred()) {
        return NULL;
    } else {
        PyErr_SetNone(PyExc_StopIteration);
        return NULL;
    }
}

还是比较简单的,我们以 列表 对应的迭代器为例,举个栗子:

lst = [1, 2, 3]
it = iter(lst)
# 对应的类型是list_iterator, 但在底层我们知道类型是PyListIter_Type
print(type(it))  # <class 'list_iterator'>

# 然后迭代
print(type(it).__next__(it))  # 1
print(type(it).__next__(it))  # 2
print(type(it).__next__(it))  # 3

# 以上就等价于 next(it), 但是我们知道内置函数 next 要更强大一些, 因为它还可以做一些其它处理
# 当然默认情况下, 和 type(it).__next__(it) 最终是殊途同归的

怎么样,是不是很简单呢?

我们看到一个变量 obj 不管指向什么可迭代对象,都可以交给 iter,得到对应的迭代器;不管什么迭代器,都可以交给 next 进行迭代。原因就在于它们接收的不是对象本身,而是对象对应的 PyObject * 泛型指针。不管你是谁的指针,只要你指向的对象是一个可迭代对象,那么都可以交给 iter。至于 next 也是同理,不管你指向的是哪一种迭代器,只要是迭代器,就可以交给 next,然后会自动调用迭代器内部的 __next__(底层是 tp_iternext)将值给迭代出来。所以这是不是相当于实现了多态呢?所以这就是 Python 的设计哲学,变量只是一个指针,传递变量的时候相当于传递指针(将指针拷贝一份),但是操作一个变量的时候会自动操作变量(指针)指向的内存。比如:a = 12345; b = a,相当于把 a 拷贝了一份给 b,但 a 是一个指针,所以此时 a 和 b 保存的地址是相同的,也就是指向了同一个对象。但 a + b 的时候则不是两个指针相加、而是将 a、b 指向的对象进行相加,也就是操作变量会操作变量指向的内存。因此在 Python 中,说传递方式是值传递或者引用传递都是不准确的,应该是变量之间的赋值传递,对象之间的引用传递。

posted @ 2020-11-26 00:39  古明地盆  阅读(1366)  评论(2编辑  收藏  举报