最後,我們會談到妳在使用更多Python函數(數據類型、函數、模塊、類等)時可能會遇到的問題。).由於篇幅有限,我們在這裏盡量做到簡潔,尤其是壹些高級概念。有關更多詳細信息,請閱讀《學習Python,第二版》中的“技巧”和“疑難解答”章節。
打開文件的調用不使用模塊搜索路徑。
當您在Python中調用open()來訪問外部文件時,Python不會使用模塊搜索路徑來定位目標文件。它將使用您提供的絕對路徑,或者假設文件在當前工作目錄中。模塊搜索路徑只為模塊加載服務。
不同的類型有不同的方法。
列表方法不能用於字符串,反之亦然。通常,方法的調用與數據類型有關,但是內部函數通常可以用在許多類型上。比如list的reverse方法只對list有用,但是len函數適用於任何有長度的對象。
不能直接更改不可變的數據類型。
請記住,您不能直接更改不可變的對象(例如,元組、字符串):
T = (1,2,3)
T[2] = 4 #錯誤
通過切片、連接等方式創建壹個新對象。,並根據要求將原始變量的值賦給它。因為Python會自動回收無用的內存,所以它並不像看起來那樣浪費:
T = T[:2]+(4,)#沒問題:T變成(1,2,4)。
使用簡單的for循環,而不是while或range。
當您想從左到右遍歷壹個有序對象的所有元素時,使用壹個簡單的for循環(例如,對於seq中的x:)比基於while- or range-的計數循環更容易編寫,並且通常運行得更快。除非萬不得已,否則盡量避免在for循環中使用range:讓Python為您解決標記問題。在下面的例子中,三個循環結構都沒有問題,但第壹個通常更好;在Python中,簡單是最重要的。
S = "伐木工人"
對於S中的c:打印c #是最簡單的
對於範圍內的I(透鏡):打印s [I] #太多。
I = 0 #太多了
而我& ltlen(S):print S[I];i += 1
不要試圖從改變對象的函數中得到結果。
方法list.append()和list.sort()等直接更改操作會更改壹個對象,但不會返回所更改的對象(不會返回任何對象);正確的方法是直接調用它們,而不用給結果賦值。經常看到初學者可以寫出這樣的代碼:
mylist = mylist.append(X)
目的是獲得append的結果,但實際上這將把None賦值給mylist,而不是changed list。壹個更特殊的例子是通過使用排序的鍵值來遍歷字典中的元素。請看下面的例子:
D = {...}
對於D.keys()中的k。sort(): print D[k]
幾乎成功了——keys方法會創建壹個鍵的列表,然後用sort方法對列表進行排序——但是因為sort方法會返回None,所以循環會失敗,因為它實際上是要遍歷None(這不是壹個序列)。若要更正此代碼,請將方法調用分開並放在不同的語句中,如下所示:
Ks = D.keys()
Ks.sort()
對於Ks中的k:打印D[k]
類型轉換只存在於數值類型中。
在Python中,123+3.145這樣的表達式就可以了——它會自動將整數轉換成浮點類型,然後使用浮點運算。但是下面的代碼會出錯:
S = "42 "
I = 1
X = S+I #類型錯誤。
這也是故意的,因為不清楚:是把字符串轉換成數字(add)還是把數字轉換成字符串(join)?在Python中,我們認為“清晰比模糊好”(即eibti(顯式比隱式好)),所以手動轉換類型:
X = int(S)+I # Add: 43
x = S+str(I)# String join:" 421 "
循環數據結構會導致循環。
雖然這在實踐中很少見,但是如果壹個對象集合包含對自身的引用,它就被稱為循環對象。如果在對象中發現循環,Python將輸出壹個[…]以避免陷入無限循環:
& gt& gt& gtL = ['grail'] #在L中,L本身再次被引用。
& gt& gt& gtL.append(L) #在對象中創建壹個循環。
& gt& gt& gtL
[‘聖杯’,[...]]
除了知道這三點代表壹個物體中的循環,這個例子也是值得學習的。因為妳可能在代碼中不經意出現這樣的循環結構,妳的代碼就會出錯。如果需要,維護壹個列表或字典來表示已經訪問過的對象,然後檢查它以確認是否遇到了循環。
賦值語句不創建對象的副本,只創建引用。
這是Python的壹個核心思想,當行為錯誤時,有時會導致錯誤。在下面的例子中,壹個list對象被賦給壹個名為l的變量,然後l在list m中被引用,如果l在內部被改變,那麽它也會改變m所引用的對象,因為兩者指向同壹個對象。
& gt& gt& gtL = [1,2,3] # ***的列表對象
& gt& gt& gtM = ['X ',L,' Y'] #嵌入對L的引用
& gt& gt& gtM
['X ',[1,2,3],' Y']
& gt& gt& gtL[1] = 0 #也換了m。
& gt& gt& gtM
['X ',[1,0,3],' Y']
通常,只有在稍微大壹點的程序中,這才是非常重要的,而這些* * *引號通常就是妳所需要的。如果沒有,可以顯式地為它們創建壹個副本,以避免* * * *使用的引用;對於列表,您可以使用空列表的壹部分來創建頂級副本:
& gt& gt& gtL = [1,2,3]
& gt& gt& gtM = ['X ',L[:],' Y'] #嵌入L的副本。
& gt& gt& gtL[1] = 0 #只改變L,不影響m。
& gt& gt& gtL
[1, 0, 3]
& gt& gt& gtM
['X ',[1,2,3],' Y']
切片範圍從缺省值0開始,到切片序列的最大長度。如果兩者都被省略,切片將提取序列中的所有元素,並創建壹個頂級副本(壹個不共享的新對象)。對於字典,使用字典的dict.copy()方法。
靜態標識本地域的變量名。
默認情況下,Python將函數中賦值的變量名視為局部的,它存在於函數的作用域中,並且只在函數運行時存在。從技術上講,Python在編譯def代碼時靜態識別局部變量,而不是在運行時遇到賦值時。如果妳不明白這壹點,就會引起誤解。例如,看看下面的例子,當妳在引用後給變量賦值時會發生什麽:
& gt& gt& gtX = 99
& gt& gt& gt定義函數():
...打印X #目前不存在。
...X = 88 #將x視為全局def中的局部變量。
...
& gt& gt& gtfunc() #中的錯誤!
妳會得到壹個“未定義的變量名”的錯誤,但是原因很微妙。在編譯這段代碼時,Python認為在這個函數中的任何地方,當出現壹個給X賦值的語句時,X都會被當作壹個局部變量名..但是實際運行這個函數的時候,執行print語句的時候還沒有發生賦值語句,所以Python會報錯“未定義變量名”。
其實前面的例子想做的很模糊:是先輸出全局X再創建局部X,還是這是程序錯誤?如果真的要輸出這個全局X,需要在全局語句中聲明,或者用信封模塊的名字引用。
默認參數和可變對象
當執行def語句時,默認參數的值只被解析和保存壹次,而不是每次調用函數時都被解析和保存。這通常是您想要的,但是因為默認值需要在每次被調用時保持相同的對象,所以當您試圖更改可變默認值時應該小心。例如,以下函數使用空列表作為默認值,然後在每次調用該函數時更改其值:
& gt& gt& gtDef saver(x=[]): #保存列表對象。
...x.append(1) #並且每次調用時。
...打印x #以更改其值
...
& gt& gt& gtSaver([2]) #不使用默認值。
[2, 1]
& gt& gt& gtSaver() #使用默認值
[1]
& gt& gt& gtSaver() #會隨著每次調用而增加!
[1, 1]
& gt& gt& gt保護程序()
[1, 1, 1]
有人將此視為Python的壹個特性——由於變量默認參數在每次調用函數時都保持其狀態,因此可以提供與C語言中靜態局部函數變量類似的函數。但是第壹次遇到就很奇怪,Python中不同調用之間有更簡單的保存狀態的方法(比如類)。
要擺脫這種行為,使用切片或方法在函數的開頭創建默認參數的副本,或者將默認值的表達式移入函數中;只要每次調用函數時這些值都在函數中,那麽每次都會得到壹個新的對象:
& gt& gt& gtdef saver(x =無):
...如果x為None: x = [] #沒有傳遞參數?
...x.append(1) #更改新列表。
...打印x
...
& gt& gt& gtSaver([2]) #不使用默認值。
[2, 1]
& gt& gt& gtSaver() #這次不會變。
[1]
& gt& gt& gt保護程序()
[1]
其他常見的編程陷阱
這裏還有壹些無法在此詳述的陷阱:
頂層文件中語句的順序是特殊的:因為運行或加載文件將從上到下運行它的語句,所以請確保將無上限的函數調用或類調用放在函數或類的定義之後。
Reload不影響用from加載的名稱:reload最好與import語句壹起使用。如果使用from語句,記得在重載後再次運行from,否則仍將使用舊名稱。
多重繼承中混合的順序是特殊的:這是因為超類的搜索是從左到右的。在類定義的開頭,如果多個超類中有重名,以最左邊的類名為準。
try語句中的空except子句可能會捕獲比預期更多的錯誤。try語句中的空except子句意味著捕獲所有錯誤,甚至是真正的程序錯誤和sys.exit()調用。