《深度剖析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 中,說傳遞方式是值傳遞或者引用傳遞都是不准確的,應該是變量之間的賦值傳遞,對象之間的引用傳遞。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM