對(duì) Python 中的 Yield 一直理解的不夠深刻娄昆,甚至存在誤解。遇到一個(gè)神奇的用法后(多個(gè) yield 連續(xù)使用)又好好研究了下,以下記錄鄙人粗糙見(jiàn)解。
首先簡(jiǎn)單科普一下 yield:
要理解 yield 語(yǔ)句盒延,關(guān)鍵要理解 python 的生成器。 用官網(wǎng)的說(shuō)法1鼠冕、2兰英, 生成器就是一個(gè)返回迭代器(iterator)的函數(shù)。 和普通函數(shù)唯一的區(qū)別就是這個(gè)函數(shù)包含 yield 語(yǔ)句供鸠。
包含了 yield 的函數(shù),就是一個(gè)生成器
我是在學(xué)爬蟲(chóng) scrapy 框架時(shí)遇見(jiàn)多個(gè)yield 連續(xù)使用的
在最后兩行代碼:
# 例1
def parse(self, response):
# 1取出符合條件的 tr列表
tr_list = response.xpath('//tr[@class="even"] | //tr[@class="odd"]')
# 判斷數(shù)據(jù)的有無(wú) ,沒(méi)有 到底了
if not tr_list:
return
# 2.遍歷 每一個(gè) tr 取出內(nèi)容
for tr in tr_list:
item = TencentItem()
item['work_name'] = tr.xpath('./td[1]/a/text()').extract_first()
item['work_type'] = tr.xpath('./td[2]/text()').extract_first()
item['work_count'] = tr.xpath('./td[3]/text()').extract_first()
item['work_place'] = tr.xpath('./td[4]/text()').extract_first()
item['work_time'] = tr.xpath('./td[5]/text()').extract_first()
item['work_link'] = 'https://hr.tencent.com/' + tr.xpath('./td[1]/a/@href').extract_first()
# 給 引擎 -->管道
yield item
# 告訴 引擎 請(qǐng)求詳情頁(yè)
yield scrapy.Request(url=item['work_link'], callback=self.detail_parse)
初次看到時(shí)陨闹,對(duì)于以上代碼執(zhí)行機(jī)制實(shí)在難以理解楞捂!
因?yàn)樵谧畛醯睦斫饫铮?yield 有兩個(gè)作用:
- 暫停執(zhí)行當(dāng)前代碼,并記錄當(dāng)前位置
- 相當(dāng)于 return趋厉, 可以在后面返回值
繼續(xù)執(zhí)行 yield 下面的代碼需要使用 next()
或者 send()
寨闹。
我就困惑了, 這里兩個(gè) yield 之間沒(méi)有使用 next() 或者 send() 方法呀君账,那么它是怎么執(zhí)行的呢繁堡?
然后我自己寫(xiě)了一個(gè)簡(jiǎn)單的測(cè)試函數(shù),來(lái)探究 yield 的執(zhí)行機(jī)制:
# 例2
# 自定義一個(gè)生成器
def genter():
a = 4
b = 5
c = 6
for i in range(5):
yield a
print('hhh'+str(i))
yield b
print("aaa" + str(i))
yield c
# 包含了yield 的 genter() 就是一個(gè)生成器
res = genter()
for i, c in enumerate(res):
if i > 1:
# 通過(guò) break 來(lái)測(cè)試執(zhí)行的結(jié)果
break
print(c)
而其結(jié)果是:
4
hhh0
5
aaa0
大家應(yīng)該知道:
在取值時(shí)乡数,使用 for 語(yǔ)句椭蹄,里面封裝了 next 方法。來(lái)一個(gè)個(gè)取出生成器的值净赴,由程序運(yùn)行結(jié)果可知:例2 中 genter() 實(shí)際是通過(guò) 多個(gè) yield 實(shí)現(xiàn)了包含多個(gè)值的生成器
顛覆認(rèn)知的是:yield 并沒(méi)有暫停绳矩,yield 語(yǔ)句后面的 print 正常打印了!玖翅!
因此與其說(shuō) yield 的作用是暫停并記錄位置翼馆, 不如嚴(yán)謹(jǐn)?shù)卣f(shuō)成:只有在只有一個(gè)yield 的情況下割以, 才是暫停并記錄位置。函數(shù)的反復(fù)調(diào)用应媚,也是這個(gè)值(迭代器)的反復(fù)調(diào)用严沥;而上述yield 之間的 next 方法就通過(guò) for 循環(huán)調(diào)用了
多個(gè) yield 的情況下,應(yīng)該理解成:這個(gè)函數(shù)本身就是一個(gè)擁有多個(gè)值(迭代器)的迭代器中姜,此時(shí) yield 的暫停消玄, 應(yīng)該暫停于下一個(gè) yield 之前!
一般我們所見(jiàn)到的生成器扎筒,只有一個(gè) yield莱找,通過(guò)反復(fù)調(diào)用這個(gè)方法,來(lái)實(shí)現(xiàn)所謂的生成器嗜桌。在這樣的情況下奥溺,我們也習(xí)慣地以為 yield 的兩個(gè)作用之一就是暫停執(zhí)行當(dāng)前的代碼, 并記錄當(dāng)前位置骨宠,并且有 return 的作用浮定。
不過(guò)多個(gè) yield 和單個(gè) yield 的情況,為何會(huì)不一樣呢层亿?
其底層的原理應(yīng)該一樣才對(duì)桦卒!
對(duì)于這個(gè),啃了下官方文檔匿又,沒(méi)看太明白方灾。可能要去學(xué)了 C 語(yǔ)言才能理解÷蹈現(xiàn)在粗淺理解:之所以包含了 yield 關(guān)鍵字的函數(shù)就是一個(gè)生成器裕偿, 是因?yàn)?yield自身就是生成器!
那么對(duì)于例1 中的情況就很好理解了痛单,parse 本身是一個(gè)生成器嘿棘,scrapy 引擎會(huì)在調(diào)用其中值時(shí)添加next() 方法吧~
另外,除了官方文檔旭绒, 還可以看下這篇講協(xié)程的文檔鸟妙。
由于學(xué)藝未精,如有錯(cuò)誤挥吵,望不吝賜教重父,謝謝!