中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

在Python中按需處理數(shù)據(jù),第1部分: Python 迭代器和生成器

2018-08-07    來源:raincent

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

幸運的是,在當(dāng)今時代,各種市場因素已將內(nèi)存、磁盤甚至 CPU 容量的價格統(tǒng)統(tǒng)壓縮至原先難以想象的低價。但與此同時,諸如大數(shù)據(jù)、AI 和認(rèn)知計算這類蓬勃發(fā)展的應(yīng)用,正在以超快的速度推升我們對于這些資源的需求。令人感覺有點啼笑皆非的是,當(dāng)計算資源較充足時,開發(fā)人員了解如何降低資源耗用量來保持競爭力卻變得愈加重要。

近二十年來,Python 始終是一種熱門編程語言,主要原因就在于它非常易于學(xué)習(xí)。短短一小時,您就能學(xué)會如何輕松操作列表和字典。但令人生厭的是,這種利用列表和字典來解決眾多問題的方法不免有些幼稚,它很快就會為您帶來麻煩,迫使您不得不設(shè)法擴展應(yīng)用規(guī)模,因為稍有不慎,Python 就會變得比其他編程語言更加耗用資源。

好消息是 Python 有一些非常實用的功能,有助于提升處理效率。其中許多功能都基于 Python 的迭代器協(xié)議,這也是本教程的重要主題。在此基礎(chǔ)上將構(gòu)建一整套教程,總共包含四個部分,主要向您展示如何利用 Python 來高效處理大型數(shù)據(jù)集。

您應(yīng)熟悉 Python 基礎(chǔ)知識,如條件、循環(huán)、函數(shù)、異常、列表和字典。此系列教程是圍繞 Python 3 開展的;要運行代碼,就需要使用 Python 3.5 或更高版本。
 

迭代器


您最初接觸到的 Python 循環(huán)可能類似以下代碼:

for ix in range(10): 
print(ix)

Python 的 for 語句在所謂的迭代器上運行。迭代器是可重復(fù)調(diào)用以生成一系列值的對象。如果 in 關(guān)鍵字后的值尚未成為迭代器,那么 for 會嘗試將其轉(zhuǎn)換為迭代器。內(nèi)置 range 函數(shù)就是可以轉(zhuǎn)換為迭代器的一個示例。它可生成一系列數(shù)字,for 循環(huán)會對這些項進行迭代,依次將每個項分配到變量 ix。

現(xiàn)在,可通過更仔細地查看像 range 這樣的迭代器來深入了解 Python。在 Python 解釋器中輸入以下內(nèi)容:

r = range(10)

您現(xiàn)在已完成了 range 迭代器的初始化,但僅此而已。接下來,要求此迭代器提供其首個值。在 Python 中使用內(nèi)置 next 函數(shù)來要求迭代器提供一個值。

>>> r = range(10)
>>> print(next(r))
Traceback (most recent call last): 
File "", line 1, in 
TypeError: 'range' object is not an iterator

此異常表明您必須要先將對象轉(zhuǎn)換為迭代器,然后才能將其用作迭代器?梢允褂脙(nèi)置 iter 函數(shù)來執(zhí)行此操作。

r = iter(range(10))
print(next(r))

此時會打印 0,這與您的預(yù)期相符。接下來再次輸入 print(next(r)),將會打印 1,以此類推。繼續(xù)在此行中輸入。在這一點上,值得高興的是,對于大部分系統(tǒng),只需按 Python 解釋器上的向上箭頭即可檢索最近使用的命令,然后按 Enter 鍵即可重復(fù)執(zhí)行命令,您甚至還可以在按 Enter 鍵之前隨意對其進行調(diào)整。

在此情況下,最終將得到類似如下的結(jié)果:

>>> print(next(r))
9
>>> print(next(r))
Traceback (most recent call last): 
File "", line 1, in 
StopIteration

我們要求的范圍僅為 10 個整數(shù),因此在它生成 9 之后即告結(jié)束。此迭代器不會立即執(zhí)行任何操作來指示它已結(jié)束,但任何后續(xù)的 next() 調(diào)用都將生成 StopIteration 異常。就像處理其他任何異常一樣,您可以選擇自行編寫代碼對其進行處理。在迭代器 r 用完后,嘗試輸入以下代碼。

try: 
print(next(r))
except StopIteration as e: 
print("That's all folks!")

