久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

深入理解Python迭代器和生成器

 hanxinhanxin 2020-03-16
深入理解Python迭代器和生成器

在第一次接觸 Python 的時(shí)候,,你可能寫過類似 for i in [2, 3, 5, 7, 11, 13]: print(i) 這樣的語句,。for in 語句理解起來很直觀形象,,比起 C++ 和 java 早期的 for (int i = 0; i < n; i ++) printf("%d\n", a[i]) 這樣的語句,,不知道簡潔清晰到哪里去了。

但是,,你想過 Python 在處理 for in 語句的時(shí)候,,具體發(fā)生了什么嗎?什么樣的對(duì)象可以被 for in 來枚舉呢,?

我們深入到 Python 的容器類型實(shí)現(xiàn)底層去走走,,了解一種叫做迭代器和生成器的東西。

你肯定用過的容器,、可迭代對(duì)象和迭代器

容器這個(gè)概念非常好理解,。我們說過,在Python 中一切皆對(duì)象,,對(duì)象的抽象就是類,,而對(duì)象的集合就是容器。

列表(list: [0, 1, 2]),,元組(tuple: (0, 1, 2)),,字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器,。對(duì)于容器,,你可以很直觀地想象成多個(gè)元素在一起的單元;而不同容器的區(qū)別,,正是在于內(nèi)部數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)方法,。然后,你就可以針對(duì)不同場(chǎng)景,,選擇不同時(shí)間和空間復(fù)雜度的容器,。

所有的容器都是可迭代的(iterable)。這里的迭代,,和枚舉不完全一樣,。迭代可以想象成是你去買蘋果,賣家并不告訴你他有多少庫存,。這樣,,每次你都需要告訴賣家,,你要一個(gè)蘋果,然后賣家采取行為:要么給你拿一個(gè)蘋果,;要么告訴你,,蘋果已經(jīng)賣完了。你并不需要知道,,賣家在倉庫是怎么擺放蘋果的,。

嚴(yán)謹(jǐn)?shù)卣f,迭代器(iterator)提供了一個(gè) next 的方法,。調(diào)用這個(gè)方法后,,你要么得到這個(gè)容器的下一個(gè)對(duì)象,要么得到一個(gè) StopIteration 的錯(cuò)誤(蘋果賣完了),。你不需要像列表一樣指定元素的索引,,因?yàn)樽值浜图线@樣的容器并沒有索引一說。比如,,字典采用哈希表實(shí)現(xiàn),,那么你就只需要知道,next 函數(shù)可以不重復(fù)不遺漏地一個(gè)一個(gè)拿到所有元素即可,。

而可迭代對(duì)象,,通過 iter() 函數(shù)返回一個(gè)迭代器,再通過 next() 函數(shù)就可以實(shí)現(xiàn)遍歷,。for in 語句將這個(gè)過程隱式化,,所以,你只需要知道它大概做了什么就行了,。

來看下面這段代碼,,主要向你展示怎么判斷一個(gè)對(duì)象是否可迭代。當(dāng)然,,這還有另一種做法,,是 isinstance(obj, Iterable)。

def is_iterable(param):
try:
iter(param)
return True
except TypeError:
return False
params = [
1234,
'1234',
[1, 2, 3, 4],
set([1, 2, 3, 4]),
{1:1, 2:2, 3:3, 4:4},
(1, 2, 3, 4)
]
for param in params:
print('{} is iterable? {}'.format(param, is_iterable(param)))
########## 輸出 ##########
1234 is iterable? False
1234 is iterable? True
[1, 2, 3, 4] is iterable? True
{1, 2, 3, 4} is iterable? True
{1: 1, 2: 2, 3: 3, 4: 4} is iterable? True
(1, 2, 3, 4) is iterable? True

通過這段代碼,,你就可以知道,,給出的類型中,除了數(shù)字 1234 之外,,其它的數(shù)據(jù)類型都是可迭代的,。

