03|列表和元組,到底用哪一個(gè)唉堪?
列表是動(dòng)態(tài)的模聋,長度大小不固定,可以隨意地增加唠亚、刪減或者改變元素(mutable)链方。 而元組是靜態(tài)的,長度大小固定灶搜,無法增加刪減或者改變(immutable)祟蚀。
count(item) 表示統(tǒng)計(jì)列表 / 元組中 item 出現(xiàn)的次數(shù)。
index(item) 表示返回列表 / 元組中 item 第一次出現(xiàn)的索引割卖。
list.reverse() 和 list.sort() 分別表示原地倒轉(zhuǎn)列表和排序(注意前酿,元組沒有內(nèi)置的這兩個(gè) 函數(shù))。
reversed() 和 sorted() 同樣表示對列表 / 元組進(jìn)行倒轉(zhuǎn)和排序鹏溯,但是會(huì)返回一個(gè)倒轉(zhuǎn)后 或者排好序的新的列表 / 元組罢维。
l = [1, 2, 3]
l.__sizeof__()
#64
tup = (1, 2, 3)
tup.__sizeof__()
#48
l = []
l.__sizeof__() // 空列表的存儲(chǔ)空間為 40 字節(jié)
40
l.append(1)
l.__sizeof__()
72 // 加入了元素 1 之后,列表為其分配了可以存儲(chǔ) 4 個(gè)元素的空間 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 // 由于之前分配了空間丙挽,所以加入元素 2肺孵,列表空間不變
l.append(3)
l.__sizeof__()
72 // 同上
l.append(4)
l.__sizeof__()
72 // 同上
l.append(5)
l.__sizeof__()
104 // 加入元素 5 之后,列表的空間不足颜阐,所以又額外分配了可以存儲(chǔ) 4 個(gè)元素的空間
python3 -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
python3 -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop
04 | 字典平窘、集合,你真的了解嗎凳怨?
集合并不支持索引操作瑰艘,因?yàn)榧媳举|(zhì)上是一個(gè)哈希表,和列表不一 樣猿棉。
#字典和集合的創(chuàng)建方式
d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male')
print(d1 == d2 == d3 ==d4)
# True
s1 = {1, 2, 3}
s2 = set([1, 2, 3])
print(s2)
# {1, 2, 3}
print(s1 == s2)
# True
字典訪問可以直接索引鍵磅叛,如果不存在,就會(huì)拋出異常萨赁;也可以使用 get(key, default) 函數(shù)來進(jìn)行索引弊琴。如果鍵不存在,調(diào)用 get() 函數(shù)可以返回 一個(gè)默認(rèn)值杖爽。
集合并不支持索引操作敲董,因?yàn)榧媳举|(zhì)上是一個(gè)哈希表紫皇,和列表不一 樣。
想要判斷一個(gè)元素在不在字典或集合內(nèi)腋寨,我們可以用 value in dict/set 來判斷聪铺。
插入操作
每次向字典或集合插入一個(gè)元素時(shí),Python 會(huì)首先計(jì)算鍵的哈希值(hash(key))萄窜,再和 mask = PyDicMinSize - 1 做與操作铃剔,計(jì)算這個(gè)元素應(yīng)該插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的查刻,那么這個(gè)元素就會(huì)被插入其中键兜。 而如果此位置已被占用,Python 便會(huì)比較兩個(gè)元素的哈希值和鍵是否相等穗泵。若兩者都相等普气,則表明這個(gè)元素已經(jīng)存在,如果值不同佃延,則更新值现诀。 若兩者中有一個(gè)不相等,這種情況我們通常稱為哈希沖突(hash collision)履肃,意思是兩 個(gè)元素的鍵不相等仔沿,但是哈希值相等。這種情況下尺棋,Python 便會(huì)繼續(xù)尋找表中空余的位置于未,直到找到位置為止。
值得一提的是陡鹃,通常來說烘浦,遇到這種情況,最簡單的方式是線性尋找萍鲸,即從這個(gè)位置開始闷叉, 挨個(gè)往后尋找空位。當(dāng)然脊阴,Python 內(nèi)部對此進(jìn)行了優(yōu)化(這一點(diǎn)無需深入了解握侧,你有興趣 可以查看源碼,我就不再贅述)嘿期,讓這個(gè)步驟更加高效品擎。 查找操作 和前面的插入操作類似,Python 會(huì)根據(jù)哈希值备徐,找到其應(yīng)該處于的位置萄传;然后,比較哈希 表這個(gè)位置中元素的哈希值和鍵蜜猾,與需要查找的元素是否相等秀菱。如果相等振诬,則直接返回;如 果不等衍菱,則繼續(xù)查找赶么,直到找到空位或者拋出異常為止。
刪除操作
對于刪除操作脊串,Python 會(huì)暫時(shí)對這個(gè)位置的元素辫呻,賦于一個(gè)特殊的值,等到重新調(diào)整哈希 表的大小時(shí)琼锋,再將其刪除印屁。 不難理解,哈希沖突的發(fā)生斩例,往往會(huì)降低字典和集合操作的速度。因此从橘,為了保證其高效 性念赶,字典和集合內(nèi)的哈希表,通常會(huì)保證其至少留有 1/3 的剩余空間恰力。隨著元素的不停插 入叉谜,當(dāng)剩余空間小于 1/3 時(shí)踩萎,Python 會(huì)重新獲取更大的內(nèi)存空間,擴(kuò)充哈希表香府。不過,這 種情況下企孩,表內(nèi)所有的元素位置都會(huì)被重新排放。 雖然哈希沖突和哈希表大小的調(diào)整勿璃,都會(huì)導(dǎo)致速度減緩擒抛,但是這種情況發(fā)生的次數(shù)極少歧沪。所 以,平均情況下莲组,這仍能保證插入诊胞、查找和刪除的時(shí)間復(fù)雜度為 O(1)。
05 | 深入淺出字符串
常見的的轉(zhuǎn)義字符
Python 的字符串是不可變的(immutable)锹杈,Python 中字符串的改變厢钧,通常只能通過創(chuàng)建新的字符串來完成鳞尔。
s='HKSIHh'
s = 'H' + s[1:]
s = s.replace('h', 'H'
你可能了解到,在其他語言中早直,如 Java寥假,有可變的字符串類型,比如 StringBuilder霞扬,每次 添加糕韧、改變或刪除字符(串),無需創(chuàng)建新的字符串喻圃,時(shí)間復(fù)雜度僅為 O(1)萤彩。這樣就大大 提高了程序的運(yùn)行效率。 但可惜的是斧拍,Python 中并沒有相關(guān)的數(shù)據(jù)類型雀扶,我們還是得老老實(shí)實(shí)創(chuàng)建新的字符串。因 此肆汹,每次想要改變字符串愚墓,往往需要 O(n) 的時(shí)間復(fù)雜度,其中昂勉,n 為新字符串的長度浪册。
s = ''
for n in range(0, 100000):
s += str(n)
自從 Python2.5 開始,每次處理字符串的拼接操作時(shí)(str1 += str2)岗照,Python 首先會(huì)檢 測 str1 還有沒有其他的引用村象。如果沒有的話,就會(huì)嘗試原地?cái)U(kuò)充字符串 buffer 的大小攒至,而 不是重新分配一塊內(nèi)存來創(chuàng)建新的字符串并拷貝厚者。
這樣的話,上述例子中的時(shí)間復(fù)雜度就僅 為 O(n) 了迫吐。
因此籍救,以后你在寫程序遇到字符串拼接時(shí),如果使用’+='更方便渠抹,就放心地去用吧蝙昙,不用 過分擔(dān)心效率問題了。 另外梧却,對于字符串拼接問題奇颠,除了使用加法操作符,我們還可以使用字符串內(nèi)置的 join 函 數(shù)放航。string.join(iterable)烈拒,表示把每個(gè)元素都按照指定的格式連接起來。
l = []
for n in range(0, 100000):
l.append(str(n))
l = ' '.join(l)
#由于列表的 append 操作是 O(1) 復(fù)雜度,字符串同理荆几。因此吨铸,這個(gè)含有 for 循環(huán)例子的時(shí)
間復(fù)雜度為 n*O(1)=O(n)。
string.strip(str)舟奠,表示去掉首尾的 str 字符串房维; string.lstrip(str),表示只去掉開頭的 str 字符串耿戚; string.rstrip(str)阿趁,表示只去掉尾部的 str 字符串歌焦。
06 | Python “黑箱”:輸入與輸出
讓我們來做一個(gè)簡單的 NLP(自然語言處理)任務(wù)独撇。如果你對此不太了解也沒有影 響躁锁,我會(huì)帶你一步步完成這個(gè)任務(wù)。
首先搜立,我們要清楚 NLP 任務(wù)的基本步驟槐秧,也就是下面的四步: 1. 讀取文件刁标; 2. 去除所有標(biāo)點(diǎn)符號(hào)和換行符,并把所有大寫變成小寫顿锰; 3. 合并相同的詞,統(tǒng)計(jì)每個(gè)詞出現(xiàn)的頻率刘陶,并按照詞頻從大到小排序牢撼; 4. 將結(jié)果按行輸出到文件 out.txt。 你可以自己先思考一下浪默,用 Python 如何解決這個(gè)問題。這里碰逸,我也給出了我的代碼阔加,并附 有詳細(xì)的注釋胜榔。我們一起來看下這段代碼。
import re
# 你不用太關(guān)心這個(gè)函數(shù)
def parse(text):
# 使用正則表達(dá)式去除標(biāo)點(diǎn)符號(hào)和換行符
text = re.sub(r'[^\w ]', ' ', text)
# 轉(zhuǎn)為小寫
text = text.lower()
# 生成所有單詞的列表
word_list = text.split(' ')
# 去除空白單詞
word_list = filter(None, word_list)
# 生成單詞和詞頻的字典
word_cnt = {}
for word in word_list:
if word not in word_cnt:
word_cnt[word] = 0
word_cnt[word] += 1
# 按照詞頻排序
sorted_word_cnt = sorted(word_cnt.items(), key=lambda kv: kv[1], reverse=True)
return sorted_word_cnt
with open('in.txt', 'r') as fin:
text = fin.read()
word_and_freq = parse(text)
with open('out.txt', 'w') as fout:
for word, freq in word_and_freq:
fout.write('{} {}\n'.format(word, freq))
07 | 修煉基本功:條件與循環(huán)
不過吭露,切記讲竿,在實(shí)際寫代碼時(shí)弄屡,我們鼓勵(lì),除了 boolean 類型的數(shù)據(jù)迈嘹,條件判斷最好是顯 性的全庸。比如壶笼,在判斷一個(gè)整型數(shù)是否為 0 時(shí),我們最好寫出判斷的條件:
if i != 0:
...
#不推薦的寫法:
if i:
...
l = [1, 2, 3, 4, 5, 6, 7]
for index, item in enumerate(l):
if index < 5:
print(item)
在循環(huán)語句中挑豌,我們還常常搭配 continue 和 break 一起使用。所謂 continue氓英,就是讓程 序跳過當(dāng)前這層循環(huán)铝阐,繼續(xù)執(zhí)行下面的循環(huán);而 break 則是指完全跳出所在的整個(gè)循環(huán) 體练对。在循環(huán)中適當(dāng)加入 continue 和 break吹害,往往能使程序更加簡潔、易讀螺男。
range() 函數(shù)是直接由 C 語言寫的下隧,調(diào)用它速度非澄矫剑快。而 while 循環(huán)中的“i += 1”這個(gè)操作土辩,得通過 Python 的解釋器間接調(diào)用底層的 C 語言宗弯;并且這個(gè)簡單的操 作搂妻,又涉及到了對象的創(chuàng)建和刪除(因?yàn)?i 是整型,是 immutable邓厕,i += 1 相當(dāng)于 i = new int(i + 1))详恼。所以引几,顯然,for 循環(huán)的效率更勝一籌敞掘。
08 | 異常處理:如何提高程序的穩(wěn)定性?
class MyInputError(Exception):
"""Exception raised when there're errors in input"""
def __init__(self, value): # 自定義異常類型的初始化
self.value = value
def __str__(self): # 自定義異常類型的 string 表達(dá)形式
return ("{} is invalid input".format(repr(self.value)))
try:
raise MyInputError(1) # 拋出 MyInputError 這個(gè)異常
except MyInputError as err:
print('error: {}'.format(err))
異常更扁,通常是指程序運(yùn)行的過程中遇到了錯(cuò)誤浓镜,終止并退出劲厌。我們通常使用 try except 語句去處理異常,這樣程序就不會(huì)被終止相叁,仍能繼續(xù)執(zhí)行辽幌。 處理異常時(shí),如果有必須執(zhí)行的語句虑润,比如文件打開后必須關(guān)閉等等拳喻,則可以放在 finally block 中猪腕。 異常處理,通常用在你不確定某段代碼能否成功執(zhí)行亚亲,也無法輕易判斷的情況下腐缤,比如數(shù) 據(jù)庫的連接、讀取等等惜索。正常的 flow-control 邏輯,不要使用異常處理剃浇,直接用條件語 句解決就可以了巾兆。
09 | 不可或缺的自定義函數(shù)
def find_largest_element(l):
if not isinstance(l, list):
print('input is not type of list')
return
if len(l) == 0:
print('empty input')
return
largest_element = l[0]
for item in l:
if item > largest_element:
largest_element = item
print('largest element is: {}'.format(largest_element))
find_largest_element([8, 1, -3, 2, 0])
# 輸出
# largest
# element is: 8
def outer():
x = "local"
def inner():
nonlocal x # nonlocal 關(guān)鍵字表示這里的 x 就是外部函數(shù) outer 定義的變量 x
x = 'nonlocal'
print("inner:", x)
inner()
print("outer:", x)
outer()
# 輸出
inner:
nonlocal
outer:
nonlocal
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函數(shù)
square = nth_power(2) # 計(jì)算一個(gè)數(shù)的平方
cube = nth_power(3) # 計(jì)算一個(gè)數(shù)的立方
# square
# # 輸出
# < function
# __main__.nth_power. < locals >.exponent(base) >
# cube
# # 輸出
# < function
# __main__.nth_power. < locals >.exponent(base) >
# print(square(2)) # 計(jì)算 2 的平方
# print(cube(2)) # 計(jì)算 2 的立方
# # 輸出
# 4 # 2^2
# 8 # 2^3
- Python 中函數(shù)的參數(shù)可以接受任意的數(shù)據(jù)類型角塑,使用起來需要注意,必要時(shí)請?jiān)诤瘮?shù)開 頭加入數(shù)據(jù)類型的檢查质帅;
- .和其他語言不同留攒,Python 中函數(shù)的參數(shù)可以設(shè)定默認(rèn)值炼邀;
- 嵌套函數(shù)的使用,能保證數(shù)據(jù)的隱私性洛退,提高程序運(yùn)行效率杰标;
- 合理地使用閉包,則可以簡化程序的復(fù)雜度媒区,提高可讀性掸犬。
10 | 簡約不簡單的匿名函數(shù)
函數(shù) map(function, iterable) 的第一個(gè)參數(shù)是函數(shù)對象湾碎,第二個(gè)參 數(shù)是一個(gè)可以遍歷的集合呻顽,它表示對 iterable 的每一個(gè)元素贩挣,都運(yùn)用 function 這個(gè)函數(shù)裕便。
python3 -mtimeit -s'xs=range(1000000)' 'map(lambda x: x*2, xs)'
# 2000000 loops, best of 5: 171 nsec per loop
python3 -mtimeit -s'xs=range(1000000)' '[x * 2 for x in xs]'
# 5 loops, best of 5: 62.9 msec per loop
python3 -mtimeit -s'xs=range(1000000)' 'l = []' 'for i in xs: l.append(i * 2)'
# 5 loops, best of 5: 92.7 msec per loop
filter(function, iterable) 函數(shù)下翎,它和 map 函數(shù)類似庆揩,function 同樣表示一個(gè) 函數(shù)對象锈拨。filter() 函數(shù)表示對 iterable 中的每個(gè)元素验辞,都使用 function 判斷壳贪,并返回 True 或者 False磕蒲,最后將返回 True 的元素組成一個(gè)新的可遍歷的集合殖卑。
l = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, l) # [2, 4]
最后我們來看 reduce(function, iterable) 函數(shù),它通常用來對一個(gè)集合做一些累積操作荣刑。 function 同樣是一個(gè)函數(shù)對象董习,規(guī)定它有兩個(gè)參數(shù)皿淋,表示對 iterable 中的每個(gè)元素以及上 一次調(diào)用后的結(jié)果恬试,運(yùn)用 function 進(jìn)行計(jì)算,所以最后返回的是一個(gè)單獨(dú)的數(shù)值哑舒。
l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l) # 1*2*3*4*5 = 120