它會打印消息“That's all folks!”for 語句使用 StopIteration 異常來確定何時退出循環(huán)。
 

其他可迭代對象


range 只是可轉(zhuǎn)換為迭代器的一種對象。以下解釋器會話演示了如何將各種標(biāo)準(zhǔn)類型解釋為迭代器。

>>> it = iter([1,2,3])
>>> print(next(it))
1
>>> it = iter((1,2,3))
>>> print(next(it))
1
>>> it = iter({1: 'a', 2: 'b', 3: 'c'})
>>> print(next(it))
1
>>> it = iter({'a': 1, 'b': 2, 'c': 3})
>>> print(next(it))
a
>>> it = iter(set((1,2,3)))
>>> print(next(it))
1
>>> it = iter('xyz')
>>> print(next(it))
x

對于列表或元組,這是非常直接的過程。字典僅對其鍵進行迭代,當(dāng)然無法保證順序。對于集合也無法保證迭代順序,即使在此示例中,來自迭代器的首個項碰巧也是用于構(gòu)造集合的元組中的首個項。字符串會對其字符進行迭代。所有此類對象都稱為可迭代對象

正如您可能想到的,并非每個 Python 對象都可轉(zhuǎn)換為迭代器。

>>> it = iter(1)
Traceback (most recent call last): 
File "", line 1, in 
TypeError: 'int' object is not iterable
>>> it = iter(None)
Traceback (most recent call last): 
File "", line 1, in 
TypeError: 'NoneType' object is not iterable

當(dāng)然,最棒的是您可以生成自己的迭代器類型。您只需使用某些明確指定的方法來定義一個類即可。這超出了本系列教程的范圍,但沒關(guān)系,因為要自行創(chuàng)建自定義迭代器,最直接的方法并不是采用一個特殊類,而是采用稱為生成器函數(shù)的特殊函數(shù)。接下來就討論生成器函數(shù)。
 

生成器


您已熟悉函數(shù)的概念,函數(shù)需要一些自變量,并返回值或 None?赡艽嬖诙鄠出口點、多個返回語句或者只有函數(shù)的最后一個縮進行,這與 return None 相同,但每次運行此函數(shù)時,只會基于此函數(shù)中的條件選中其中一個出口點。

生成器函數(shù)是特殊類型的函數(shù),交互方式更為復(fù)雜,但對于調(diào)用此類函數(shù)的代碼而言則更為實用。以下是可供您粘貼到自己的解釋器會話中的簡單示例:

def gen123(): 
yield 2
yield 5
yield 9

以上是一個自動的生成器函數(shù),因為其主體中包含至少一條 yield 語句。這一細微差別是將常規(guī)函數(shù)轉(zhuǎn)變?yōu)樯善骱瘮?shù)的唯一因素,這有點棘手,因為常規(guī)函數(shù)與生成器函數(shù)之間存在巨大差異。

以類似任何其他函數(shù)的方式來調(diào)用生成器函數(shù):

>>> it = gen123()
>>> print(it)

此函數(shù)調(diào)用會立即返回,并且不含函數(shù)主體中指定的值。調(diào)用生成器函數(shù)始終會返回所謂的生成器對象。生成器對象是一種迭代器,可從生成器函數(shù)主體中的 yield 語句生成各種值。 在標(biāo)準(zhǔn)術(shù)語中,生成器對象會生成一系列值。讓我們從先前的代碼片段中深入挖掘生成器對象。

>>> print(next(it))
2
>>> print(next(it))
5
>>> print(next(it))
9
>>> print(next(it))
Traceback (most recent call last): 
File "", line 1, in 
StopIteration

每次對該對象調(diào)用 next() 時,都會返回下一個 yield 值,直至沒有任何其他值為止,在此情況下,將返回 StopIterator 異常。當(dāng)然,由于它是一個迭代器,因而可在 for 循環(huán)中使用。只要記得創(chuàng)建新的生成器對象即可,因為第一個生成器對象已耗盡。

>>> it = gen123()
>>> for ix in it: 
...print(ix)
... 

>>> for ix in gen123(): 
...print(ix)
... 

生成器函數(shù)自變量


生成器函數(shù)接受自變量,這些自變量可傳遞到生成器主體中。粘貼以下生成器函數(shù)。

def gen123plus(x): 
yield x + 1
yield x + 2
yield x + 3

現(xiàn)在,對其試用其他自變量,例如:

