生活實例說明什麼是遞歸和迭代python
A想去騰達大廈,問B怎麼走路,B 說我不知道,我給你問問C,C也不知道,C又去問D,D知道,把路告訴了C,C又告訴B,B最後告訴A, 這就是遞歸。git
A想去騰達大廈,問B怎麼走,B說我不知道,可是C知道,你本身去問C吧,C說我要不知道, 可是D知道,你本身去問D吧,這就是迭代。編程
人類繁衍也是一種迭代,先有父,纔有兒,有兒纔有孫。api
每次循環獲得的結果都是依賴於上一次獲得的結果而來的,這叫迭代。併發
迭代是一個重複的過程,每次重複即一次迭代,而且每次迭代的結果都是下一次迭代的初始值app
1. 迭代器協議是指:對象必須提供一個next方法,執行該方法要麼返回迭代中的下一項,要麼就引發一個StopIteration異常,以終止迭代(只能日後走不能往前退)ssh
2.可迭代對象:實現了迭代器協議的對象(如何實現:對象內部定義一個__iter__()方法,就是可迭代對象。ide
3.協議是一種約定,可迭代對象實現了迭代器協議,python()的內部工具(如for循環,sum,min,max函數等)遵循迭代器協議訪問對象的。函數
4. 迭代器就能夠可迭代對象,他兩是一會事兒工具
爲什麼要有迭代器?什麼是可迭代對象?什麼是迭代器對象?
#一、爲什麼要有迭代器? 對於序列類型:字符串、列表、元組,咱們可使用索引的方式迭代取出其包含的元素。但對於字典、集合、文件等類型是沒有索引的,若還想取出其內部包含的元素,
則必須找出一種不依賴於索引的迭代方式,這就是迭代器 #二、什麼是可迭代對象? 可迭代對象指的是內置有__iter__方法的對象,即obj.__iter__,以下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #三、什麼是迭代器對象? 可迭代對象執行obj.__iter__()獲得的結果就是迭代器對象 而迭代器對象指的是即內置有__iter__又內置有__next__方法的對象 文件類型是迭代器對象 open('a.txt').__iter__() open('a.txt').__next__() #四、注意: 迭代器對象必定是可迭代對象,而可迭代對象不必定是迭代器對象
迭代器對象的使用
dic={'a':1,'b':2,'c':3} iter_dic=dic.__iter__() #獲得迭代器對象,迭代器對象即有__iter__又有__next__,可是:迭代器.__iter__()獲得的仍然是迭代器自己 iter_dic.__iter__() is iter_dic #True print(iter_dic.__next__()) #等同於next(iter_dic) print(iter_dic.__next__()) #等同於next(iter_dic) print(iter_dic.__next__()) #等同於next(iter_dic) # print(iter_dic.__next__()) #拋出異常StopIteration,或者說結束標誌 #有了迭代器,咱們就能夠不依賴索引迭代取值了 iter_dic=dic.__iter__() while 1: try: k=next(iter_dic) print(dic[k]) except StopIteration: break #這麼寫太醜陋了,須要咱們本身捕捉異常,控制next,python這麼牛逼,能不能幫我解決呢?能,請看for循環
# l 裏的全部內容都放在內存裏了 l = [1,2,3,4,5] # 將列表轉換成迭代器,只獲得了一個迭代器對象,就是個內存地址,iter_l是能夠節省內存的 iter_l = l.__iter__() # 只要有了iter_l 這個迭代器,或者可迭代對象,在任何地方均可以傳輸的 print(iter_l.__next__()) # 1 print(iter_l.__next__()) # 2 print(iter_l.__next__()) # 3 print(iter_l.__next__()) # 4 print(iter_l.__next__()) # 5 print(iter_l.__next__()) # StopIteration # next()就是在調用iter_l.__next__()的方法,二者實際上是一回事。 # 只不過next()是Python內置的函數方法;而__next__()是數據類型內置的方法。 print(next(iter_l)) # 1 print(next(iter_l)) # 2 print(next(iter_l)) # 3 print(next(iter_l)) # 4 print(next(iter_l)) # 5 print(next(iter_l)) # StopIteration
for 循環的本質:循環全部對象,全都是使用迭代器協議。
for循環的本質就是遵循迭代器協議訪問對象,那麼for循環的對象確定都是迭代器了啊,那既然這樣,for循環能夠遍歷(字符串,列表,元組,字典,集合,文件對象),那麼這些類型的數據確定都是可迭代對象啊?可是,爲何定義了一個列表 l = [1,2,3]卻沒有next()方法?打臉嗎?
正本清源:
字符串,列表,元組,字典,集合,文件對象,這些都不是可迭代對象,只不過在for循環時,調用了他們內部的__iter__方法,把他們變成了可迭代對象。
而後for循環調用可迭代對象的__next__方法去取值,並且for循環會捕捉StopIteration異常,以終止迭代。
x = 'hello' print(dir(x)) 結果,x 字符串沒有 next()方法,說明字符串沒有遵循迭代器協議,那字符串就不是可迭代對象,同理,列表,元組,字典,集合等都不是可迭代對象。 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] 那既然這些都不是可迭代對象,爲何for循環能夠遍歷那? 仍是以x字符串爲例說明: # 1. 字符串x先調用內部方法__iter__(),把x變成可迭代對象。 iter_test = x.__iter__() # 2. x已經變成了可迭代對象,那麼就能夠進行遍歷了,以下。 print(iter_test.__next__()) # h print(iter_test.__next__()) # e print(iter_test.__next__()) # l print(iter_test.__next__()) # l print(iter_test.__next__()) #0 # 3. 再往下就沒有了,生出不兒子了,StopIteration異常了 print(iter_test.__next__()) 再回到for循環,理解一下原理 l = [1,2,3] # 1. 先把列表 l變成一個可迭代對象,讓他遵循迭代器協議,即l.__iter__(),而後把這個放在了for循環的l的位置。 # 2. 而後 for循環執行可迭代對象next方法進行遍歷。 # 3. 上面字符串的x當next找不到對象的時候,就StopItertion異常,而 for 循環碰到 StopIteration時,就捕捉到了該異常,知道已經遍歷完了。 for i in l: # i_l = l.__iter__(), --> i_l.__next__() print(i) 到目前爲止,列表的幾種取值方式: 1. 索引方式:print(l[0)) 2. 遵循迭代器協議訪問方式,生成可迭代對象,iter_l = l.__iter__() ,而後就能夠調用__next__()方法取值了 print(iter_l.__next__()) print(iter_l.__next__()) 3. for循環方法方式 能夠用索引的方式遍歷列表嗎? 固然能夠。 index = 0 while index < len(l): print(l[index]) index += 1
#基於for循環,咱們能夠徹底再也不依賴索引去取值了 dic={'a':1,'b':2,'c':3} for k in dic: print(dic[k]) #for循環的工做原理 #1:執行in後對象的dic.__iter__()方法,獲得一個迭代器對象iter_dic #2: 執行next(iter_dic),將獲得的值賦值給k,而後執行循環體代碼 #3: 重複過程2,直到捕捉到異常StopIteration,結束循環
爲什麼要有for循環?
基於上面講的列表有三種訪問方式,其實表明着 序列類型(字符串,列表,元組)均可以用上述的三種方式任意一種訪問,你能夠不用for循環訪問,可是非序列類型(字典,集合,文件對象)呢?他們沒有下標,不用for循環怎麼遍歷?因此,for循環就是基於迭代器協議提供了一個統一的能夠遍歷全部對象的方法,即在遍歷以前,先調用__iter__方法,將要遍歷的對象轉換成一個迭代器,而後使用迭代器協議去實現循環訪問,這樣全部的對象就均可以經過for循環來遍歷了,並且你看到的效果也確實如此,這就是無所不能強大至極的for循環。
#優勢: - 提供一種統一的、不依賴於索引的迭代方式 - 惰性計算,節省內存 #缺點: - 沒法獲取長度(只有在next完畢才知道到底有幾個值) - 一次性的,只能日後走,不能往前退
能夠理解爲一種數據類型,這種數據類型自動實現了迭代器協議(其餘的數據類型須要調用本身內置的__iter__方法),因此生成器就是可迭代對象
好比,列表要轉換成遵循迭代器協議,須要 先調用__iter__()方法後,才能使用.__next__()方法
而 生成器不用先執行__iter__()方法,就意味着生成器能夠直接調用__next__()方法
#只要函數內部包含有yield關鍵字,那麼函數名()獲得的結果就是生成器,而且不會執行函數內部代碼 def func(): print('====>first') yield 1 print('====>second') yield 2 print('====>third') yield 3 print('====>end') g=func() print(g) #<generator object func at 0x0000000002184360>
# 生成器就是迭代器 g.__iter__ g.__next__ #二、因此生成器就是迭代器,所以能夠這麼取值 res=next(g) print(res)
生成器分類及在Python中的表現形式:(Python裏有兩種不一樣的方式提供生成器)
1. 生成器表達式:相似於列表推導,可是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
(1)三元表達式
想知道生成器表達式,得先知道三元表達式
# 三元表達式 name = 'mamingc' res = 'good' if name == 'mamingc' else 'bad' print(res) # good # 什麼是三元表達式?對上面的三元表達式進行解析 # 其實質仍是個if條件判斷,只不過書寫形式不一樣於常規的if判斷語句。 # 若是name == 'mamingc',則該條件爲真,那麼執行條件爲真的語句,可是這個語句須要放到if的前面; # 若是條件不爲真,則執行else語句,else的語句緊接着else後面寫 # 而後將該條語句賦值給一個變量,這就就是三元表達式 # # 三元表達式轉換成常規的if語句以下: name = 'lizhi' if name == 'lizhi': print("good") else: print("bad")
(2)列表解析
# 列表解析 l = ['雞蛋%s' %i for i in range(10)] l1 = [i for i in range(10)] print(l) # ['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9'] print(l1) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 什麼是列表解析?對上面的列表解析進行解析: # 首先,得有一個列表,for i in range(10) 在列表內進行了循環, # 其次:循環獲得的結果常規方法是append給了egg_list列表,而列表解析獲得的結果則直接放到了for i in range(10)的前面 # 最後:將該語句賦值給一個變量 # 這就是列表解析 # 列表解析還能夠這麼玩 l2 = ['雞蛋%s' %i for i in range(10) if i > 5] print(l2) # ['雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9'] # 一元:主體一個for循環:for i in range(10) # 二元:雞蛋的處理:'雞蛋%s' %i # 三元:來一個if判斷:if i > 5
# 列表解析:若是要處理的數據量很大,那就很是耗內存,由於仍是要先生成一個列表,因此處理大量數據則須要用到生成器表達式。
(3)真正的生成器表達式來啦
# 生成器表達式 # 生成器表達式直接就是迭代器,就不要再費勁的先調用__iter__了 laomuji = ('雞蛋%s' %i for i in range(10)) # 注意,把列表解析的[]換成(),就變成了生成器表達式 print(laomuji) # <generator object <genexpr> at 0x015A88A0> print(laomuji.__next__()) # 雞蛋0 print(laomuji.__next__()) # 雞蛋1 print(next(laomuji)) # 雞蛋2 # next本質就是調用__next__
# 生成器表達式,自動實現了迭代器協議,就意味着下面確定有一個next()方法,能實現next()纔是生成器的核心。
生成器表達式總結:
1. 把列表解析的[]換成(),獲得的就是生成器表達式
2. 列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3. Python使用迭代器協議,讓for循環變得更加通用。大部份內置函數,也可使用迭代器協議訪問對象的。
例如,sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,因此,咱們能夠直接這樣計算一系列值的和
print(sum(x**2 for x in range(4))) # 14
而不用畫蛇添足的先構造一個列表:
print(sum([x **2 for x in range(4)])) # 14
# 這麼大的一個數,若是用列表的方式進行計算,電腦極可能會卡死 # 可是經過迭代器的方式計算,計算出結果的時間耗時比較久,可是電腦此時還能正常切換使用。這就能體現出省內存了。 import time localtime = time.asctime( time.localtime(time.time()) ) print("開始時本地時間爲 :", localtime) # 開始時本地時間爲 : Sat Jun 16 22:32:41 2018 print(sum(i for i in range(100000000000000000000000))) # print(sum(i for i in range(100000))) print("結束時本地時間爲 :",localtime) # 結束時本地時間爲 :
2. 生成器函數:常規函數定義,可是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每一個結果中間,掛起函數的狀態,以便下次從它離開的地方繼續執行。
yield至關於return,return只能執行一個,執行一次,函數就退出了,而yield能夠執行屢次。
生成器函數的特性:
1. 有yield就行,yield的功能,至關於return,用來作返回值的
2. yield能夠用來保留函數的運行狀態,函數一運行到yield返回一個值,就停住了;若是須要再運行一次,就要再觸發一次next,並且下一次的運行是從上一次next終止的位置開始運行的,而不是從函數的開頭從新運行的。
3. 若是把函數寫成生成器形式了,它有一個最大的優勢:
def test(): yield 1 yield 2 yield 3 g=test() # 執行函數,有返回值,想要拿到函數的運行結果的返回值,把運行結果的返回值賦值給變量。test()只是至關於拿到了一個生成器 print('來自函數',g) # 來自函數 <generator object test at 0x00DACD50> 生成器對象 print(g.__next__()) # 1 print(g.__next__()) # 2 print(next(g)) # 3 print(g.__next__()) # StopIteration
import time def test(): print('開始生孩子啦。。。。。。') print('開始生孩子啦。。。。。。') print('開始生孩子啦。。。。。。') yield '我' # 至關於return time.sleep(3) print('開始生兒子啦') yield '兒子' time.sleep(3) print('開始生孫子啦') yield '孫子' res=test() print(res) # 結果 <generator object test at 0x017A09C0> print(res.__next__()) #test() # 結果 開始生孩子啦。。。。。。 開始生孩子啦。。。。。。 開始生孩子啦。。。。。。 我 print(res.__next__()) #test() # 結果 開始生兒子啦 兒子 print(next(res)) #test() # 結果 開始生孫子啦 孫子
# 去慶豐吃包子 # 第一種形式,return方式,一次性把100個包子都作好了,纔給客人吃。哪怕你來了多少人,都得等我這個100個包子都所有作好 # 了纔給客人吃 def product_baozi(): ret=[] for i in range(100): ret.append('一屜包子%s' %i) return ret baozi_list=product_baozi() print(baozi_list) # 第二種形式,用yield方式,作一屜包子,來一我的就能夠賣一屜,作好了就能夠立馬賣了。 # 就至關於來了客人,客人何時要,飯店就能夠何時賣;而不是等全部包子都作好了纔開始賣, # 這麼比較,yield就效率會高一些。 def product_baozi(): for i in range(100): print('正在生產包子') yield '一屜包子%s' %i #i=1 print('開始賣包子') pro_g=product_baozi() baozi1=pro_g.__next__() # 結果正在生產包子 運行到 yield '一屜包子%s' %i 就停住了,
baozi1=pro_g.__next__() # 下一次next,就從上一次停住的位置開始運行,獲得下面的結果 # 結果 開始賣包子 正在生產包子
## 下蛋的傳說 ## def xiadan(): ret=[] for i in range(10000): ret.append('雞蛋%s' %i) return ret print(xiadan()) # return方式 #缺點1:佔空間大,須要先找個籃子,把全部10000個蛋都先放到籃子裏 #缺點2:效率低 def xiadan(): for i in range(5): yield '雞蛋%s' %i # 生成器函數的好處呢,就不須要先把全部的蛋都放到一個籃子了,而是來一我的,下一個蛋你拿走就是了。 alex_lmj=xiadan() # 來一我的,alex就給你下一個蛋你拿走 print(alex_lmj.__next__()) # 後面沒人來拿雞蛋,alex就不用下蛋了 # 結果 雞蛋0 # 當再來一我的,alex再下一個蛋你拿走,而不須要等着了。 print(alex_lmj.__next__()) # 結果 雞蛋1
爲啥使用生成器,生成器的優勢:
Python使用生成器對延遲操做提供了支持。所謂延遲操做,是指在須要的時候才產生結果,而不是當即產生結果。這也是生成器的主要好處。
生成器總結:
1. 是可迭代對象
2. 實現了延遲計算,省內存那
3. 生成器本質和其餘的數據類型同樣,都實現了迭代器協議,只不過生成器附加了一個延遲計算省內存的好處,其他的可迭代對象可沒有這點好處,記住哦!!
綜上已經對生成器有了必定的認識,下面以生成器函數爲例進行總結。
生成器遵循迭代器協議,是可迭代對象。 生成器就是迭代器原理實際上是這個,可是不能說。
優勢一:生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成全部的結果,這對於大數據量處理,將會很是有用。
# 列表解析 sum([i for i in range(10000000)]) # 內存佔用大,機器容易卡死 # 生成器表達式 sum(i for i in range(1000000)) # 幾乎不佔內存
優勢二:生成器還能有效提升代碼可讀性
# 用前面下蛋的傳說例子很能說明問題 # 首先,普通的return方式,實現功能用了5行 # 而,生成器函數則3行就實現了功能。
這裏,至少有兩個充分的理由說明,使用生成器比不使用生成器代碼更加清晰:
這例子充分說明,合理使用生成器,可以有效提升代碼可讀性。只要徹底接受了生成器的概念,理解了yield語句和return語句同樣,也返回一個值。那麼,就能理解爲何使用生成器比不使用生成器更好,可以理解使用生成器真的可讓代碼變得更清晰易懂。
注意事項:生成器只能遍歷一次(母雞一輩子只能下必定數量的蛋,下完就不下了或者死掉了)
人口普查文件: {'name':'北京','population':10} {'name':'山東','population':1000000} {'name':'山西','population':31} {'name':'河北','population':3110330000} {'name':'臺灣','population':311030330}
# 需求:提取出每一個省的人口數 及 總人口數
# 分析過程 def get_population(): with open('人口普查', 'r', encoding= 'utf-8') as f: for i in f: yield i g = get_population() # print(g.__next__()) # {'name':'北京','population':10}, # print(type(g.__next__())) # <class 'str'> # print(g.__next__()['population']) # 返回的是一行文件的記錄,文件記錄全都是字符串,因此會報TypeError: string indices must be integers # 前面內置函數學過一個eval()函數,能夠把字符串內的數據類型進行運算,提取出數據類型 sheng1 = eval(g.__next__()) print(sheng1) # {'name': '北京', 'population': 10} print(type(sheng1)) # <class 'dict'> # 獲取總人口 all_pop = sum(eval(i)['population'] for i in g) print(all_pop)
# 完整的代碼及結果 def get_population(): with open('人口普查', 'r', encoding= 'utf-8') as f: for i in f: yield i g = get_population() print("北京人口總數 %s " %eval(g.__next__())['population']) print("山西人口總數 %s " %eval(g.__next__())['population']) print("山東人口總數 %s " %eval(g.__next__())['population']) print("河北人口總數 %s " %eval(g.__next__())['population']) print("臺灣人口總數 %s " %eval(g.__next__())['population']) # 結果 北京人口總數 2000 山西人口總數 100 山東人口總數 310 河北人口總數 210 臺灣人口總數 110 # 獲取總人口 all_pop = sum(eval(i)['population'] for i in g) print(all_pop) # 2730
生產者及消費者模型
# 之前可能會這麼作 import time def producer(): ret=[] for i in range(100): time.sleep(0.1) ret.append('包子%s' %i) return ret def consumer(res): for index,baozi in enumerate(res): time.sleep(0.1) print('第%s我的,吃了%s' %(index,baozi)) res=producer() consumer(res)
先學習三種觸發生成器函數往下走的方法
#yield 3至關於return 控制的是函數的返回值 # yield的另一個特性,接受send傳過來的值,賦值給x, x=yield def test(): print('開始啦') yield 1 #return 1 first=None print('第一次') yield 2 print('第二次') third = yield print('第三次',third) yield 3 print('第四次') t=test() # 第一種:直接調生成器下面的__next__()方法 res1=t.__next__() # 第一次觸發yield 後就停住了 print(res1) # 結果 # 開始啦 # 1 # yield 1的返回值 # 第二種:用系統提供的內置函數next()函數+生成器函數 res2 = next(t) # 第二次觸發yeild 往下繼續運行, 運行到yield 2 後就停住了 print(res2) # 結果 # 第一次 # 從 yield 1 後面開始運行 # 2 # yield 2 的返回值 # 第三種:生成器下面的send()方法,功能同next,均可以讓函數往下走 # 同時,send 還有一個功能,就是能夠傳遞一個參數給yield,進行觸發,保證函數可以繼續往下走。 res=t.send(None) print(res) # 結果 # 第二次 # None res3=t.send('函數停留在third那個位置,我就是給third賦值的') # 第三次經過send觸發生成器繼續運行 print(res3) # 運行到 third = yield 時,停住了,沒有返回值,因此返回None # 結果 # 第三次 函數停留在first那個位置,我就是給first賦值的 # 3
怎麼實現併發的效果?
兩個程序同時在等,A處理完了跳到B,B處理完了再跳到B,兩個沒有前後順序
消費者生產者模型 # 消費者 def consumer(name): print('我是[%s],我準備開始吃包子了' %name) while True: # 死循環 baozi=yield # time.sleep(1) print('%s 很開心的把【%s】吃掉了' %(name,baozi)) # 生產者 def producer(): c1=consumer('wusir') c2=consumer('yunyuan') c1.__next__() c2.__next__() for i in range(10): time.sleep(1) c1.send('包子 %s' %i) c2.send('包子 %s' %i) producer()