欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你解密Python迭代器的實現(xiàn)原理

 更新時間:2022年12月13日 15:05:31   作者:古明地覺  
這篇文章主要為大家詳細介紹了Python中迭代器的實現(xiàn)原理,文中的示例代碼講解詳細,對我們學(xué)習(xí)Python有一定的幫助,需要的可以參考一下

可迭代對象與迭代器

Python 一切皆對象,類型對象定義了哪些操作,決定了實例對象擁有哪些行為。

比如類型對象如果定義了 __iter__,那么其實例對象便被稱為可迭代對象(iterable),像字符串、元組、列表、字典、集合等等都是可迭代對象。而整數(shù)、浮點數(shù),由于其類型對象沒有定義 __iter__,所以它們不是可迭代對象。

from?typing?import?Iterable

print(
????isinstance("",?Iterable),
????isinstance((),?Iterable),
????isinstance([],?Iterable),
????isinstance({},?Iterable),
????isinstance(set(),?Iterable),
)??#?True?True?True?True?True

print(
????isinstance(0,?Iterable),
????isinstance(0.0,?Iterable),
)??#?False?False

可迭代對象的一大特點就是它可以使用 for 循環(huán)進行遍歷,但是能被 for 循環(huán)遍歷的則不一定是可迭代對象。我們舉個例子:

class?A:

????def?__getitem__(self,?item):
????????return?f"參數(shù)item:?{item}"

a?=?A()
# 內(nèi)部定義了?__getitem__
# 首先可以讓實例對象像字典一樣訪問屬性
print(a["name"])??#?參數(shù)item:?name
print(a["satori"])??#?參數(shù)item:?satori

#?此外還可以像可迭代對象一樣被 for 循環(huán)
#?循環(huán)的時候會自動給 item 傳值,0?1?2?3...
#?如果內(nèi)部出現(xiàn)了 StopIteration,循環(huán)結(jié)束
#?否則會一直循環(huán)下去。這里我們手動 break
for?idx,?val?in?enumerate(a):
????print(val)
????if?idx?==?5:
????????break
"""
參數(shù)item:?0
參數(shù)item:?1
參數(shù)item:?2
參數(shù)item:?3
參數(shù)item:?4
參數(shù)item:?5
"""

所以實現(xiàn)了__getitem__的類的實例,也是可以被for循環(huán)的,但它并不是可迭代對象。

from?typing?import?Iterable
print(isinstance(a,?Iterable))??#?False

總之判斷一個對象是否是可迭代對象,就看它的類型對象有沒有實現(xiàn) __iter__。

可迭代對象我們知道了,那什么是迭代器呢?很簡單,調(diào)用可迭代對象的 __iter__ 方法,得到的就是迭代器。

迭代器的創(chuàng)建

不同類型的對象,都有自己的迭代器,舉個例子。

lst?=?[1,?2,?3]
# 底層調(diào)用的其實是 list.__iter__(lst)
# 從 C 的角度上看,就是 PyList_Type.tp_iter(lst)
it?=?lst.__iter__()
print(it)??#?<list_iterator?object?at?0x000001DC6E898640>
print(
????str.__iter__("")
)??#?<str_iterator?object?at?0x000001DC911B8070>
print(
????tuple.__iter__(())
)??#?<tuple_iterator?object?at?0x000001DC911B8070>

迭代器也是可迭代對象,只不過迭代器內(nèi)部的 __iter__ 返回的還是它本身。當(dāng)然啦,在創(chuàng)建迭代器的時候,我們更常用內(nèi)置函數(shù) iter。

lst?=?[1,?2,?3]
#?等價于?type(lst).__iter__(lst)
it?=?iter(lst)

但是 iter 函數(shù)還有一個鮮為人知的用法,我們來看一下:

val?=?0

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

#?iter 可以接收一個參數(shù):?iter(可迭代對象)
#?iter 也可以接收兩個參數(shù):?iter(可調(diào)用對象,?value)
for?i?in?iter(foo,?5):
????print(i)
"""
1
2
3
4
"""

如果接收的是兩個參數(shù),那么第一個參數(shù)一定是 callable。進行迭代的時候,會不停地調(diào)用接收的可調(diào)用對象,每次迭代出來的值便是 callable 的返回值。當(dāng)返回值等于傳遞第二個參數(shù) value(在底層被稱為哨兵)時,終止迭代。我們看一下 iter 函數(shù)的底層實現(xiàn)。

