午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

Python的容器(container)、可迭代對象(Iterables)、迭代器(Iterators)、生成器(Generators)理解...

 imnobody2001 2022-05-18 發(fā)布于黑龍江

背景介紹

在學(xué)習(xí)Python的初期,對容器,迭代器,生產(chǎn)器等概念一直沒有去梳理清楚,所以也就是一直糊里糊涂的用著Python。最近,用的多了,總是需要看一些源碼文件,想通過源碼文件來了解自己寫的代碼性能瓶頸會在哪里?此時,發(fā)現(xiàn)這些概念,我似乎需要仔細的梳理一下。梳理的過程中,會參閱一些參考文獻,畢竟站在前人的肩膀上會看的更遠嘛,在文末,會一并附上參看文獻。

contrainer 容器

出自官方文檔的一句定義:Some objects contain references to other objects; these are called containers.
字面意思十分明了:容器是包含其它數(shù)據(jù)類型的一種數(shù)據(jù)結(jié)構(gòu)或數(shù)據(jù)類型。
因此,我們可以認(rèn)為,容器一種特殊的自定義的數(shù)據(jù)結(jié)構(gòu)。

容器是一種把多個元素組織在一起的數(shù)據(jù)結(jié)構(gòu),容器中的元素可以逐個地迭代獲取,可以用in, not in關(guān)鍵字判斷元素是否包含在容器中。通常這類數(shù)據(jù)結(jié)構(gòu)把所有的元素存儲在內(nèi)存中(也有一些特例,并不是所有的元素都放在內(nèi)存,比如迭代器和生成器對象)那么在Python中,常見的容器有哪些?

list,
set,
dictionary,
OrderedDictionary
bytearray
array
string,
frozenset,
tuple,
bytes

以上常用的容器,各自都有自己的數(shù)據(jù)特點。這里本不應(yīng)該一一闡述,但多數(shù)使用者都應(yīng)該有所了解。
但是有一點似乎無法回避,那就是,哪些容器不能修改,哪些可以修改?傳址和傳值的對應(yīng)?哪些查找的復(fù)雜度高哪些更快?

可修改與否?

我們常用的容器,如:列表,字典是可以直接修改的,即從地址上修改存儲的內(nèi)容;

有可以修改,就有不可以修改,不然就沒有必要區(qū)別分了。不可修改的有元組tuple,類似的不可變數(shù)據(jù)類型包括整型int、浮點型float、字符串型string。

當(dāng)然不可變的容器和數(shù)據(jù)類型在我理解并不是真的不可變,如果你要修改,就是改變指針指向位置,將指針指向新的內(nèi)容位置,那么原始內(nèi)容是不變的,隨著指針的移動,就便成了廢棄的了,被程序清楚。

傳址與傳址的概念

傳值是指傳入一個參數(shù)的值,傳址是指傳入一個參數(shù)的地址,也就是內(nèi)存的地址(指針)。二者的區(qū)別是如果函數(shù)里面對傳入的參數(shù)重新賦值,函數(shù)外的全局變量是否相應(yīng)改變,用傳值傳入的參數(shù)不會改變的,用傳址傳入就會改變。

Python不允許程序員顯性的傳值還是傳址操作。Python參數(shù)傳遞采用的是“傳對象引用”的方式。這種方式也相當(dāng)于傳值和傳址的綜合形式。當(dāng)函數(shù)收到的是一個可變對象(比如字典或者列表)的引用(指針),就能修改對象的原始值——相當(dāng)于傳址。如果函數(shù)收到的是一個不可變對象(比如數(shù)字、字符或者元組)的引用,就不能直接修改原始對象——相當(dāng)于傳值。

所以python的傳值和傳址是根據(jù)傳入?yún)?shù)的類型來選擇的

傳值的參數(shù)類型:數(shù)字,字符串,元組

傳址的參數(shù)類型:列表,字典

所以在寫程序時候一定要注意這一點,如果不小心就容易出現(xiàn)bug

自定義容器

Python 提供了collections容器類,(待續(xù))

Iterables 可迭代對象

