• 《深度剖析CPython解释器》31. 源码解密内置函数 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) 最终是殊途同归的
    

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

  • 相关阅读:
    cssReset
    CSS的一些小技巧
    前端图标神器
    单例模式
    CSS 控制Html页面高度导致抖动问题的原因
    PHP中include()与require()的区别说明
    extends和implements区别
    静态,抽象类、接口、类库
    jQuery轮播图(手动点击轮播)
    jQuery实现大图轮播
  • 原文地址:https://www.cnblogs.com/traditional/p/14040093.html
Copyright © 2020-2023  润新知