生成器,又是什么,?

據(jù)我所知,,很多人對(duì)生成器這個(gè)概念會(huì)比較陌生,因?yàn)樯善髟诤芏喑S谜Z言中,并沒有相對(duì)應(yīng)的模型,。

這里,,你只需要記著一點(diǎn):生成器是懶人版本的迭代器。

我們知道,,在迭代器中,,如果我們想要枚舉它的元素,這些元素需要事先生成,。這里,,我們先來看下面這個(gè)簡單的樣例。

import os
import psutil
顯示當(dāng)前 python 程序占用的內(nèi)存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))
def test_iterator():
show_memory_info('initing iterator')
list_1 = [i for i in range(100000000)]
show_memory_info('after iterator initiated')
print(sum(list_1))
show_memory_info('after sum called')
def test_generator():
show_memory_info('initing generator')
list_2 = (i for i in range(100000000))
show_memory_info('after generator initiated')
print(sum(list_2))
show_memory_info('after sum called')
%time test_iterator()
%time test_generator()
########## 輸出 ##########
initing iterator memory used: 48.9765625 MB
after iterator initiated memory used: 3920.30078125 MB
4999999950000000
after sum called memory used: 3920.3046875 MB
Wall time: 17 s
initing generator memory used: 50.359375 MB
after generator initiated memory used: 50.359375 MB
4999999950000000
after sum called memory used: 50.109375 MB
Wall time: 12.5 s

聲明一個(gè)迭代器很簡單,,[i for i in range(100000000)]就可以生成一個(gè)包含一億元素的列表,。每個(gè)元素在生成后都會(huì)保存到內(nèi)存中,你通過代碼可以看到,,它們占用了巨量的內(nèi)存,,內(nèi)存不夠的話就會(huì)出現(xiàn) OOM 錯(cuò)誤。

不過,,我們并不需要在內(nèi)存中同時(shí)保存這么多東西,比如對(duì)元素求和,,我們只需要知道每個(gè)元素在相加的那一刻是多少就行了,,用完就可以扔掉了。

于是,,生成器的概念應(yīng)運(yùn)而生,,在你調(diào)用 next() 函數(shù)的時(shí)候,才會(huì)生成下一個(gè)變量,。生成器在 Python 的寫法是用小括號(hào)括起來,,(i for i in range(100000000)),即初始化了一個(gè)生成器,。

這樣一來,,你可以清晰地看到,生成器并不會(huì)像迭代器一樣占用大量內(nèi)存,,只有在被使用的時(shí)候才會(huì)調(diào)用,。而且生成器在初始化的時(shí)候,并不需要運(yùn)行一次生成操作,,相比于 test_iterator() ,,test_generator() 函數(shù)節(jié)省了一次生成一億個(gè)元素的過程,因此耗時(shí)明顯比迭代器短,。

到這里,,你可能說,生成器不過如此嘛,我有的是錢,,不就是多占一些內(nèi)存和計(jì)算資源嘛,,我多出點(diǎn)錢就是了唄。

哪怕你是土豪,,請(qǐng)坐下先喝點(diǎn)茶,,再聽我繼續(xù)講完,這次,,我們來實(shí)現(xiàn)一個(gè)自定義的生成器,。

生成器,還能玩什么花樣,?

數(shù)學(xué)中有一個(gè)恒等式,,(1 + 2 + 3 + ... + n)^2 = 1^3 + 2^3 + 3^3 + ... + n^3,想必你高中就應(yīng)該學(xué)過它?,F(xiàn)在,,我們來驗(yàn)證一下這個(gè)公式的正確性。老規(guī)矩,,先放代碼,,你先自己閱讀一下,看不懂的也不要緊,,接下來我再來詳細(xì)講解,。