>>> for ix in gen123plus(10): 
...print(ix)
... 

對生成器對象進行迭代時,其函數(shù)處于暫掛狀態(tài),并在您執(zhí)行時恢復(fù),這樣就為 Python 函數(shù)引入了新的概念,F(xiàn)在,您實際上可以通過重疊方式從多個函數(shù)運行代碼。以下面的會話為例。

>>> it1 = gen123plus(10)
>>> it2 = gen123plus(20)
>>> print(next(it1))
11
>>> print(next(it2))
21
>>> print(next(it1))
12
>>> print(next(it1))
13
>>> print(next(it2))
22
>>> print(next(it1))
Traceback (most recent call last): 
File "", line 1, in 
StopIteration
>>> print(next(it2))
23
>>> print(next(it2))
Traceback (most recent call last): 
File "", line 1, in 
StopIteration

我從一個生成器函數(shù)創(chuàng)建兩個生成器對象。隨后,我可從其中任一對象獲取下一個項,并留意每個對象獨立暫掛和恢復(fù)的方式。它們在各個方面都彼此獨立,包括進入 StopIteration 的方式。

務(wù)必仔細研究此會話,直至您確實掌握整個過程為止。一旦掌握了這一過程,您也就真正對生成器有了基礎(chǔ)的認(rèn)識,并明白了它們?nèi)绱藦姶蟮脑蛩凇?/p>

請記住,您還可以使用所有常用的定位和關(guān)鍵字自變量功能。
 

生成器函數(shù)中的局部狀態(tài)


您可在生成器函數(shù)中通過條件、循環(huán)和局部變量執(zhí)行所有常規(guī)操作,構(gòu)建極其復(fù)雜的專用迭代器。

讓我們通過下一個示例來輕松一下。我們都對受制于天氣感到十分厭煩。讓我們來自行創(chuàng)造一些天氣。清單 1 是一個氣象模擬器,可打印一系列晴天或雨天,有時還帶有一些注釋。

提到天氣,晴天后通常還是晴天,雨天后通常還是雨天。您可通過隨機選擇次日天氣來對此進行模擬,但天氣保持不變的概率較高?梢杂“反復(fù)無常”一詞來形容天氣變化情況。在此生成器函數(shù)中,有一個自變量為 volatility,它的值應(yīng)在 0 到 1 之間。這個自變量的值越低,天氣從前一天到后一天保持不變的可能性就越高。在此清單中,volatility 設(shè)置為 0.2,這意味著平均 80% 的天氣前后兩天應(yīng)保持不變。

此清單具有一項附加功能,如果連續(xù)三天以上為晴天,或者連續(xù)三天以上為雨天,它會發(fā)布一些注釋。
 

清單 1. 清單 1.氣象模擬器

import random


def weathermaker(volatility, days): 
'''
Yield a series of messages giving the day's weather and occasional commentary


volatility - a float between 0 and 1; the greater this number the greater
the likelihood that the weather will change on each given day
days - number of days for which to generate weather
'''
#Always start as if yesterday were sunny
current_weather = 'sunny'
#First item is the probability that the weather will stay the same
#Second item is the probability that the weather will change
#The higher the volatility the greater the likelihood of change
weights = [1.0-volatility, volatility]
#For fun track how many sunny days in a row there have been
sunny_run = 1
#How many rainy days in a row there have been
rainy_run = 0
for day in range(days): 
#Figure out the opposite of the current weather
other_weather = 'rainy' if current_weather == 'sunny' else 'sunny'
#Set up to choose the next day's weather.First set up the choices
choose_from = [current_weather, other_weather]
#random.choices returns a list of random choices based on the weights
#By default a list of 1 item, so we grab that first and only item with [0]
current_weather = random.choices(choose_from, weights)[0]
yield 'today it is ' + current_weather
if current_weather == 'sunny': 
#Check for runs of three or more sunny days
sunny_run += 1
rainy_run = 0
if sunny_run >= 3: 
yield "Uh oh! We're getting thirsty!"
else: 
#Check for runs of three or more rainy days
rainy_run += 1
sunny_run = 0
if rainy_run >= 3: 
yield "Rain, rain go away!"
return


#Create a generator object and print its series of messages
for msg in weathermaker(0.2, 10): 
print(msg)