static?PyObject?*
builtin_iter(PyObject?*self,?
????PyObject?*const?*args,?Py_ssize_t?nargs)
{
????PyObject?*v;
??
????//?iter?函數(shù)要么接收一個參數(shù),?要么接收兩個參數(shù)
????if?(!_PyArg_CheckPositional("iter",?nargs,?1,?2))
????????return?NULL;
????v?=?args[0];
????//?如果接收一個參數(shù)
????//?那么直接使用?PyObject_GetIter?獲取對應(yīng)的迭代器即可
????//?可迭代對象的類型不同,那么得到的迭代器也不同
????if?(nargs?==?1)
????????return?PyObject_GetIter(v);
????//?如果接收的不是一個參數(shù),?那么一定是兩個參數(shù)
????//?如果是兩個參數(shù),?那么第一個參數(shù)一定是可調(diào)用對象
????if?(!PyCallable_Check(v))?{
????????PyErr_SetString(PyExc_TypeError,
????????????????????????"iter(v,?w):?v?must?be?callable");
????????return?NULL;
????}
????//?獲取value(哨兵)
????PyObject?*sentinel?=?args[1];
????//調(diào)用PyCallIter_New
????//得到?calliterobject?對象
????/*
????該對象位于?Objects/iterobject.c?中
????*/
????return?PyCallIter_New(v,?sentinel);
}

以上就是 iter 函數(shù)的內(nèi)部邏輯,既可以接收一個參數(shù),也可以接收兩個參數(shù)。這里我們只看接收一個可迭代對象的情況,所以核心就在于 PyObject_GetIter,它是根據(jù)可迭代對象生成迭代器的關(guān)鍵,我們來看一下它的邏輯是怎么樣的?該函數(shù)定義在 Objects/abstract.c 中。