def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3)
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n):
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)
########## 輸出 ##########
<generator object generator at 0x000001E70651C4F8>
<generator object generator at 0x000001E70651C390>
next_1 = 1, next_3 = 1
next_1 = 2, next_3 = 8
next_1 = 3, next_3 = 27
next_1 = 4, next_3 = 64
next_1 = 5, next_3 = 125
next_1 = 6, next_3 = 216
next_1 = 7, next_3 = 343
next_1 = 8, next_3 = 512
1296 1296

這段代碼中,你首先注意一下 generator() 這個(gè)函數(shù),,它返回了一個(gè)生成器,。

接下來的yield 是魔術(shù)的關(guān)鍵。對(duì)于初學(xué)者來說,,你可以理解為,,函數(shù)運(yùn)行到這一行的時(shí)候,程序會(huì)從這里暫停,,然后跳出,,不過跳到哪里呢?答案是 next() 函數(shù),。那么 i ** k 是干什么的呢,?它其實(shí)成了 next() 函數(shù)的返回值。

這樣,,每次 next(gen) 函數(shù)被調(diào)用的時(shí)候,,暫停的程序就又復(fù)活了,從 yield 這里向下繼續(xù)執(zhí)行,;同時(shí)注意,,局部變量 i 并沒有被清除掉,,而是會(huì)繼續(xù)累加。我們可以看到 next_1 從 1 變到 8,,next_3 從 1 變到 512,。

聰明的你應(yīng)該注意到了,這個(gè)生成器居然可以一直進(jìn)行下去,!沒錯(cuò),,事實(shí)上,迭代器是一個(gè)有限集合,,生成器則可以成為一個(gè)無限集,。我只管調(diào)用 next(),生成器根據(jù)運(yùn)算會(huì)自動(dòng)生成新的元素,,然后返回給你,,非常便捷。

到這里,,土豪同志應(yīng)該也坐不住了吧,,那么,還能再給力一點(diǎn)嗎,?

別急,,我們?cè)賮砜匆粋€(gè)問題:給定一個(gè) list 和一個(gè)指定數(shù)字,求這個(gè)數(shù)字在 list 中的位置,。

下面這段代碼你應(yīng)該不陌生,,也就是常規(guī)做法,枚舉每個(gè)元素和它的 index,,判斷后加入 result,最后返回,。

def index_normal(L, target):
result = []
for i, num in enumerate(L):
if num == target:
result.append(i)
return result
print(index_normal([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2))
########## 輸出 ##########
[2, 5, 9]

那么使用迭代器可以怎么做呢,?二話不說,先看代碼,。

def index_generator(L, target):
for i, num in enumerate(L):
if num == target:
yield i
print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))
########## 輸出 ##########
[2, 5, 9]

聰明的你應(yīng)該看到了明顯的區(qū)別,,我就不做過多解釋了。唯一需要強(qiáng)調(diào)的是,, index_generator 會(huì)返回一個(gè) Generator 對(duì)象,,需要使用 list 轉(zhuǎn)換為列表后,才能用 print 輸出,。

這里我再多說兩句,。在Python 語言規(guī)范中,用更少,、更清晰的代碼實(shí)現(xiàn)相同功能,,一直是被推崇的做法,,因?yàn)檫@樣能夠很有效提高代碼的可讀性,減少出錯(cuò)概率,,也方便別人快速準(zhǔn)確理解你的意圖,。當(dāng)然,要注意,,這里“更少”的前提是清晰,,而不是使用更多的魔術(shù)操作,雖說減少了代碼卻反而增加了閱讀的難度,。