weathermaker 函數(shù)使用了許多常用編程功能,并且還解釋了生成器的一些很有趣的特性。生成的項數(shù)并不固定?赡軙c天數(shù)一樣少,也可能會因連續(xù)晴天或雨天而添加的注釋導(dǎo)致數(shù)量增加。這些都是在不同條件分支下生成的。

運行此清單后,應(yīng)顯示如下內(nèi)容:

$ python weathermaker.py
today it is sunny
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is rainy
today it is sunny
today it is rainy
today it is rainy
today it is rainy
Rain, rain go away!
today it is rainy
Rain, rain go away!

當(dāng)然,這是隨機的,每次天氣保持不變的幾率都可達到 80%,因此可以輕易獲得以下結(jié)果:

$ python weathermaker.py
today it is sunny
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!
today it is sunny
Uh oh! We're getting thirsty!

請花些時間自行嘗試一下,首先為 volatilitydays 傳入不同的值,然后對生成器函數(shù)代碼本身進行調(diào)整。試驗是確保您真正了解生成器運作方式的最佳途徑。

我希望這個有趣的示例通過生成器的一些強大功能,可以激發(fā)您的想像力。當(dāng)然無需生成器也可以編寫上述代碼,但這種方法不僅更易于表達、通常更高效,并且除了清單底部的簡單循環(huán)之外,還能通過其他有趣的方法來復(fù)用 weathermaker 生成器。
 

生成器表達式



生成器的常見用途是在一個迭代器上進行迭代,并通過某種方式對其進行操縱,生成經(jīng)過修改的迭代器。

讓我們來編寫一個生成器,此生成器采用一個迭代器,并根據(jù)提供的一組替換值按順序替換找到的值。

def substituter(seq, substitutions): 
for item in seq: 
if item in substitutions: 
yield substitutions[item]
else: 
yield item

在以下會話中,您可以看到此生成器的使用方式示例:

>>> s = 'hello world and everyone in the world'
>>> subs = {'hello': 'goodbye', 'world': 'galaxy'}
>>> for word in substituter(s.split(), subs): 
...print(word, end=' ')
... 
goodbye galaxy and everyone in the galaxy

同樣,請花些時間來自行嘗試,試試其他循環(huán)操作,直至您明確理解生成器的工作方式為止。

這種操作極為常見,以致于 Python 為它提供了一種易用的語法,即生成器表達式。以下是使用生成器表達式實現(xiàn)的先前會話。

>>> words = ( subs.get(item, item) for item in s.split() )
>>> for word in words: 
...print(word, end=' ')
... 
goodbye galaxy and everyone in the galaxy

簡而言之,只要您使用括號將 for 表達式括起,那么它就會變成一個生成器表達式。生成的對象即為生成器對象,在此情況下此對象被分配至 words。有時,您最后會使用某些更有趣的 Python 內(nèi)容來適應(yīng)此類表達式。在此情況下,我對字典使用 get 方法,此方法會查找一個鍵,但允許我指定未找到此鍵時返回的默認(rèn)值。我要求系統(tǒng)返回 item 的替換值(如果能找到),否則僅按現(xiàn)狀返回 item。
 

列表解析概述


您可能已熟悉列表解析。以下是相似的語法,但使用了方括號。列表解析的結(jié)果就是一個列表:

>>> mylist = [ ix for ix in range(10, 20) ]
>>> print(mylist)
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

生成器表達式的語法與之相似,但它返回的是生成器對象:

>>> mygen = ( ix for ix in range(10, 20) )
>>> print(mygen)
 at 0x10ccccba0>
>>> print(next(mygen))
10

以上兩個示例之間的實際差異主要在于,第一個示例中創(chuàng)建的列表自創(chuàng)建之后便存在,會占用所有必要內(nèi)存來存儲其值。生成器表達式不會使用這么多的存儲空間,而是處于暫掛狀態(tài),當(dāng)對其進行迭代時才恢復(fù),就像生成器函數(shù)的主體一樣。事實上,它允許您按需獲取數(shù)據(jù),而不是預(yù)先為您存儲所有數(shù)據(jù)。

打個比方,您全家每年喝 200 加侖牛奶,但您并不想在自家地下室建一個儲藏設(shè)施來儲藏這么多牛奶,而是根據(jù)需要每次到店里買 1 加侖。使用生成器代替不斷構(gòu)建列表,就有點像去雜貨店購買而不是自建倉庫一樣。

