Python for循環(huán)可以遍歷任何可序列的對象,格式是:for … in …
names = ['Michael', 'Bob', 'Tracy']for name in names: print(name) #執(zhí)行這段代碼,會依次打印names的每一個元素:MichaelBobTracysum = 0for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: sum = sum + xprint(sum)#如果要計算1-100的整數(shù)之和,從1寫到100有點困難,Python提供一個range()函數(shù),可以生成一個整數(shù)序列sum = 0for x in range(100): sum = sum + xprint(sum)
Python字典,dict
全稱dictionary,在其他語言中也稱為map,使用鍵-值(key-value)
存儲,具有極快的查找速度。
這種key-value
存儲方式,在放進去的時候,必須根據(jù)key
算出value
的存放位置,這樣取的時候才能根據(jù)key
直接拿到value
;
請務(wù)必注意,dict
內(nèi)部存放的順序和key
放入的順序是沒有關(guān)系的,雖然我們循環(huán)字典的時候,貌似是按照字典寫入順序輸出的,這是因為Python3在內(nèi)部為我們做了優(yōu)化,Python2 可就不好說了,尤其是當(dāng)字典存儲大量數(shù)據(jù)時。
和list
比較,dict
有以下幾個特點:
查找和插入的速度極快,不會隨著key的增加而變慢;需要占用大量的內(nèi)存,內(nèi)存浪費多。
而list相反:
查找和插入的時間隨著元素的增加而增加;占用空間小,浪費內(nèi)存很少。
所以,dict是用空間來換取時間的一種方法。
dict可以用在需要高速查找的很多地方,在Python代碼中幾乎無處不在,正確使用dict非常重要,需要牢記的第一條就是dict的key必須是不可變對象。
這是因為dict根據(jù)key來計算value的存儲位置,如果每次計算相同的key得出的結(jié)果不同,那dict內(nèi)部就完全混亂了。這個通過key計算位置的算法稱為哈希算法(Hash)。
要保證hash的正確性,作為key的對象就不能變。在Python中,字符串、整數(shù)等都是不可變的,因此,可以放心地作為key。而list是可變的,就不能作為key
#循環(huán)遍歷字典的key>>> a={'a': '1', 'b': '2', 'c': '3'}>>> for key in a: print(key+':'+a[key])a:1b:2c:3>>> for key in a.keys(): print(key+':'+a[key])a:1b:2c:3#循環(huán)遍歷字典的value>>> for value in a.values(): print(value)123#循環(huán)遍歷字典項>>> a={'a': '1', 'b': '2', 'c': '3'}>>> for kv in a.items():... print(kv)... ('a', '1')('c', '3')('b', '2')#循環(huán)遍歷字典key-value>>> for key,value in a.items(): print(key+':'+value)a:1b:2c:3>>> for (key,value) in a.items(): print(key+':'+value)a:1b:2c:3
列表生成式主要使用for循環(huán)來生成一個列表,for循環(huán)包含列表中每一個元素的生成規(guī)則
# 要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))>>> list(range(1, 11))[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]# 如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?>>> L = []>>> for x in range(1, 11):... L.append(x * x)...>>> L[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# 采用列表生成式>>> [x * x for x in range(1, 11)][1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# for循環(huán)后面還可以加上if判斷,這樣我們就可以篩選出僅偶數(shù)的平方>>> [x * x for x in range(1, 11) if x % 2 == 0][4, 16, 36, 64, 100]# 使用兩層循環(huán),可以生成全排列>>> [m + n for m in 'ABC' for n in 'XYZ']['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']>>> import os >>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目錄['.ssh', '.Trash', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Movies', 'Pictures']# 列表生成式也可以使用兩個變量來生成list>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }>>> [k + '=' + v for k, v in d.items()]['y=B', 'x=A', 'z=C']# 把一個list中所有的字符串變成小寫>>> L = ['Hello', 'World', 'IBM', 'Apple']>>> [s.lower() for s in L]['hello', 'world', 'ibm', 'apple']
通過列表生成式,直接創(chuàng)建一個列表;但是受到內(nèi)存限制,列表容量肯定是有限的;創(chuàng)建一個包含100萬個元素的列表,估計還沒有這么大內(nèi)存設(shè)備,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數(shù)元素占用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間。在Python中,這種一邊循環(huán)一邊計算的機制,稱為生成器:generator
。
我學(xué)到的有兩種生成器:列表是生成器、函數(shù)式生成器
要創(chuàng)建一個列表式生成器generator
,很簡單,只要把一個列表生成式的[]改成(),就創(chuàng)建了一個generator
>>> L = [x * x for x in range(5)]>>> L[0, 1, 4, 9, 16, 25]>>> g = (x * x for x in range(5))>>> g<generator object <genexpr> at 0x1022ef630>
創(chuàng)建L和g的區(qū)別僅在于最外層的[]和(),L是一個list
,而g是一個generator
我們可以直接打印出list的每一個元素,但我們怎么打印出generator
的每一個元素呢?
如果要一個一個打印出來,可以通過next()
函數(shù)獲得generator
的下一個返回值:
>>> next(g)0>>> next(g)1>>> next(g)4>>> next(g)9>>> next(g)16>>> next(g)25>>> next(g)Traceback (most recent call last): File '<stdin>', line 1, in <module>StopIteration# generator保存的是算法,每次調(diào)用next(g),就計算出g的下一個元素的值,# 直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration的錯誤
上面這種不斷調(diào)用next(g)
實在是太變態(tài)了,正確的方法是使用for
循環(huán),因為generator
也是可迭代對象
>>> g = (x * x for x in range(10))>>> for n in g:... print(n)... 01491625# 所以,創(chuàng)建了一個generator后,基本上永遠(yuǎn)不會調(diào)用next(),而是通過for循環(huán)來迭代它,并且不需要關(guān)心StopIteration的錯誤。
如果推算的算法比較復(fù)雜,用類似列表生成式的for
循環(huán)無法實現(xiàn)的時候,還可以用函數(shù)來實現(xiàn)。
著名的斐波拉契數(shù)列(Fibonacci),就無法用for
循環(huán)列表生成式實現(xiàn),除第一個和第二個數(shù)外,任意一個數(shù)都可由前兩個數(shù)相加得到,可用函數(shù)式生成器實現(xiàn):
# 波拉契數(shù)列用列表生成式寫不出來,但是用函數(shù)把它打印出來卻很容易:def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'# 注意,賦值語句:# a, b = b, a + b# 相當(dāng)于:# t = (b, a + b) # t是一個tuple# a = t[0]# b = t[1]# 不必顯式寫出臨時變量t就可以賦值
可以看出,fib函數(shù)實際上是定義了斐波拉契數(shù)列的推算規(guī)則,從第一個元素開始,推算出后續(xù)任意的元素,這種邏輯其實非常類似generator
。
上面的函數(shù)和generator
僅一步之遙。要把fib
函數(shù)變成generator
,只需要把print(b)
改為yield b
就可以了
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'# 定義g函數(shù)式enerator的方法:如果一個函數(shù)定義中包含yield關(guān)鍵字,那么這個函數(shù)就不再是一個普通函數(shù),而是一個generator>>> f = fib(6)>>> f<generator object fib at 0x104feaaa0>
這里,最難理解的就是generator
和函數(shù)的執(zhí)行流程不一樣。函數(shù)是順序執(zhí)行,遇到return
語句或者最后一行函數(shù)語句就返回。而變成generator
的函數(shù),在每次調(diào)用next()
的時候執(zhí)行,遇到yield
語句返回,再次執(zhí)行時從上次返回的yield
語句處繼續(xù)執(zhí)行。
想想,Python解釋器是怎么做到的
舉個簡單的例子,定義一個generator
,依次返回數(shù)字1,3,5:
def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5)# 調(diào)用該generator時,首先要生成一個generator對象,然后用next()函數(shù)不斷獲得下一個返回值:>>> o = odd()>>> next(o)step 11>>> next(o)step 23>>> next(o)step 35>>> next(o)Traceback (most recent call last): File '<stdin>', line 1, in <module>StopIteration
可以看到,odd
不是普通函數(shù),而是generator
,在執(zhí)行過程中,遇到yield
就中斷,下次又繼續(xù)執(zhí)行。執(zhí)行3次yield
后,已經(jīng)沒有yield
可以執(zhí)行了,所以,第4次調(diào)用next(o)
就報錯。
回到fib
的例子,我們在循環(huán)過程中不斷調(diào)用yield
,就會不斷中斷。當(dāng)然要給循環(huán)設(shè)置一個條件來退出循環(huán),不然就會產(chǎn)生一個無限數(shù)列出來。
同樣的,把函數(shù)改成generator
后,我們基本上從來不會用next()
來獲取下一個返回值,而是直接使用for
循環(huán)來迭代:
>>> for n in fib(6):... print(n)...112358
但是用for
循環(huán)調(diào)用generator
時,發(fā)現(xiàn)拿不到generator
的return
語句的返回值。如果想要拿到返回值,必須捕獲StopIteration
錯誤,返回值包含在StopIteration
的value
中:
>>> g = fib(6)>>> while True:... try:... x = next(g)... print('g:', x)... except StopIteration as e:... print('Generator return value:', e.value)... break...g: 1g: 1g: 2g: 3g: 5g: 8Generator return value: done
生成器yiled
關(guān)鍵字即可以有返回值,還可以接收值;利用這個特性,可以做到單線程下,實現(xiàn)并行效果
生成器對象的方法:
send()
:恢復(fù)生成器在上一次yiled
關(guān)鍵字暫停時,繼續(xù)運行,同時yiled
關(guān)鍵字還可以接收send
函數(shù)傳送的參數(shù)__next()__
:生成器繼續(xù)運行到下一次的yiled
關(guān)鍵字處,暫停import timedef consumer(name): print('%s consumer is ready!' %name) while True: p = yield print('product [%s] is comming,received by [%s]!' %(p, name)) def producer(name, g_consumer): g_consumer.__next__() print('producer %s is ready!'%name) for i in range(5): time.sleep(1) print('producer %s get new product %s'%(name, i)) g_consumer.send(i) c1 = consumer('A')producer('luke',c1)'''程序運行結(jié)果:A consumer is ready!producer luke is ready!producer luke get new product 0product [0] is comming,received by [A]!producer luke get new product 1product [1] is comming,received by [A]!producer luke get new product 2product [2] is comming,received by [A]!producer luke get new product 3product [3] is comming,received by [A]!producer luke get new product 4product [4] is comming,received by [A]!'''
迭代是一個重復(fù)的過程,每次重復(fù)即一次迭代,并且每次迭代的結(jié)果都是下一次迭代的初始值,
__iter__()
方法的對象,即obj.__iter__()
obj.__iter__()
得到的結(jié)果就是迭代器對象;而迭代器對象指的是即內(nèi)置有__iter__
又內(nèi)置有__next__
方法的對象。open('a.txt').__iter__()
open('a.txt').__next__()
優(yōu)點:
缺點:
for
循環(huán)的工作原理:
in
后對象的dic.__iter__()
方法,得到一個迭代器對象iter_dic
next(iter_dic)
,將得到的值賦值給k
,然后執(zhí)行循環(huán)體代碼StopIteration
,結(jié)束循環(huán)可以直接作用于for循環(huán)的數(shù)據(jù)類型有以下幾種:
list、tuple、dict、set、str
等;generator
,包括生成器和帶yield
的generator function
。這些可以直接作用于for
循環(huán)的對象統(tǒng)稱為可迭代對象:Iterable
??梢允褂?code>isinstance()判斷一個對象是否是Iterable
對象:
>>> from collections import Iterable>>> isinstance([], Iterable)True>>> isinstance({}, Iterable)True>>> isinstance('abc', Iterable)True>>> isinstance((x for x in range(10)), Iterable)True>>> isinstance(100, Iterable)False
生成器不但可以作用于for循環(huán),還可以被next()函數(shù)不斷調(diào)用并返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續(xù)返回下一個值了。
next()
函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器:Iterator
。isinstance()
判斷一個對象是否是Iterator
對象:>>> from collections import Iterator>>> isinstance((x for x in range(10)), Iterator)True>>> isinstance([], Iterator)False>>> isinstance({}, Iterator)False>>> isinstance('abc', Iterator)False
dic={'a':1,'b':2,'c':3}iter_dic=dic.__iter__() #得到迭代器對象,迭代器對象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身iter_dic.__iter__() is iter_dic #Trueprint(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,或者說結(jié)束標(biāo)志#有了迭代器,我們就可以不依賴索引迭代取值了iter_dic=dic.__iter__()while 1: try: k=next(iter_dic) print(dic[k]) except StopIteration: break#這么寫太麻煩了,需要我們自己捕捉異常,控制next,for循環(huán)這么牛逼,干嘛不用for循環(huán)呢#基于for循環(huán),我們可以完全不再依賴索引去取值了dic={'a':1,'b':2,'c':3}for k in dic: print(dic[k])
由for循環(huán)開始,繞了一圈又回到了for循環(huán),中間引申出列表生成式、生成器、迭代器,它們之間的關(guān)系我已經(jīng)寫完了,感覺自己又進步一點了,再接再厲。