前面說的很多容器其實都是可迭代對象,此外還有更多的對象同樣也是可迭代對象,比如處于打開狀態(tài)的files等等。凡是可以返回一個迭代器的對象都可以稱之為可迭代對象,舉一個簡單的例子:

>>> x = [1, 2, 3, 4]
>>> y = iter(x)
>>> z = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(z)
1
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

這里x是一個可迭代對象,可迭代對象和容器一樣是一種通俗的叫法,它們具有包含關(guān)系,屬于概括的概念,并不是指某種具體的數(shù)據(jù)類型,list是可迭代對象,dict是可迭代對象,set也是可迭代對象。y和z是兩個獨立的迭代器,迭代器內(nèi)部持有一個狀態(tài),該狀態(tài)用于記錄當(dāng)前迭代所在的位置,以方便下次迭代的時候獲取正確的元素。迭代器有一種具體的迭代器類型,比如list_iterator,set_iterator??傻鷮ο髮崿F(xiàn)了__iter__方法,該方法返回一個迭代器對象。

當(dāng)運行以下代碼:

x = [1, 2, 3]
for elem in x:
     ...

背后真實的調(diào)用過程如下圖所示:

在這里插入圖片描述

Iterators 迭代器

迭代的意思就是重復(fù)的做一些事情,例如循環(huán)的形式。任何具有__next__()方法的對象都是迭代器,對迭代器調(diào)用next()方法可以獲取下一個值。next()方法不需要任何參數(shù),如果next()方法被調(diào)用時,迭代器沒有值可以返回,就會引發(fā)一個StopIteration的異常。

另外迭代器一般是__iter__() 方法返回。

所以迭代器本質(zhì)上是一個產(chǎn)生值的工廠,每次向迭代器請求下一個值,迭代器都會進行計算出相應(yīng)的值并返回。

迭代器的例子很多,例如,所有itertools模塊中的函數(shù)都會返回一個迭代器,有的還可以產(chǎn)生無窮的序列。

>>> from itertools import count
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14

從一個有限序列中生成無限序列:

>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

為了更直觀地感受迭代器內(nèi)部的執(zhí)行過程,我們定義一個迭代器,如斐波那契數(shù)列:

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1
 	#返回迭代器
    def __iter__(self):
        return self
 	#下一個元素
    def __next__(self):
        value = self.curr
        self.curr += self.prev
        self.prev = value
        return value
 
>>> f = Fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Fib既是一個可迭代對象(包含__iter__方法),又是一個迭代器(因為實現(xiàn)了__next__方法)。實例變量prev和curr用戶維護迭代器內(nèi)部的狀態(tài)。每次調(diào)用next()方法的時候做兩件事:

為下一次調(diào)用next()方法修改狀態(tài),為當(dāng)前這次調(diào)用生成返回結(jié)果。迭代器就像一個懶加載的工廠,等到有人需要的時候才給它生成值返回,沒調(diào)用的時候就處于休眠狀態(tài)等待下一次調(diào)用。

Generators 生成器

生成器算得上是Python語言中最吸引人的特性之一,生成器其實是一種特殊的迭代器,不過這種迭代器更加優(yōu)雅。
生成器不需要再像上面的類一樣寫__iter__()和__next__()方法了,只需要一個yield關(guān)鍵字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一種懶加載的模式生成值。用生成器來實現(xiàn)斐波那契數(shù)列的例子是:

def fib():
    prev, curr = 0, 1
    while True:
        yield curr
        prev, curr = curr, curr + prev

>>> f = fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

fib就是一個普通的python函數(shù),它特殊的地方在于函數(shù)體中沒有return關(guān)鍵字,函數(shù)的返回值是一個生成器對象。當(dāng)執(zhí)行f=fib()返回的是一個生成器對象,此時函數(shù)體中的代碼并不會執(zhí)行,只有顯示或隱示地調(diào)用next的時候才會真正執(zhí)行里面的代碼。

來剖析代碼:首先,fib是一個很普通的函數(shù),但是函數(shù)中沒有return語句,函數(shù)的返回值是一個生成器。

當(dāng)調(diào)用f = fib()時,生成器被實例化并返回,這時并不會執(zhí)行任何代碼,生成器處于空閑狀態(tài),注意這里prev, curr = 0, 1并未執(zhí)行。

