python語(yǔ)言陷阱
python作為一門(mén)動(dòng)態(tài)語(yǔ)言,除了擁有強(qiáng)大的表達(dá)能力和高效的開(kāi)發(fā)效率外,它還有著一些語(yǔ)言層面的陷阱。
可變參數(shù)作為默認(rèn)參數(shù)
有時(shí)會(huì)看到有些小伙伴在python中定義函數(shù)的時(shí)候會(huì)使用一些可變參數(shù)作為函數(shù)的默認(rèn)參數(shù),最常見(jiàn)的如:list。
def test(a=[]):
a.append(1)
print(a)
if __name__ == '__main__':
test() # [1]
test() # [1, 1]
使用可變參數(shù)作為函數(shù)的默認(rèn)參數(shù)會(huì)讓最后的運(yùn)行結(jié)果超出預(yù)期所料。如上述例子中,第一次和第二次的結(jié)果截然不同。
如果不明白為什么會(huì)產(chǎn)生這樣的結(jié)果,看下面這個(gè)例子就會(huì)明白了
def test(a=[]):
a.append(1)
print(a)
def test2(a=print("test2")):
pass
if __name__ == '__main__':
test() # [1]
test() # [1, 1]
運(yùn)行上述例子,你會(huì)發(fā)現(xiàn)控制臺(tái)會(huì)去打印"test2"。通過(guò)對(duì)比你會(huì)發(fā)現(xiàn),函數(shù)的參數(shù)在函數(shù)被定義的時(shí)候就已經(jīng)確定了,如果參數(shù)是個(gè)表達(dá)式,則取表達(dá)式的結(jié)果。所以在test函數(shù)中, 無(wú)論運(yùn)行多少次,在函數(shù)里面都是在對(duì)函數(shù)被定義時(shí)的那個(gè)list進(jìn)行操作。
最開(kāi)始的那個(gè)例子就等同于下面的代碼
a = []
def test():
a.append(1)
print(a)
if __name__ == '__main__':
test() # [1]
test() # [1, 1]
避免此類(lèi)事故的發(fā)生的最好的方法就是不使用可變參數(shù),將默認(rèn)參數(shù)的行為放到函數(shù)內(nèi)部去定義。下面是改進(jìn)后的版本。
def test(a=None):
if a is None:
a = []
a.append(1)
print(a)
if __name__ == '__main__':
test() # [1]
test() # [1]
tuple的隱式變換
a = 1, # 1
b = [1], # ([1],)
c = (1) # 1
d = (1,) # (1,)
e = True if 10 / 2 > 2 else False, 1, 2, 3 # (True, 1, 2, 3)
print(a, b, c, d, e) # (1,) ([1],) 1 (1,) (True, 1, 2, 3)
在python中,tuple存在著一些隱式轉(zhuǎn)換的問(wèn)題。觀(guān)察上面例子,如果你是個(gè)python新手你會(huì)發(fā)現(xiàn),除了d的值符合你的預(yù)期,別的結(jié)果都似乎不是你想的那樣(注釋的部分即為運(yùn)行結(jié)果)。
符合一下幾種條件的都會(huì)被隱式轉(zhuǎn)換。
1、當(dāng)一個(gè)表達(dá)式以逗號(hào)結(jié)尾的時(shí)候,那么這個(gè)表達(dá)式的結(jié)果會(huì)被轉(zhuǎn)換為tuple。如上述a,b。
2、當(dāng)一個(gè)tuple里面只有一個(gè)元素,第一個(gè)元素后面沒(méi)有使用逗號(hào)隔開(kāi)的時(shí)候,那么這個(gè)tuple就會(huì)被轉(zhuǎn)換成僅有的那個(gè)元素。如上述c和d。
3、當(dāng)一個(gè)表達(dá)式有多個(gè)值,但表達(dá)式的接受者只有一個(gè)時(shí),那么接受者會(huì)被轉(zhuǎn)成tuple類(lèi)型。如上述e。
神奇的列表
item = [[]] * 3
print(item) # [[], [], []]
item[0].append(1)
print(item) # [[1], [1], [1]]
在上面例子中,明明只對(duì)0號(hào)索引的list進(jìn)行了append操作,為什么最后所有索引位置的list都添加進(jìn)了值。
注意看第一個(gè)表達(dá)式 item = [[]] * 3, * 3 的操作是操作的外層的列表,而里面的list沒(méi)有發(fā)生變化。因?yàn)閘ist是可變對(duì)象,每個(gè)list里面的list其實(shí)指向的都是同一個(gè)list。