《Effective Python》一書結(jié)合Python的語言特性,對代碼規(guī)范進(jìn)行了詳細(xì)總結(jié),是一本非常不錯的Python實操指南。但我在閱讀的過程中發(fā)現(xiàn)有些地方僅僅是告知讀者“怎么做”,但是具體“為什么”不是很深入。下面內(nèi)容是我對這些知識點的總結(jié)和相應(yīng)原理的擴(kuò)展。
(如有不準(zhǔn)確之處歡迎指正)
bytes是計算機(jī)原始的二進(jìn)制格式,而str是包含Unicode字符的,開發(fā)者不能以+號之類的操作符直接對它們兩個進(jìn)行混合操作。
實際上,它們互相之間是編碼(encode)與解碼(decode)的關(guān)系。
>>> s = "哇哈">>> b = bytes(s,encoding="utf-8") # encode>>> print(s)哇哈>>> print(b)b'\xe5\x93\x87\xe5\x93\x88'
可以看到,s是str類型,返回的依舊是人類能懂的文字,而b則返回的實際上是6個16進(jìn)制,每一個代表一字節(jié)。
注意,在bytes函數(shù)中使用了encoding參數(shù)并且賦值"utf-8"。為什么呢?這是因為s中保存的是unicode字符(也叫萬國碼),這種字符人類能看懂,但計算機(jī)是不懂的。如果要把它轉(zhuǎn)換成計算機(jī)能懂的語言(二進(jìn)制),就需要進(jìn)行編碼(encode),而utf-8是一種編碼的方式,通過這個方式可以將unicode編碼成bytes格式,反之就是解碼。
一般而言Python在使用str的時候會自動編碼解碼,不需要我們操心。但如果開發(fā)者需要手動操作bytes類型的數(shù)據(jù)則需要顯式編碼。
>>> s2 = str(b,encoding="utf-8") # 這里參數(shù)是encoding但實際是decode了>>> print(s2)哇哈
當(dāng)我們需要把bytes轉(zhuǎn)成str是一樣的,顯示注明編碼(解碼)方式,然后將bytes類型對象進(jìn)行解碼,得到原本的unicode字符。
剛參加工作時寫了這么一句代碼:
if (is_one_digit or is_two_digits or is_third_digits) and ((0< (current_digit-last_chinese_digit) <= 2) or ((last_chinese_digit == 9 or last_chinese_digit == 8) and current_digit == 0) or (last_chinese_digit == 0)) and is_selection_line_score<=0 and calculation_or_not(rect_list)[0]>0.2:
是不是很惡心?一般人看見這種代碼心里肯定萬馬狂奔。單行如果有多個and或or這種東西,最好是要拆開幾行來寫,然后再放到if語句中做判斷。
>>> a = [1,2,3]>>> print(a[0:2]) # 0多余,可以省略。[1, 2]>>> print(a[:2]) # 如果從表頭開始,0可以省略:同理如果到表尾,表尾也可以省略。[1, 2]
>>> a = [1,2,3]>>> b = a[:100] # 切片無視越界>>> b[1, 2, 3]>>> c = a[100] # 訪問單個元素索引越界報錯Traceback (most recent call last):File "<stdin>", line 1, in <module>IndexError: list index out of range
>>> a = [1,2,3,4,5,6]>>> a[:3] = [10,11] # 右側(cè)值會將左側(cè)列表指定范圍內(nèi)的值替換掉。>>> a[10, 11, 4, 5, 6]
>>> a = [1,2,3,4,5,6,7,8,9,10]>>> print(a[1:5:2]) # 這樣寫顯得有些亂[2, 4]>>> b = a[1:5] # 可以先做范圍切割>>> print(b[::2]) # 再做步進(jìn)切割[2, 4]
>>> a = [1,2,3,4,5,6,7,8,9,10]>>> b = [x+1 for x in a] # 用一份列表制作另外一份>>> b[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]>>> c = [x+1 for x in a if x>5] # 還可以添加條件判斷過濾掉一部分元素>>> c[7, 8, 9, 10, 11]
列表推導(dǎo)支持多級循環(huán),也支持多個條件判斷,但最好不要寫太多,不然代碼很難懂。
建議:
2個條件,2個循環(huán),或者1個條件1個循環(huán).
生成器真的是Python中極為強(qiáng)大的一個功能,它與列表推導(dǎo)的不同在于:列表推導(dǎo)得到的是一個實實在在的列表,而生成器得到的是一個算法,通過這個算法可以一項一項計算得到我們想要的結(jié)果,這樣做就帶來了一個好處:節(jié)約內(nèi)存。
>>> a = [1,2,3,4,5,6,7,8,9,10]>>> b = [x+1 for x in a] # 列表推導(dǎo)式>>> c = (x+1 for x in a) # 生成器表達(dá)式>>> b[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]>>> c<generator object <genexpr> at 0x000001F0CCE7D5C8>
可以看到,通過列表推導(dǎo)得到的列表b保存的是一個完整的列表。如果這個列表有上千萬個元素,那么它占用的內(nèi)存空間無疑是巨大的。而c則只保存了一個生成器對象,它會在在你需要的時候一個一個計算出值。
>>> for x in c:... print(x)...234567891011
生成器表達(dá)式還有另外一個好處:可以互相結(jié)合。
>>> a = [1,2,3]>>> b = (x+1 for x in a) # 通過b可以得到2,3,4>>> c = (y**2 for y in b) # 通過c可以得到4,9,16>>> for y in c:... print(y)...4916
外圍的生成器每次前進(jìn)時,都會推動內(nèi)部的那個生成器,于是產(chǎn)生連鎖反應(yīng)。而且這種連鎖生成器表達(dá)式可以在Python中高效執(zhí)行。
生成器是迭代器的一種。那么迭代器是什么呢?
Python中有一種對象,它可以被for循環(huán)進(jìn)行遍歷,我們統(tǒng)稱這種對象為“可迭代對象”(Iteralbe)??傻鷮ο笾钥梢员谎h(huán)遍歷,是因為當(dāng)循環(huán)體作用到它身上時,會自動調(diào)用它內(nèi)部的__iter__方法使得它返回一個類似于“傳送帶”功能一樣的對象,這個對象會一個接一個把元素進(jìn)行返回。這個“傳送帶”即是迭代器(Iterator)。
a = [1,2,3,4]for x in a: print(x)# --------------------------# 下面這個語句塊與上面是等價的# --------------------------it = iter(a) # 手動調(diào)用iter(),實際是調(diào)用a中__iter__方法,返回一個迭代器itwhile True: try: x = next(it) # 使用next()函數(shù)不斷取迭代器中的下一個值。 print(x) except StopIteration: # 當(dāng)沒有值可取時會發(fā)生異常并結(jié)束循環(huán)。 break
在Python中,list,dict,str等統(tǒng)統(tǒng)都是可迭代對象,也就是說,它們都可以執(zhí)行iter()函數(shù)。但它們并不是迭代器;迭代器是一種惰性計算序列,它只有當(dāng)你需要時才會計算相應(yīng)的值給你,生成器就是一種迭代器。
總結(jié)一下:
enumerate 可以直接得到當(dāng)前迭代器中每個元素的索引,寫起來更簡潔。
Python3 中的zip相當(dāng)于生成器,會在遍歷過程中逐次產(chǎn)生元組。需要注意的是,如果提供的迭代器長度不等,則zip會自動提前停止。
從來沒這么寫過......略
final語句塊用于執(zhí)行那些無論如何都要執(zhí)行的部分。
else則用于將異常與非異常語句塊區(qū)分開,提升代碼可讀性。
(未完待續(xù))