然后這個生成器被包含在isslice()中,而這又是一個迭代器,所以還是沒有執(zhí)行上面的代碼。

然后這個迭代器又被包含在list()中,它會根據(jù)傳進來的參數(shù)生成一個列表。所以它首先對isslice()對象調(diào)用next()方法,isslice()對象又會對實例f調(diào)用next()。
我們來看其中的一步操作,在第一次調(diào)用中,會執(zhí)行prev, curr = 0, 1, 然后進入while循環(huán),當(dāng)遇到y(tǒng)ield curr的時候,返回當(dāng)前curr值,然后又進入空閑狀態(tài)。
生成的值傳遞給外層的isslice(),也相應(yīng)生成一個值,然后傳遞給外層的list(),外層的list將這個值1添加到列表中。
然后繼續(xù)執(zhí)行后面的九步操作,每步操作的流程都是一樣的。

然后執(zhí)行到底11步的時候,isslice()對象就會拋出StopIteration異常,意味著已經(jīng)到達末尾了。注意生成器不會接收到第11次next()請求,后面會被垃圾回收掉。

生成器在Python中是一個非常強大的編程結(jié)構(gòu),可以用更少地中間變量寫流式代碼,此外,相比其它容器對象它更能節(jié)省內(nèi)存和CPU,當(dāng)然它可以用更少的代碼來實現(xiàn)相似的功能。現(xiàn)在就可以動手重構(gòu)你的代碼了,但凡看到類似:

def something():
    result = []
    for ... in ...:
        result.append(x)
    return result

都可以用生成器函數(shù)來替換實現(xiàn),效果會更好:

def iter_something():
    for ... in ...:
        yield x

生成器的類型

在Python中生成器有兩種類型:生成器函數(shù)以及生成器表達式。生成器函數(shù)就是包含yield參數(shù)的函數(shù)。生成器表達式與列表解析式類似。
假設(shè)使用如下語法創(chuàng)建一個列表:

>>> numbers = [1, 2, 3, 4, 5, 6]
>>> [x * x for x in numbers]
[1, 4, 9, 16, 25, 36]

使用set解析式也可以達到同樣的目的:

>>> {x * x for x in numbers}{1, 4, 36, 9, 16, 25}

或者dict解析式:

>>> {x: x * x for x in numbers}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36}

還可以使用生成器表達式:

>>> lazy_squares = (x * x for x in numbers)
>>> lazy_squares
<generator object <genexpr> at 0x10d1f5510>
>>> next(lazy_squares)
1
>>> list(lazy_squares)
[4, 9, 16, 25, 36]

注意我們第一次調(diào)用next()之后,lazy_squares對象的狀態(tài)已經(jīng)發(fā)生改變,所以后面后面地調(diào)用list()方法只會返回部分元素組成的列表。

總結(jié)

生成器是Python中一種非常強大的特性,它讓我們能夠編寫更加簡潔的代碼,同時也更加節(jié)省內(nèi)存,使用CPU也更加高效。

容器,迭代對象,迭代器,生成器具有著一種必然的聯(lián)系,是一種進步和推廣。

容器是一系列元素的集合,str、list、set、dict、file、sockets對象都可以看作是容器,容器都可以被迭代(用在for,while等語句中),因此他們被稱為可迭代對象。

可迭代對象實現(xiàn)了__iter__方法,該方法返回一個迭代器對象。

迭代器持有一個內(nèi)部狀態(tài)的字段,用于記錄下次迭代返回值,它實現(xiàn)了__next__和__iter__方法,迭代器不會一次性把所有元素加載到內(nèi)存,而是需要的時候才生成返回結(jié)果。

生成器是一種特殊的迭代器,它的返回值不是通過return而是用yield。

學(xué)習(xí)一個東西,梳理好內(nèi)在聯(lián)系,深層次理解一個概念很重要。

參考文獻:

1.https:///posts/iterators-vs-generators/
2.https://docs./2/library/stdtypes.html#iterator-types
3.http://python./87805/
4.https:///iterators-vs-generators.html
5.https://blog.csdn.net/github_39261590/article/details/74002290

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多