回歸正題,。接下來我們?cè)賮砜匆粋€(gè)問題:給定兩個(gè)序列,判定第一個(gè)是不是第二個(gè)的子序列,。(LeetCode 鏈接如下:https:///problems/is-subsequence/ )

先來解讀一下這個(gè)問題本身,。序列就是列表,子序列則指的是,,一個(gè)列表的元素在第二個(gè)列表中都按順序出現(xiàn),,但是并不必挨在一起。舉個(gè)例子,,[1, 3, 5] 是 [1, 2, 3, 4, 5] 的子序列,,[1, 4, 3] 則不是。

要解決這個(gè)問題,,常規(guī)算法是貪心算法,。我們維護(hù)兩個(gè)指針指向兩個(gè)列表的最開始,然后對(duì)第二個(gè)序列一路掃過去,,如果某個(gè)數(shù)字和第一個(gè)指針指的一樣,,那么就把第一個(gè)指針前進(jìn)一步。第一個(gè)指針移出第一個(gè)序列最后一個(gè)元素的時(shí)候,,返回 True,,否則返回 False。

不過,,這個(gè)算法正常寫的話,,寫下來怎么也得十行左右。

那么如果我們用迭代器和生成器呢,?

def is_subsequence(a, b):
b = iter(b)
return all(i in b for i in a)
print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))
########## 輸出 ##########
True
False

這簡短的幾行代碼,,你是不是看得一頭霧水,不知道發(fā)生了什么,?

來,,我們先把這段代碼復(fù)雜化,然后一步步看,。

def is_subsequence(a, b):
b = iter(b)
print(b)
gen = (i for i in a)
print(gen)
for i in gen:
print(i)
gen = ((i in b) for i in a)
print(gen)
for i in gen:
print(i)
return all(((i in b) for i in a))
print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))
########## 輸出 ##########
<list_iterator object at 0x000001E7063D0E80>
<generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C570>
1
3
5
<generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C5E8>
True
True
True
False
<list_iterator object at 0x000001E7063D0D30>
<generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C5E8>
1
4
3
<generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C570>
True
True
False
False

首先,,第二行的b = iter(b),,把列表 b 轉(zhuǎn)化成了一個(gè)迭代器,這里我先不解釋為什么要這么做,。

接下來的gen = (i for i in a)語句很好理解,,產(chǎn)生一個(gè)生成器,這個(gè)生成器可以遍歷對(duì)象 a,,因此能夠輸出 1, 3, 5,。而 (i in b)需要好好揣摩,這里你是不是能聯(lián)想到 for in 語句,?

沒錯(cuò),,這里的(i in b),大致等價(jià)于下面這段代碼:

while True:
val = next(b)
if val == i:
yield True

這里非常巧妙地利用生成器的特性,,next() 函數(shù)運(yùn)行的時(shí)候,,保存了當(dāng)前的指針。比如再看下面這個(gè)示例:

b = (i for i in range(5))
print(2 in b)
print(4 in b)
print(3 in b)
########## 輸出 ##########
True
True
False

至于最后的 all() 函數(shù),,就很簡單了,。它用來判斷一個(gè)迭代器的元素是否全部為 True,如果是則返回 True,,否則就返回 False.

于是到此,,我們就很優(yōu)雅地解決了這道面試題。在這個(gè)技術(shù)知識(shí)點(diǎn)上,,在實(shí)際工作的應(yīng)用上,,你已經(jīng)比很多人更加熟練了。繼續(xù)加油,!

總結(jié)一下,,今天我們講了四種不同的對(duì)象,分別是容器,、可迭代對(duì)象,、迭代器和生成器。

  • 容器是可迭代對(duì)象,,可迭代對(duì)象調(diào)用 iter() 函數(shù),可以得到一個(gè)迭代器,。迭代器可以通過 next() 函數(shù)來得到下一個(gè)元素,,從而支持遍歷。
  • 生成器是一種特殊的迭代器(注意這個(gè)邏輯關(guān)系反之不成立),。使用生成器,,你可以寫出來更加清晰的代碼;合理使用生成器,,可以降低內(nèi)存占用,、優(yōu)化程序結(jié)構(gòu),、提高程序速度。
  • 生成器在 Python 2 的版本上,,是協(xié)程的一種重要實(shí)現(xiàn)方式,;而 Python 3.5 引入 async await 語法糖后,生成器實(shí)現(xiàn)協(xié)程的方式就已經(jīng)落后了

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多