PyObject?*
PyObject_GetIter(PyObject?*o)
{??
????//?獲取可迭代對象的類型對象
????PyTypeObject?*t?=?Py_TYPE(o);
????//?我們說類型對象定義的操作,決定了實例對象的行為
????//?實例對象調(diào)用的那些方法都是定義在類型對象里面的
????//?所以obj.func()本質(zhì)上就是type(obj).func(obj)的語法糖
????getiterfunc?f;
????//?所以這里是獲取類型對象的?tp_iter?成員
????//?也就是?Python?中的?__iter__
????f?=?t->tp_iter;
????//?如果?f?為?NULL
????//?說明該類型對象內(nèi)部的tp_iter成員被初始化為NULL
????//?即內(nèi)部沒有定義?__iter__?
????//?像str、tuple、list等類型對象,它們的tp_iter成員都是不為NULL的
????if?(f?==?NULL)?{
???????//?如果?tp_iter?為?NULL,那么解釋器會退而求其次
???????//?檢測該類型對象中是否定義了?__getitem__
???????//?如果定義了,那么直接調(diào)用PySeqIter_New
???????//?得到一個seqiterobject對象
???????//?下面的PySequence_Check負(fù)責(zé)檢測類型對象是否實現(xiàn)了__getitem__
????????if?(PySequence_Check(o))
????????????return?PySeqIter_New(o);
????????//?走到這里說明該類型對象既沒有__iter__、也沒有__getitem__
????????//?因此它的實例對象不具備可迭代的性質(zhì),于是拋出異常
????????return?type_error("'%.200s'?object?is?not?iterable",?o);
????}
????else?{
????????//?否則說明定義了__iter__,于是直接進行調(diào)用
????????//?Py_TYPE(o)->tp_iter(o)?返回對應(yīng)的迭代器
????????PyObject?*res?=?(*f)(o);
????????//?但如果返回值res不為NULL、并且還不是迭代器
????????//?證明?__iter__?的返回值有問題,于是拋出異常
????????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 函數(shù)的底層實現(xiàn),并且當(dāng)類型對象內(nèi)部沒有定義 __iter__ 時,解釋器會退而求其次檢測內(nèi)部是否定義了 __getitem__。

因此以上就是迭代器的創(chuàng)建過程,每個可迭代對象都有自己的迭代器,而迭代器本質(zhì)上只是對原始數(shù)據(jù)的一層封裝罷了。

迭代器的底層結(jié)構(gòu)

由于迭代器的種類非常多,字符串、元組、列表等等,都有自己的迭代器,這里就不一一介紹了。我們就以列表的迭代器為例,看看迭代器在底層的結(jié)構(gòu)是怎么樣的。

typedef?struct?{
????PyObject_HEAD
????Py_ssize_t?it_index;
????//指向創(chuàng)建該迭代器的列表
????PyListObject?*it_seq;
}?listiterobject;

顯然對于列表而言,迭代器就是在其之上進行了一層簡單的封裝,所謂元素迭代本質(zhì)上還是基于索引,并且我們每迭代一次,索引就自增 1。一旦出現(xiàn)索引越界,就將 it_seq 設(shè)置為 NULL,表示迭代器迭代完畢。

我們實際演示一下:

from?ctypes?import?*

class?PyObject(Structure):
????_fields_?=?[
????????("ob_refcnt",?c_ssize_t),
????????("ob_size",?c_void_p)
????]

class?ListIterObject(PyObject):
????_fields_?=?[
????????("it_index",?c_ssize_t),
????????("it_seq",?POINTER(PyObject))
????]

it?=?iter([1,?2,?3])
it_obj?=?ListIterObject.from_address(id(it))

#?初始的時候,索引為0
print(it_obj.it_index)??#?0
#?進行迭代
next(it)
#?索引自增1,此時it_index等于1
print(it_obj.it_index)??#?1
#?再次迭代
next(it)
#?此時it_index等于2
print(it_obj.it_index)??#?2
#?再次迭代
next(it)
#?此時it_index等于3
print(it_obj.it_index)??#?3

當(dāng) it_index 為 3 的時候,如果再次迭代,那么底層發(fā)現(xiàn) it_index 已超過最大索引,就知道迭代器已經(jīng)迭代完畢了。然后會將 it_seq 設(shè)置為 NULL,并拋出 StopIteration。如果是 for 循環(huán),那么會自動捕獲此異常,然后停止循環(huán)。

所以這就是迭代器,真的沒有想象中的那么神秘,甚至在知道它的實現(xiàn)原理之后,還覺得有點 low。

就是將原始的數(shù)據(jù)包了一層,加了一個索引而已。所謂的迭代仍然是基于索引來做的,并且每迭代一次,索引自增 1。當(dāng)索引超出范圍時,證明迭代完畢了,于是將 it_seq 設(shè)置為 NULL,拋出 StopIteration。

元素迭代的具體過程

我們知道在迭代元素的時候,可以通過 next 內(nèi)置函數(shù),當(dāng)然它本質(zhì)上也是調(diào)用了對象的 __next__ 方法。

static?PyObject?*
builtin_next(PyObject?*self,?
????PyObject?*const?*args,?Py_ssize_t?nargs)
{
????PyObject?*it,?*res;
??
????//?同樣接收一個參數(shù)或者兩個參數(shù)
????//?因為調(diào)用next函數(shù)時,可以傳入一個默認(rèn)值
????//?當(dāng)?shù)鳑]有元素可以迭代的時候,會返回指定的默認(rèn)值
????if?(!_PyArg_CheckPositional("next",?nargs,?1,?2))
????????return?NULL;

????it?=?args[0];
????//?第一個參數(shù)必須是一個迭代器
????if?(!PyIter_Check(it))?{
????????//?否則的話,?拋出TypeError
????????//?表示第一個參數(shù)傳遞的不是一個迭代器
????????PyErr_Format(PyExc_TypeError,
????????????"'%.200s'?object?is?not?an?iterator",
????????????it->ob_type->tp_name);
????????return?NULL;
????}?
????//?it->ob_type?表示獲取類型對象,也就是該迭代器的類型
????//?可能是列表的迭代器、元組的迭代器、字符串的迭代器等等
????//?具體是哪一種不重要,因為實現(xiàn)了多態(tài)
????//?然后再獲取?tp_iternext?成員,相當(dāng)于__next__
????//?拿到函數(shù)指針之后,傳入迭代器進行調(diào)用
????res?=?(*it->ob_type->tp_iternext)(it);
????
????//?如果?res?不為?NULL,?那么證明迭代到值了,?直接返回
????if?(res?!=?NULL)?{
????????return?res;
????}?else?if?(nargs?>?1)?{
????????//?否則的話,說明?res?==?NULL,
????????//?這意味著迭代完畢了,或者程序出錯了
????????//?然后看?nargs?是否大于1,?如果大于1,?說明設(shè)置了默認(rèn)值
????????PyObject?*def?=?args[1];
????????//?如果出現(xiàn)異常
????????if?(PyErr_Occurred())?{
???????????//?那么就看該異常的種類
???????????//?能否匹配?StopIteration
????????????if(!PyErr_ExceptionMatches(PyExc_StopIteration))
????????????//?如果不是,說明程序的邏輯有問題
????????????//?于是直接return?NULL,結(jié)束執(zhí)行
????????????//?然后在?Python?里面我們會看到打印到stderr中的異常信息
????????????????return?NULL;
????????????//?如果是?StopIteration,證明迭代完畢了
????????????//?但我們設(shè)置了默認(rèn)值,那么就應(yīng)該返回默認(rèn)值
????????????//?而不應(yīng)該拋出?StopIteration,于是將異?;厮輻=o清空
????????????PyErr_Clear();
????????}
????????//?然后增加默認(rèn)值的引用計數(shù),?并返回
????????Py_INCREF(def);
????????return?def;
????}?else?if?(PyErr_Occurred())?{
????????//走到這里說明程序出異常了,并且沒有指定默認(rèn)值
????????//那么這種情況,不管什么異常都直接拋出
????????return?NULL;
????}?else?{
????????//?都不是的話,仍是直接拋出?StopIteration
????????PyErr_SetNone(PyExc_StopIteration);
????????return?NULL;
????}
}

以上就是next函數(shù)的背后邏輯,實際上還是調(diào)用了迭代器的__next__方法。

lst?=?[1,?2,?3]
it?=?iter(lst)
#?然后迭代,等價于next(it)
print(type(it).__next__(it))??#?1
print(type(it).__next__(it))??#?2
print(type(it).__next__(it))??#?3
#?但是next可以指定默認(rèn)值
#?如果不指定默認(rèn)值,或者還是type(it).__next__(it)
#?那么就會報錯,會拋出StopIteration
print(next(it,?666))??#?666

以上就是元素的迭代,但是我們知道內(nèi)置函數(shù)next要更強大一些,因為它還可以指定一個默認(rèn)值。當(dāng)然在不指定默認(rèn)值的情況下,next(it) 和 type(it).__next__(it) 是等價的。

我們?nèi)砸粤斜淼牡鳛槔?,看?__next__ 的具體實現(xiàn)。

static?PyObject?*
listiter_next(listiterobject?*it)
{
????PyListObject?*seq;??//列表
????PyObject?*item;?????//元素
????
????assert(it?!=?NULL);
????//拿到具體對應(yīng)的列表
????seq?=?it->it_seq;
????//如果seq為NULL,證明迭代器已經(jīng)迭代完畢
????//否則它不會為NULL
????if?(seq?==?NULL)
????????return?NULL;
????assert(PyList_Check(seq));
????//如果索引小于列表的長度,證明尚未迭代完畢
????if?(it->it_index?<?PyList_GET_SIZE(seq))?{
?????  ?//通過索引獲取指定元素
????????item?=?PyList_GET_ITEM(seq,?it->it_index);
??????  //it_index自增1
????????++it->it_index;
??????  //增加引用計數(shù)后返回
????????Py_INCREF(item);
????????return?item;
????}
????//否則的話,說明此次索引正好已經(jīng)超出最大范圍
????//意味著迭代完畢了,將it_seq設(shè)置為NULL
????//并減少它的引用計數(shù),然后返回
????it->it_seq?=?NULL;
????Py_DECREF(seq);
????return?NULL;
}

顯然這和我們之前分析的是一樣的,以上我們就以列表為例,考察了迭代器的實現(xiàn)原理和元素迭代的具體過程。當(dāng)然其它對象也有自己的迭代器,有興趣可以自己看一看。

小結(jié)

到此,我們再次體會到了 Python 的設(shè)計哲學(xué),通過 PyObject * 和 ob_type 實現(xiàn)了多態(tài)。原因就在于它們接收的不是對象本身,而是對象的 PyObject * 泛型指針。

不管變量 obj 指向什么樣的可迭代對象,都可以交給 iter 函數(shù),會調(diào)用類型對象內(nèi)部的 __iter__,底層是 tp_iter,得到對應(yīng)的迭代器。不管變量 it 指向什么樣的迭代器,都可以交給 next 函數(shù)進行迭代,會調(diào)用迭代器的類型對象的 __next__,底層是 tp_iternext,將值迭代出來。

至于__iter__和__next__本身,每個迭代器都會有,我們這里只以列表的迭代器為例。

到此這篇關(guān)于一文帶你解密Python迭代器的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Python迭代器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論