此外,還有字典表達式,但這不在本教程的討論范圍內(nèi)。請注意,您可以輕松將生成器表達式轉(zhuǎn)換為列表,有時這是一次性耗用生成器的一種方式,但若稍有不慎,這也可能導(dǎo)致通過創(chuàng)建需要大量內(nèi)存的列表而違背了使用生成器的本意。

>>> mygen = ( ix for ix in range(10, 20) )
>>> print(list(mygen))
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

在此系列教程中,有時我將從生成器構(gòu)建列表,以便于快速展示。
 

過濾和鏈接


您可以在生成器表達式中使用簡單的條件從輸入迭代器中過濾掉某些項。以下示例可生成從 1 到 20 之間不是 2 和 3 的倍數(shù)的所有數(shù)字。它使用易用的 math.gcd 函數(shù),返回這兩個整數(shù)的最大公約數(shù)。例如,如果某個數(shù)字和 2 的最大公約數(shù)為 1,那么此數(shù)字不是 2 的倍數(shù)。

>>> import math
>>> notby2or3 = ( n for n in range(1, 20) if math.gcd(n, 2) == 1 and math.gcd(n, 3) == 1 )
>>> print(list(notby2or3))
[1, 5, 7, 11, 13, 17, 19]

您可以看到正確將 if 表達式直接插入生成器表達式的方式。請注意,您還可以在生成器表達式中嵌套 for 表達式。同樣,生成器表達式只是生成器函數(shù)的緊湊語法,如果需要真正復(fù)雜的生成器表達式,最終可能會僅使用生成器函數(shù)生成更可讀的代碼。

您可將生成器對象鏈接在一起,包括生成器表達式。

>>> notby2or3 = ( n for n in range(1, 20) if math.gcd(n, 2) == 1 and math.gcd(n, 3) == 1 )
>>> squarednotby2or3 = ( n*n for n in notby2or3 )
>>> print(list(squarednotby2or3))
[1, 25, 49, 121, 169, 289, 361]

此類鏈接的生成器模式既強大又高效。在上述示例中,第一行定義了生成器對象,但未完成其中任何工作。第二行定義了第二個生成器對象,此對象引用第一個對象,但未完成這些對象的任何工作。直到請求完整迭代(在此情況下,由 list 構(gòu)造函數(shù)發(fā)出請求),方才完成所有工作。這種根據(jù)需要執(zhí)行迭代工作的構(gòu)想稱為惰性求值,它是使用生成器精心設(shè)計的代碼的特征之一。但請記住,使用此類生成器和生成器表達式鏈時,必須由某項實際觸發(fā)迭代。在本例中,由 list 函數(shù)觸發(fā)。也可由 for 循環(huán)觸發(fā)。設(shè)置完各種生成器后忘記觸發(fā)迭代是一種很容易犯的錯誤,在這種情況下,您可能冥思苦想也找不到代碼不運行的原因。
 

惰性的價值


在本教程中,您學(xué)習(xí)了迭代器的基礎(chǔ)知識,同時也了解了關(guān)于迭代器、生成器函數(shù)和表達式的一些最有趣的來源。

當(dāng)然,即使不使用任何生成器,您也可以編寫本教程中的所有代碼,但通過學(xué)習(xí)使用生成器,就思考如何處理任何持續(xù)發(fā)展的概念或數(shù)據(jù)而言,提供了更靈活、更高效的方式。同樣以我先前的比喻為例,每次按需從商店買一加侖牛奶,顯然要比自建倉庫儲藏一年的供應(yīng)量更合理。雖然開發(fā)人員將此同等方法稱為惰性求值,但惰性更多的是表示您獲取自己所需內(nèi)容的時機。每隔一天去一次雜貨店似乎算不上懶惰。同樣,有時編寫使用生成器的代碼需要更多的工作量,甚至更加燒腦,但更易于擴展的處理方式也會隨之帶來諸多優(yōu)勢。

學(xué)習(xí)使用迭代器和生成器是掌握 Python 的一個重要步驟,而另一個重要步驟就是學(xué)習(xí)使用標(biāo)準(zhǔn)庫中為處理迭代器而提供的眾多神奇的工具。這將是本系列中下一個教程的主題。

標(biāo)簽: 大數(shù)據(jù) 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:大屏數(shù)據(jù)可視化示例

下一篇:最詳細大數(shù)據(jù)項目落地路線圖實踐總結(jié)