1. 迭代器協(xié)議
成都創(chuàng)新互聯(lián)主要為客戶提供服務(wù)項(xiàng)目涵蓋了網(wǎng)頁(yè)視覺(jué)設(shè)計(jì)、VI標(biāo)志設(shè)計(jì)、成都營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)站程序開(kāi)發(fā)、HTML5響應(yīng)式重慶網(wǎng)站建設(shè)公司、移動(dòng)網(wǎng)站建設(shè)、微商城、網(wǎng)站托管及網(wǎng)站維護(hù)、WEB系統(tǒng)開(kāi)發(fā)、域名注冊(cè)、國(guó)內(nèi)外服務(wù)器租用、視頻、平面設(shè)計(jì)、SEO優(yōu)化排名。設(shè)計(jì)、前端、后端三個(gè)建站步驟的完善服務(wù)體系。一人跟蹤測(cè)試的建站服務(wù)標(biāo)準(zhǔn)。已經(jīng)為成都石雕行業(yè)客戶提供了網(wǎng)站推廣服務(wù)。
由于生成器自動(dòng)實(shí)現(xiàn)了迭代器協(xié)議,而迭代器協(xié)議對(duì)很多人來(lái)說(shuō),也是一個(gè)較為抽象的概念。所以,為了更好的理解生成器,我們需要簡(jiǎn)單的回顧一下迭代器協(xié)議的概念。
迭代器協(xié)議是指:對(duì)象需要提供next方法,它要么返回迭代中的下一項(xiàng),要么就引起一個(gè)StopIteration異常,以終止迭代
可迭代對(duì)象就是:實(shí)現(xiàn)了迭代器協(xié)議的對(duì)象
協(xié)議是一種約定,可迭代對(duì)象實(shí)現(xiàn)迭代器協(xié)議,Python的內(nèi)置工具(如for循環(huán),sum,min,max函數(shù)等)使用迭代器協(xié)議訪問(wèn)對(duì)象。
舉個(gè)例子:在所有語(yǔ)言中,我們都可以使用for循環(huán)來(lái)遍歷數(shù)組,Python的list底層實(shí)現(xiàn)是一個(gè)數(shù)組,所以,我們可以使用for循環(huán)來(lái)遍歷list。如下所示:
for n in [1, 2, 3, 4]:
... print n
但是,對(duì)Python稍微熟悉一點(diǎn)的朋友應(yīng)該知道,Python的for循環(huán)不但可以用來(lái)遍歷list,還可以用來(lái)遍歷文件對(duì)象,如下所示:
with open(‘/etc/passwd’) as f: # 文件對(duì)象提供迭代器協(xié)議
... for line in f: # for循環(huán)使用迭代器協(xié)議訪問(wèn)文件
... print line
...
為什么在Python中,文件還可以使用for循環(huán)進(jìn)行遍歷呢?這是因?yàn)?,在Python中,文件對(duì)象實(shí)現(xiàn)了迭代器協(xié)議,for循環(huán)并不知道它遍歷的是一個(gè)文件對(duì)象,它只管使用迭代器協(xié)議訪問(wèn)對(duì)象即可。正是由于Python的文件對(duì)象實(shí)現(xiàn)了迭代器協(xié)議,我們才得以使用如此方便的方式訪問(wèn)文件,如下所示:
f = open('/etc/passwd')
dir(f)
['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...'
2. 生成器
Python使用生成器對(duì)延遲操作提供了支持。所謂延遲操作,是指在需要的時(shí)候才產(chǎn)生結(jié)果,而不是立即產(chǎn)生結(jié)果。這也是生成器的主要好處。
Python有兩種不同的方式提供生成器:
生成器函數(shù):常規(guī)函數(shù)定義,但是,使用yield語(yǔ)句而不是return語(yǔ)句返回結(jié)果。yield語(yǔ)句一次返回一個(gè)結(jié)果,在每個(gè)結(jié)果中間,掛起函數(shù)的狀態(tài),以便下次重它離開(kāi)的地方繼續(xù)執(zhí)行
生成器表達(dá)式:類似于列表推導(dǎo),但是,生成器返回按需產(chǎn)生結(jié)果的一個(gè)對(duì)象,而不是一次構(gòu)建一個(gè)結(jié)果列表
2.1 生成器函數(shù)
我們來(lái)看一個(gè)例子,使用生成器返回自然數(shù)的平方(注意返回的是多個(gè)值):
def gensquares(N):
for i in range(N):
yield i ** 2
for item in gensquares(5):
print item,
使用普通函數(shù):
def gensquares(N):
res = []
for i in range(N):
res.append(i*i)
return res
for item in gensquares(5):
print item,
可以看到,使用生成器函數(shù)代碼量更少。
2.2 生成器表達(dá)式
使用列表推導(dǎo),將會(huì)一次產(chǎn)生所有結(jié)果:
squares = [x**2 for x in range(5)]
squares
[0, 1, 4, 9, 16]
將列表推導(dǎo)的中括號(hào),替換成圓括號(hào),就是一個(gè)生成器表達(dá)式:
squares = (x**2 for x in range(5))
squares
generator object at 0x00B2EC88
next(squares)
next(squares)
1
next(squares)
4
list(squares)
[9, 16]
Python不但使用迭代器協(xié)議,讓for循環(huán)變得更加通用。大部分內(nèi)置函數(shù),也是使用迭代器協(xié)議訪問(wèn)對(duì)象的。例如, sum函數(shù)是Python的內(nèi)置函數(shù),該函數(shù)使用迭代器協(xié)議訪問(wèn)對(duì)象,而生成器實(shí)現(xiàn)了迭代器協(xié)議,所以,我們可以直接這樣計(jì)算一系列值的和:
sum(x ** 2 for x in xrange(4))
而不用多此一舉的先構(gòu)造一個(gè)列表:
sum([x ** 2 for x in xrange(4)])
2.3 再看生成器
前面已經(jīng)對(duì)生成器有了感性的認(rèn)識(shí),我們以生成器函數(shù)為例,再來(lái)深入探討一下Python的生成器:
語(yǔ)法上和函數(shù)類似:生成器函數(shù)和常規(guī)函數(shù)幾乎是一樣的。它們都是使用def語(yǔ)句進(jìn)行定義,差別在于,生成器使用yield語(yǔ)句返回一個(gè)值,而常規(guī)函數(shù)使用return語(yǔ)句返回一個(gè)值
自動(dòng)實(shí)現(xiàn)迭代器協(xié)議:對(duì)于生成器,Python會(huì)自動(dòng)實(shí)現(xiàn)迭代器協(xié)議,以便應(yīng)用到迭代背景中(如for循環(huán),sum函數(shù))。由于生成器自動(dòng)實(shí)現(xiàn)了迭代器協(xié)議,所以,我們可以調(diào)用它的next方法,并且,在沒(méi)有值可以返回的時(shí)候,生成器自動(dòng)產(chǎn)生StopIteration異常
狀態(tài)掛起:生成器使用yield語(yǔ)句返回一個(gè)值。yield語(yǔ)句掛起該生成器函數(shù)的狀態(tài),保留足夠的信息,以便之后從它離開(kāi)的地方繼續(xù)執(zhí)行
3. 示例
我們?cè)賮?lái)看兩個(gè)生成器的例子,以便大家更好的理解生成器的作用。
首先,生成器的好處是延遲計(jì)算,一次返回一個(gè)結(jié)果。也就是說(shuō),它不會(huì)一次生成所有的結(jié)果,這對(duì)于大數(shù)據(jù)量處理,將會(huì)非常有用。
大家可以在自己電腦上試試下面兩個(gè)表達(dá)式,并且觀察內(nèi)存占用情況。對(duì)于前一個(gè)表達(dá)式,我在自己的電腦上進(jìn)行測(cè)試,還沒(méi)有看到最終結(jié)果電腦就已經(jīng)卡死,對(duì)于后一個(gè)表達(dá)式,幾乎沒(méi)有什么內(nèi)存占用。
sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))
除了延遲計(jì)算,生成器還能有效提高代碼可讀性。例如,現(xiàn)在有一個(gè)需求,求一段文字中,每個(gè)單詞出現(xiàn)的位置。
不使用生成器的情況:
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result
使用生成器的情況:
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index
這里,至少有兩個(gè)充分的理由說(shuō)明 ,使用生成器比不使用生成器代碼更加清晰:
使用生成器以后,代碼行數(shù)更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數(shù)越少越好
不使用生成器的時(shí)候,對(duì)于每次結(jié)果,我們首先看到的是result.append(index),其次,才是index。也就是說(shuō),我們每次看到的是一個(gè)列表的append操作,只是append的是我們想要的結(jié)果。使用生成器的時(shí)候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。
這個(gè)例子充分說(shuō)明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語(yǔ)句和return語(yǔ)句一樣,也是返回一個(gè)值。那么,就能夠理解為什么使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。
4. 使用生成器的注意事項(xiàng)
相信通過(guò)這篇文章,大家已經(jīng)能夠理解生成器的作用和好處。但是,還沒(méi)有結(jié)束,使用生成器,也有一點(diǎn)注意事項(xiàng)。
我們直接來(lái)看例子,假設(shè)文件中保存了每個(gè)省份的人口總數(shù),現(xiàn)在,需要求每個(gè)省份的人口占全國(guó)總?cè)丝诘谋壤o@然,我們需要先求出全國(guó)的總?cè)丝冢缓笤诒闅v每個(gè)省份的人口,用每個(gè)省的人口數(shù)除以總?cè)丝跀?shù),就得到了每個(gè)省份的人口占全國(guó)人口的比例。
如下所示:
def get_province_population(filename):
with open(filename) as f:
for line in f:
yield int(line)
gen = get_province_population('data.txt')
all_population = sum(gen)
#print all_population
for population in gen:
print population / all_population
執(zhí)行上面這段代碼,將不會(huì)有任何輸出,這是因?yàn)?,生成器只能遍歷一次。在我們執(zhí)行sum語(yǔ)句的時(shí)候,就遍歷了我們的生成器,當(dāng)我們?cè)俅伪闅v我們的生成器的時(shí)候,將不會(huì)有任何記錄。所以,上面的代碼不會(huì)有任何輸出。
可以不用一直保持此程序運(yùn)行啊,
Linux的話可以用低消耗的crontab 來(lái)自定義時(shí)間執(zhí)行此python命令即可。
提供個(gè)思路,可以使用python的apscheduler庫(kù)。
Python這門語(yǔ)言中,生成器毫無(wú)疑問(wèn)是最有用的特性之一。與此同時(shí),也是使用的最不廣泛的Python特
性之一。究其原因,主要是因?yàn)?,在其他主流語(yǔ)言里面沒(méi)有生成器的概念。正是由于生成器是一
個(gè)“新”的東西,所以,它一方面沒(méi)有引起廣大工程師的重視,另一方面,也增加了工程師的學(xué)習(xí)成本,
最終導(dǎo)致大家錯(cuò)過(guò)了Python中如此有用的一個(gè)特性。
我的這篇文章,希望通過(guò)簡(jiǎn)單易懂的方式,深入淺出地介紹Python的生成器,以改變“如此有用的特性卻
使用極不廣泛”的現(xiàn)象。本文的組織如下:在第1章,我們簡(jiǎn)單地介紹了Python中的迭代器協(xié)議;在本文
第2章,將會(huì)詳細(xì)介紹生成器的概念和語(yǔ)法;在第3章,將會(huì)給出一個(gè)有用的例子,說(shuō)明使用生成器的好
處;在本文最后,簡(jiǎn)單的討論了使用生成器的注意事項(xiàng)。
1. 迭代器協(xié)議
由于生成器自動(dòng)實(shí)現(xiàn)了迭代器協(xié)議,而迭代器協(xié)議對(duì)很多人來(lái)說(shuō),也是一個(gè)較為抽象的概念。所以,為了
更好的理解生成器,我們需要簡(jiǎn)單的回顧一下迭代器協(xié)議的概念。
1. 迭代器協(xié)議是指:對(duì)象需要提供next方法,它要么返回迭代中的下一項(xiàng),要么就引起一個(gè)
StopIteration異常,以終止迭代
2. 可迭代對(duì)象就是:實(shí)現(xiàn)了迭代器協(xié)議的對(duì)象
3. 協(xié)議是一種約定,可迭代對(duì)象實(shí)現(xiàn)迭代器協(xié)議,Python的內(nèi)置工具(如for循環(huán),sum,min,max函
數(shù)等)使用迭代器協(xié)議訪問(wèn)對(duì)象。
舉個(gè)例子:在所有語(yǔ)言中,我們都可以使用for循環(huán)來(lái)遍歷數(shù)組,Python的list底層實(shí)現(xiàn)是一個(gè)數(shù)組,所
以,我們可以使用for循環(huán)來(lái)遍歷list。如下所示:
for n in [1, 2, 3, 4]:
... print n
但是,對(duì)Python稍微熟悉一點(diǎn)的朋友應(yīng)該知道,Python的for循環(huán)不但可以用來(lái)遍歷list,還可以用來(lái)遍歷
文件對(duì)象,如下所示:
with open(‘/etc/passwd’) as f: # 文件對(duì)象提供迭代器協(xié)議
... for line in f: # for循環(huán)使用迭代器協(xié)議訪問(wèn)文件
... print line
...
為什么在Python中,文件還可以使用for循環(huán)進(jìn)行遍歷呢?這是因?yàn)?,在Python中,文件對(duì)象實(shí)現(xiàn)了迭代
器協(xié)議,for循環(huán)并不知道它遍歷的是一個(gè)文件對(duì)象,它只管使用迭代器協(xié)議訪問(wèn)對(duì)象即可。正是由于
Python的文件對(duì)象實(shí)現(xiàn)了迭代器協(xié)議,我們才得以使用如此方便的方式訪問(wèn)文件,如下所示:
f = open('/etc/passwd')
dir(f)
['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...'
2. 生成器
Python使用生成器對(duì)延遲操作提供了支持。所謂延遲操作,是指在需要的時(shí)候才產(chǎn)生結(jié)果,而不是立即產(chǎn)
生結(jié)果。這也是生成器的主要好處。
Python有兩種不同的方式提供生成器:
2017/11/6 如何更好地理解Python迭代器和生成器? - 知乎
2/5
1. 生成器函數(shù):常規(guī)函數(shù)定義,但是,使用yield語(yǔ)句而不是return語(yǔ)句返回結(jié)果。yield語(yǔ)句一次返回一
個(gè)結(jié)果,在每個(gè)結(jié)果中間,掛起函數(shù)的狀態(tài),以便下次重它離開(kāi)的地方繼續(xù)執(zhí)行
2. 生成器表達(dá)式:類似于列表推導(dǎo),但是,生成器返回按需產(chǎn)生結(jié)果的一個(gè)對(duì)象,而不是一次構(gòu)建一個(gè)
結(jié)果列表
2.1 生成器函數(shù)
我們來(lái)看一個(gè)例子,使用生成器返回自然數(shù)的平方(注意返回的是多個(gè)值):
def gensquares(N):
for i in range(N):
yield i ** 2
for item in gensquares(5):
print item,
使用普通函數(shù):
def gensquares(N):
res = []
for i in range(N):
res.append(i*i)
return res
for item in gensquares(5):
print item,
可以看到,使用生成器函數(shù)代碼量更少。
2.2 生成器表達(dá)式
使用列表推導(dǎo),將會(huì)一次產(chǎn)生所有結(jié)果:
squares = [x**2 for x in range(5)]
squares
[0, 1, 4, 9, 16]
將列表推導(dǎo)的中括號(hào),替換成圓括號(hào),就是一個(gè)生成器表達(dá)式:
squares = (x**2 for x in range(5))
squares next(squares)
next(squares)
1
next(squares)
4
list(squares)
[9, 16]
Python不但使用迭代器協(xié)議,讓for循環(huán)變得更加通用。大部分內(nèi)置函數(shù),也是使用迭代器協(xié)議訪問(wèn)對(duì)象
的。例如, sum函數(shù)是Python的內(nèi)置函數(shù),該函數(shù)使用迭代器協(xié)議訪問(wèn)對(duì)象,而生成器實(shí)現(xiàn)了迭代器協(xié)
2017/11/6 如何更好地理解Python迭代器和生成器? - 知乎
3/5
議,所以,我們可以直接這樣計(jì)算一系列值的和:
sum(x ** 2 for x in xrange(4))
而不用多此一舉的先構(gòu)造一個(gè)列表:
sum([x ** 2 for x in xrange(4)])
2.3 再看生成器
前面已經(jīng)對(duì)生成器有了感性的認(rèn)識(shí),我們以生成器函數(shù)為例,再來(lái)深入探討一下Python的生成器:
1. 語(yǔ)法上和函數(shù)類似:生成器函數(shù)和常規(guī)函數(shù)幾乎是一樣的。它們都是使用def語(yǔ)句進(jìn)行定義,差別在
于,生成器使用yield語(yǔ)句返回一個(gè)值,而常規(guī)函數(shù)使用return語(yǔ)句返回一個(gè)值
2. 自動(dòng)實(shí)現(xiàn)迭代器協(xié)議:對(duì)于生成器,Python會(huì)自動(dòng)實(shí)現(xiàn)迭代器協(xié)議,以便應(yīng)用到迭代背景中(如for
循環(huán),sum函數(shù))。由于生成器自動(dòng)實(shí)現(xiàn)了迭代器協(xié)議,所以,我們可以調(diào)用它的next方法,并且,
在沒(méi)有值可以返回的時(shí)候,生成器自動(dòng)產(chǎn)生StopIteration異常
3. 狀態(tài)掛起:生成器使用yield語(yǔ)句返回一個(gè)值。yield語(yǔ)句掛起該生成器函數(shù)的狀態(tài),保留足夠的信息,
以便之后從它離開(kāi)的地方繼續(xù)執(zhí)行
3. 示例
我們?cè)賮?lái)看兩個(gè)生成器的例子,以便大家更好的理解生成器的作用。
首先,生成器的好處是延遲計(jì)算,一次返回一個(gè)結(jié)果。也就是說(shuō),它不會(huì)一次生成所有的結(jié)果,這對(duì)于大
數(shù)據(jù)量處理,將會(huì)非常有用。
大家可以在自己電腦上試試下面兩個(gè)表達(dá)式,并且觀察內(nèi)存占用情況。對(duì)于前一個(gè)表達(dá)式,我在自己的電
腦上進(jìn)行測(cè)試,還沒(méi)有看到最終結(jié)果電腦就已經(jīng)卡死,對(duì)于后一個(gè)表達(dá)式,幾乎沒(méi)有什么內(nèi)存占用。
sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))
除了延遲計(jì)算,生成器還能有效提高代碼可讀性。例如,現(xiàn)在有一個(gè)需求,求一段文字中,每個(gè)單詞出現(xiàn)
的位置。
不使用生成器的情況:
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result
使用生成器的情況:
2017/11/6 如何更好地理解Python迭代器和生成器? - 知乎
4/5
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index
這里,至少有兩個(gè)充分的理由說(shuō)明 ,使用生成器比不使用生成器代碼更加清晰:
1. 使用生成器以后,代碼行數(shù)更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前
提下,代碼行數(shù)越少越好
2. 不使用生成器的時(shí)候,對(duì)于每次結(jié)果,我們首先看到的是result.append(index),其次,才是index。
也就是說(shuō),我們每次看到的是一個(gè)列表的append操作,只是append的是我們想要的結(jié)果。使用生成
器的時(shí)候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回
index。
這個(gè)例子充分說(shuō)明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,
理解了yield語(yǔ)句和return語(yǔ)句一樣,也是返回一個(gè)值。那么,就能夠理解為什么使用生成器比不使用生成
器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。
4. 使用生成器的注意事項(xiàng)
相信通過(guò)這篇文章,大家已經(jīng)能夠理解生成器的作用和好處。但是,還沒(méi)有結(jié)束,使用生成器,也有一點(diǎn)
注意事項(xiàng)。
我們直接來(lái)看例子,假設(shè)文件中保存了每個(gè)省份的人口總數(shù),現(xiàn)在,需要求每個(gè)省份的人口占全國(guó)總?cè)丝?/p>
的比例。顯然,我們需要先求出全國(guó)的總?cè)丝冢缓笤诒闅v每個(gè)省份的人口,用每個(gè)省的人口數(shù)除以總?cè)?/p>
口數(shù),就得到了每個(gè)省份的人口占全國(guó)人口的比例。
如下所示:
def get_province_population(filename):
with open(filename) as f:
for line in f:
yield int(line)
gen = get_province_population('data.txt')
all_population = sum(gen)
#print all_population
for population in gen:
print population / all_population
執(zhí)行上面這段代碼,將不會(huì)有任何輸出,這是因?yàn)?,生成器只能遍歷一次。在我們執(zhí)行sum語(yǔ)句的時(shí)候,
就遍歷了我們的生成器,當(dāng)我們?cè)俅伪闅v我們的生成器的時(shí)候,將不會(huì)有任何記錄。所以,上面的代碼不
會(huì)有任何輸出。
因此,生成器的唯一注意事項(xiàng)就是:生成器只能遍歷一次。
5. 總結(jié)
2017/11/6 如何更好地理解Python迭代器和生成器? - 知乎
5/5
本文深入淺出地介紹了Python中,一個(gè)容易被大家忽略的重要特性,即Python的生成器。為了講解生成
器,本文先介紹了迭代器協(xié)議,然后介紹了生成器函數(shù)和生成器表達(dá)式,并通過(guò)示例演示了生成器的優(yōu)點(diǎn)
和注意事項(xiàng)。在實(shí)際工作中,充分利用Python生成器,不但能夠減少內(nèi)存使用,還能夠提高代碼可讀性。
掌握生成器也是Python高手的標(biāo)配。希望本文能夠幫助大家理解Python的生成器
許多Django應(yīng)用需要執(zhí)行異步任務(wù), 以便不耽誤http request的執(zhí)行. 我們也可以選擇許多方法來(lái)完成異步任務(wù), 使用Celery是一個(gè)比較好的選擇, 因?yàn)镃elery
有著大量的社區(qū)支持, 能夠完美的擴(kuò)展, 和Django結(jié)合的也很好. Celery不僅能在Django中使用, 還能在其他地方被大量的使用. 因此一旦學(xué)會(huì)使用Celery, 我
們可以很方便的在其他項(xiàng)目中使用它.
1. Celery版本
本篇博文主要針對(duì)Celery 3.0.x. 早期版本的Celery可能有細(xì)微的差別.
2. Celery介紹
Celery的主要用處是執(zhí)行異步任務(wù), 可以選擇延期或定時(shí)執(zhí)行功能. 為什么需要執(zhí)行異步任務(wù)呢?
第一, 假設(shè)用戶正發(fā)起一個(gè)request, 并等待request完成后返回. 在這一request后面的view功能中, 我們可能需要執(zhí)行一段花費(fèi)很長(zhǎng)時(shí)間的程序任務(wù), 這一時(shí)間
可能遠(yuǎn)遠(yuǎn)大于用戶能忍受的范圍. 當(dāng)這一任務(wù)并不需要立刻執(zhí)行時(shí), 我們便可以使用Celery在后臺(tái)執(zhí)行, 而不影響用戶瀏覽網(wǎng)頁(yè). 當(dāng)有任務(wù)需要訪問(wèn)遠(yuǎn)程服務(wù)器完
成時(shí), 我們往往都無(wú)法確定需要花費(fèi)的時(shí)間.
第二則是定期執(zhí)行某些任務(wù). 比如每小時(shí)需要檢查一下天氣預(yù)報(bào), 然后將數(shù)據(jù)儲(chǔ)存到數(shù)據(jù)庫(kù)中. 我們可以編寫這一任務(wù), 然后讓Celery每小時(shí)執(zhí)行一次. 這樣我們
的web應(yīng)用便能獲取最新的天氣預(yù)報(bào)信息.
我們這里所講的任務(wù)task, 就是一個(gè)Python功能(function). 定期執(zhí)行一個(gè)任務(wù)可以被認(rèn)為是延時(shí)執(zhí)行該功能. 我們可以使用Celery延遲5分鐘調(diào)用function
task1, 并傳入?yún)?shù)(1, 2, 3). 或者我們也可以每天午夜運(yùn)行該function.
我們偏向于將Celery放入項(xiàng)目中, 便于task訪問(wèn)統(tǒng)一數(shù)據(jù)庫(kù)和Django設(shè)置.
當(dāng)task準(zhǔn)備運(yùn)行時(shí), Celery會(huì)將其放入列隊(duì)queue中. queue中儲(chǔ)存著可以運(yùn)行的task的list. 我們可以使用多個(gè)queue, 但為了簡(jiǎn)單, 這里我們只使用一個(gè).
將任務(wù)task放入queue就像加入todo list一樣. 為了使task運(yùn)行, 我們還需要在其他線程中運(yùn)行的苦工worker. worker實(shí)時(shí)觀察著代運(yùn)行的task, 并逐一運(yùn)行這
些task. 你可以使用多個(gè)worker, 通常他們位于不同服務(wù)器上. 同樣為了簡(jiǎn)單起見(jiàn), 我們這只是用一個(gè)worker.
我們稍后會(huì)討論queue, worker和另外一個(gè)十分重要的進(jìn)程, 接下來(lái)我們來(lái)動(dòng)動(dòng)手:
3. 安裝Celery
我們可以使用pip在vietualenv中安裝:
pip install django-celery
4. Django設(shè)置
我們暫時(shí)使用django runserver來(lái)啟動(dòng)celery. 而Celery代理人(broker), 我們使用Django database broker implementation. 現(xiàn)在我們只需要知道Celery
需要broker, 使用django自身便可以充當(dāng)broker. (但在部署時(shí), 我們最好使用更穩(wěn)定和高效的broker, 例如Redis.)
在settings.py中:
import djcelery
djcelery.setup_loader()
BROKER_URL = 'django://'
...
INSTALLED_APPS = (
...
'djcelery',
'kombu.transport.django',
...
)
第一二項(xiàng)是必須的, 第三項(xiàng)則告訴Celery使用Django項(xiàng)目作為broker.
在INSTALLED_APPS中添加的djcelery是必須的. kombu.transport.django則是基于Django的broker
最后創(chuàng)建Celery所需的數(shù)據(jù)表, 如果使用South作為數(shù)據(jù)遷移工具, 則運(yùn)行:
python manage.py migrate
否則運(yùn)行: (Django 1.6或Django 1.7都可以)
python manage.py syncdb
5. 創(chuàng)建一個(gè)task
正如前面所說(shuō)的, 一個(gè)task就是一個(gè)Pyhton function. 但Celery需要知道這一function是task, 因此我們可以使用celery自帶的裝飾器decorator: @task. 在
django app目錄中創(chuàng)建taske.py:
from celery import task
@task()
def add(x, y):
return x + y
當(dāng)settings.py中的djcelery.setup_loader()運(yùn)行時(shí), Celery便會(huì)查看所有INSTALLED_APPS中app目錄中的tasks.py文件, 找到標(biāo)記為task的function, 并
將它們注冊(cè)為celery task.
將function標(biāo)注為task并不會(huì)妨礙他們的正常執(zhí)行. 你還是可以像平時(shí)那樣調(diào)用它: z = add(1, 2).
6. 執(zhí)行task
讓我們以一個(gè)簡(jiǎn)單的例子作為開(kāi)始. 例如我們希望在用戶發(fā)出request后異步執(zhí)行該task, 馬上返回response, 從而不阻塞該request, 使用戶有一個(gè)流暢的訪問(wèn)
過(guò)程. 那么, 我們可以使用.delay, 例如在在views.py的一個(gè)view中:
from myapp.tasks import add
...
add.delay(2, 2)
...
Celery會(huì)將task加入到queue中, 并馬上返回. 而在一旁待命的worker看到該task后, 便會(huì)按照設(shè)定執(zhí)行它, 并將他從queue中移除. 而worker則會(huì)執(zhí)行以下代
碼:
import myapp.tasks.add
myapp.tasks.add(2, 2)
7. 關(guān)于import
這里需要注意的是, 在impprt task時(shí), 需要保持一致. 因?yàn)樵趫?zhí)行djcelery.setup_loader()時(shí), task是以INSTALLED_APPS中的app名,
加.tasks.function_name注冊(cè)的, 如果我們由于python path不同而使用不同的引用方式時(shí)(例如在tasks.py中使用from myproject.myapp.tasks import
add形式), Celery將無(wú)法得知這是同一task, 因此可能會(huì)引起奇怪的bug.
8. 測(cè)試
a. 啟動(dòng)worker
正如之前說(shuō)到的, 我們需要worker來(lái)執(zhí)行task. 以下是在開(kāi)發(fā)環(huán)境中的如何啟動(dòng)worker:
首先啟動(dòng)terminal, 如同開(kāi)發(fā)django項(xiàng)目一樣, 激活virtualenv, 切換到django項(xiàng)目目錄. 然后啟動(dòng)django自帶web服務(wù)器: python manage.py runserver.
然后啟動(dòng)worker:
python manage.py celery worker --loglevel=info
此時(shí), worker將會(huì)在該terminal中運(yùn)行, 并顯示輸出結(jié)果.
b. 啟動(dòng)task
打開(kāi)新的terminal, 激活virtualenv, 并切換到django項(xiàng)目目錄:
$ python manage.py shell
from myapp.tasks import add
add.delay(2, 2)
此時(shí), 你可以在worker窗口中看到worker執(zhí)行該task:
[2014-10-07 08:47:08,076: INFO/MainProcess] Got task from broker: myapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc]
[2014-10-07 08:47:08,299: INFO/MainProcess] Task myapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc] succeeded in 0.183349132538s: 4
9. 另一個(gè)例子
下面我們來(lái)看一個(gè)更為真實(shí)的例子, 在views.py和tasks.py中:
# views.py
from myapp.tasks import do_something_with_form_data
def view(request):
form = SomeForm(request.POST)
if form.is_valid():
data = form.cleaned_data
# Schedule a task to process the data later
do_something_with_form_data.delay(data)
return render_to_response(...)
# tasks.py
@task
def do_something_with_form_data(data):
call_slow_web_service(data['user'], data['text'], ...)
10. 調(diào)試
由于Celery的運(yùn)行需要啟動(dòng)多個(gè)部件, 我們可能會(huì)漏掉一兩個(gè). 所以我們建議:
使用最簡(jiǎn)單的設(shè)置
使用python debug和logging功能顯示當(dāng)前的進(jìn)程
11. Eager模式
如果在settings.py設(shè)置:
CELERY_ALWAYS_EAGER = True
那么Celery便以eager模式運(yùn)行, 則task便不需要加delay運(yùn)行:
# 若啟用eager模式, 則以下兩行代碼相同
add.delay(2, 2)
add(2, 2)
12. 查看queue
因?yàn)槲覀兪褂昧薲jango作為broker, queue儲(chǔ)存在django的數(shù)據(jù)庫(kù)中. 這就意味著我們可以通過(guò)django admin查看該queue:
# admin.py
from django.contrib import admin
from kombu.transport.django import models as kombu_models
admin.site.register(kombu_models.Message)
13. 檢查結(jié)果
每次運(yùn)行異步task后, Celery都會(huì)返回AsyncResult對(duì)象作為結(jié)果. 你可以將其保存, 然后在將來(lái)查看該task是否運(yùn)行成功和返回結(jié)果:
# views.py
result = add.delay(2, 2)
...
if result.ready():
print "Task has run"
if result.successful():
print "Result was: %s" % result.result
else:
if isinstance(result.result, Exception):
print "Task failed due to raising an exception"
raise result.result
else:
print "Task failed without raising exception"
else:
print "Task has not yet run"
14. 定期任務(wù)
還有一種Celery的常用模式便是執(zhí)行定期任務(wù). 執(zhí)行定期任務(wù)時(shí), Celery會(huì)通過(guò)celerybeat進(jìn)程來(lái)完成. Celerybeat會(huì)保持運(yùn)行, 一旦到了某一定期任務(wù)需要執(zhí)
行時(shí), Celerybeat便將其加入到queue中. 不像worker進(jìn)程, Celerybeat只有需要一個(gè)即可.
啟動(dòng)Celerybeat:
python manage.py celery beat
使Celery運(yùn)行定期任務(wù)的方式有很多種, 我們先看第一種, 將定期任務(wù)儲(chǔ)存在django數(shù)據(jù)庫(kù)中. 即使是在django和celery都運(yùn)行的狀態(tài), 這一方式也可以讓我們
方便的修改定期任務(wù). 我們只需要設(shè)置settings.py中的一項(xiàng)便能開(kāi)啟這一方式:
# settings.py
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
Python中的sleep函數(shù)可以傳小數(shù)進(jìn)去,就可以進(jìn)行毫秒級(jí)的延時(shí)了,代碼如下:
#?例1:循環(huán)輸出休眠1秒
import?time
i?=?1
while?i?lt;=?3:
print?i?#?輸出i
i?+=?1
time.sleep(1)?#?休眠1秒
#?例2:循環(huán)輸出休眠100毫秒
import?time
i?=?1
while?i?lt;=?3:
print?i?#?輸出i
i?+=?1
time.sleep(0.1)?#?休眠0.1秒