1. 程式人生 > >分享一個高手的python學習隨筆。對於入門的新手有很大幫助

分享一個高手的python學習隨筆。對於入門的新手有很大幫助

黑體表示章節, 下劃線表示可以直接在原文對應位置查到的專有技術名詞。

第三章 如何執行程式

  import進行模組匯入只能執行一次,多次執行需使用reload。

  模組往往是變數名的封裝,被認為是名稱空間。例如:

#myfile.py
title = "test"

>>>import myfile
>>>print myfile.title
test 

  替代方案是from,下面有同樣的效果:

>>>from myfile import title
>>>print tittle
test
from myfile import
* 則可以把myfile所有變數全部匯入(第19章內容)。

第四章 介紹Python物件型別

  雖然字串支援多種操作,但是它具有不可變性,即原字串不能改變,只能用新字串作為結果賦予一個變數。下面是一個試圖改變原字串的操作及報錯資訊:

>>> s="spam"
>>> s[0] = 'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

 第五章 數字

   str和repr顯示格式

複製程式碼
>>>num = 1/3.0
>>>num
0.33333333333333331
>>>print num
333333333333

>>>repr(num)
'0.33333333333333331' #互動模式回顯
>>>str(num)
'333333333333' #列印語句
複製程式碼

  浮點數運算在精確方面有缺陷。這和硬體有關,列印結果也不能完全解決。

>>> 0.1+0.1+0.1-0.3
5.551115123125783e-17
>>> print
0.1+0.1+0.1-0.3 5.55111512313e-17

  使用小數物件可以進行修正

>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Decimal('0.0') 

 第六章 動態型別簡介

a = 3這個語句實際上執行了三個步驟:建立一個物件代表值3;如果還未建立,建立一個變數a;將變數與新的物件3連線。這時,變數a成為了物件3的一個引用,也可以看做是指標。

  型別屬於物件,而不是變數,這就很好理解為什麼Python中同一個變數名可以作為不同型別的物件的引用了。

  在這種機制下,每個物件都有一個引用計數器,當計數器為0時就被系統回收,這便是Python中物件的垃圾收集的方法了。

  不同變數引用同一個數字或字串時,對變數操作(eg.a=3 a=a+2)只是建立了一個新的物件並使它引用新物件,這也是上一章提到的字串不能改動的原因。而對於一些型別來說,有的操作確實能改變物件,如下所示:

複製程式碼
#situation 1
>>>L1=[2,3,4]
>>>L2=L1
>>>L1=24
>>>L2
[2,3,4]

#situation 2
>>L1=[2,3,4]
>>>L2=L1
>>>L2[0]=24
>>>L1
[24,3,4]
複製程式碼

  為了讓兩個變數使用不同的物件,可以拷貝物件,使用L2=L1[:]來代替L2=L1即可。對於字典則使用D.copy()方法。標準庫的copy模組提供了一個對任意物件的呼叫方法,下面兩種方式的區別暫不討論:

import copy
X = copy.copy(Y) #表層拷貝
X = copy.deepcopy(Y) #深拷貝

  這裡就出現了個問題,兩個引用是否是同一物件?可以用下面的方式判斷:

>>>L=[1,2,3]
>>>M=L
>>>L== M
True
>>>L is M
True

負值索引相當於從末尾倒數。-1就是最後一個元素的索引。對於s="spam",-5是個非法的索引值。分號:前的空值表示從第一個開始,後的空值表示直到最後一個。

 第七章 字串

  單雙引號是一樣的,這樣允許使用者不使用轉移字元來實現帶有單或雙引號的字串。個人認為避免了按shift才能使用雙引號“的麻煩。

>>>'knight"s ',"knight's"
('knight"s ',"knight's")

  此外,合併相鄰的字串常量,如'knight"s ' "knight's"(中間有空格)會顯示為'knight"s knight\'s'。可見,最外層是單引號,為了保持原內容,Python把單引號裡的單引號改寫成了轉義字元,這個例子和書上的不同,更有助於理解。轉義字元和C很類似,多了幾種;但是Python裡沒有空字串,Python為每個字串儲存了內容和長度。同時,如果一個字串中沒有合法的轉義編碼出現在"\"後,那麼它將在字串中保留反斜線。抑制轉義的方法是在字串前加r,如r"C:\new\text.dat",此時的\n和\t就不會被當做是轉義字元,同時,這樣做也不必把\改寫成\\。

  三重引號適用於多行的字串的直接輸入而不使用轉義字元。利用三重引號也可以實現類似C中/* */註釋掉程式碼的目的。

  Unicode字串通過在前面加u獲得。

  擴充套件分片是第三個索引,用作步進。這時完整的分片形式為X[I:J:K],其中步進為K。當步進取-1時,可以把字串反轉,很神奇的方法。

  利用分片,可以對字串進行修改,即把新字串加到原字串上,再把原字串切掉。

  字串格式化的用法與C的printf很像,不同之處在於所有引數外需要加一個(),形成%(arg1,arg2,arg3)的形式。格式化程式碼請參考原書表格,通用結構:%[(name)][flags][width][.precision]code,其中name可以是字典名,這時在引數表裡提供這個字典的鍵即可。

  既然字串是物件,那麼它就有對應的方法。書上介紹了修改字串的replace()、查詢find()、把每個元素取出建立列表的list()、把列表合併成字串的join()(可以作為list()的反操作)、提取元件的split()。

第八章 列表

  用中括號表示列表,列表的組成物件是有序的,組成列表的各個物件允許不同。

  用大括號表示字典,字典的組成物件是無序的,字典鍵的搜尋方式是雜湊搜尋,速度很快。

  可以用字典來模擬列表:使用序數作為字典的索引即可。類似地,字典可以用來表示一些稀疏矩陣。

  字典的get方法用於避免不存在的鍵,如果鍵不存在,返回值是0。

  字典介面是使用方式類似字典並且實際工作都和字典一樣的一些Python擴充套件程式的介面。

第9章 元組、檔案及其他 

用小括號表示元組,元組不能原處修改。

  為了避免只含一個元素的元組被當做表示式,使用一個逗號,寫為(40,)。逗號可以幫助識別元組,下面的也是元組的表示方式:

t = 0,'Ni',1.2,3

  從檔案中讀取的是字串,需要作為其他型別來操作時必須轉換。

  eval()用來把字串作為物件,因此也可以達到執行Python的任何表示式。

  pickle模組可以直接在檔案中儲存幾乎任何Python物件。

  struct模組提供了二進位制資料的打包。打包+存入檔案,讀取檔案+解包。

  在“賦值VS引用”這一節,對於複合方式的賦值,修改其成員會導致所有使用該成員的物件的改變。直觀來看就是下面:

複製程式碼
>>>X = [1,2,3]
>>>L = ['a', X, 'b']
>>>D = {'x':X, 'y':2}
>>>X[1] = 'surprise'
>>>L
['a', [1,'surprise',3], 'b']
>>>D
{'x':[1,'surprise',3], 'y':2}
複製程式碼

  這是一個陷阱,為了避免這種情況,根據具體型別使用拷貝(比如分片、copy方法)而不是引用。

  Python內部暫時儲存並重復使用短字串,因此對同樣內容的字串,is判定可能根據其長度為True(字串較短時)或False(字串較長時)。不同型別的比較(用==進行)的判定方式不一樣。

  還有其他內建型別陷阱,如重複能增加層次深度,迴圈資料結構L=L.append(L)

第二部分練習題

  2.(摘自附錄B)分片運算超出邊界(例如,L[-1000:100])可工作,因為Python會縮放超出邊界的分片(必要時,限制值可設為零和序列長度)。以翻轉的方式提取序列是行不通的(較低邊界值比較高邊界值更大,例如,L[3:1])。你會得到空分片([ ]),因為Python會縮放分片限制值,以確定較低邊界永遠比較高邊界小或相等(例如,L[3:1]會縮放成L[3:3],空的插入點是在偏移值3處)。Python分片一定是從左至右抽取,即使你用負號索引值也是這樣(會先加上序列長度轉換成正值)。注意到,Python 2.3的第三限制值分片會稍微修改此行為:L[3:1:-1]的確是從右至左抽取。

  3.索引運算、分片運算以及del:對於L=[1,2,3,4], L[2] = []只能把3變為[],而L[2:3] = []卻能刪掉第三項。

  4.X,Y = Y,X,左邊視為兩個物件,右邊視為一個元組,這個表示式交換了兩個引用。

第10章 Python語句簡介 

  絕大多數的Python程式每行一個語句,不需要分號。Python的風格就是完全不要分號,雖然在語句末加上分號也能通過。唯一需要分號的情況是一行中多個語句的分隔符。相反地,括號可以使一個語句分隔成很多行,比如用列表直觀地定義一個矩陣時。反斜線\也可以達到這個目的。

  巢狀程式碼只需要保持縮排一致即可。Python是WYSIWYG語言(what you see is what you get,所見即所得)

第11章 賦值、表示式和列印

形如X+=Y的賦值語句稱為增強賦值語句,它有三個優點:程式設計師輸入減少,左側只需要計算一次(X=X+Y中X計算兩次),優化技術會自動選擇(支援原處修改的型別可以直接原處修改)。

  單一下劃線開頭的變數名不會被from module import *這樣的語句匯入。前後都有雙下劃線的變數名是系統定義的變數名,對直譯器有特殊意義。雙下劃線開頭但結尾沒有雙下劃線的變數是類的本地變數(參考第19章)。

  表示式語句通常用於執行可原處修改列表的列表方法,即對於列表L,L.append(3)是正確的,而L=L.append(4)是錯誤的,第二個式子右邊返回的是None。

  標準輸出的重定向方法:

import sys
sys.stdout = open('log.txt','a')
...
print x,y,x #寫入log.txt

 為了避免忘記恢復sys.stdout,寫入log.txt也可以用:

log = open('log.txt','a')
print >>log, x, y, x

第12章 if測試

   類似於C,Python的布林運算or是短路運算,而它返回第一個為真的操作物件,或者是第二個為假的物件。[ ] or { } 將返回{ }。

  if選擇分支有以下幾種等價形式:

複製程式碼
#最常見的分支形式
if X:
    A = Y
else:
    A = Z

#Python2.5以後引入
A = Y if X else Z

#Python2.5以前(以後也相容)
#需要理解and和or的運算和返回值規則
A = ((X and Y) or Z)

#列表形式
A = [Z,Y][bool(X)]
複製程式碼

第13章  while和for迴圈

pass語句是無運算的佔位符,為了表示語法需要語句並且還沒有任何實用的語句可寫時就可以使用它。

  while和for迴圈都有一個可選的else語句,在迴圈條件不滿足時且沒有用break結束迴圈時使用。

  C語言形式的 while((x = next()) != NULL) { ...process x...}在Python裡行不通:C語言賦值語句會返回賦值後的值,而Python賦值語句只是語句,不是表示式。

  Python的迭代協議:有next方法的物件會前進到下一個結果,而在末尾時引發StopIteration。所有的迭代工具內部工作都呼叫next,並捕捉StopIteration異常來確定何時離開。

  用for修改列表時,for x in L:x+=1是無法修改的,因為它修改的是迴圈變數x而不是列表L。應該使用L[i]  +=1的索引來控制修改。

  zip()可以用於for並行修改多個物件時的情況(按最短的截斷)。它也可以用來再迴圈中建立列表:for (k,v) in zip(keys, vals):D[k]=v。

  enumerate()用於產生偏移和元素:for (offset,item) in enumerate(S): print offset,item

  基本的列表解析:L=[x+10 for x in L]

  擴充套件的列表解析,刪除檔案中的換行符:

lines = [line.rstrip() for line in open('script1.py') if line[0] =='p']

  從檔案中逐行讀取文字行的最佳方法是不要刻意去讀:

for line in open('script1.py'):
    print line.upper()

第十四章 文件

__doc__屬性封裝了物件上的文件,通過它可以檢視(比如函式的)註釋。

  文件字串被認為最是用於較大、功能性的文件,而#最好只限於關於費解的表示式或語句的微型文件。PyDoc系統能夠將前者取出並顯示。

第十五章 函式基礎

def是可執行程式碼,直到運行了def時其定義的函式才開始存在。

  由於函式的引數沒有型別規定,因此可以很方便地實現多型:

複製程式碼
>>>def times(x,y):
...        return x*y
...

>>>times(2,4)
8
>>>times('Ni',4)
'NiNiNiNi'
複製程式碼

第十六章 作用域與引數

變數名解析的LEGB原則:變數名引用分為三個作用域進行查詢,本地作用域(L,每次呼叫函式時建立)、上一級呼叫的本地作用域(E)、全域性作用域(G,模組作用域)、內建作用域(B,預定義的變數名如open)。僅對簡單變數生效,對於特定物件的變數如object.spam,查詢規則規則完全不同。

  內建作用域是一個名為__builtin__的內建模組,import後才可以使用,這時可以用dir(__buildin__)檢視預定義的變數名。根據LEGB原則,在本地作用域定義一個新的open = 'spam'會導致open()函式不能被呼叫。

  global用於全域性變數宣告。

  作者認為在模組匯入時,匯入其他模組的模組擁有了對其他模組變數的修改權,這使得被匯入模組的維護變得複雜:維護者不知道第二個模組什麼情況下會修改變數。因此最好的解決辦法是不這樣做,在檔案間進行通訊的最好辦法就是通過呼叫函式,傳遞引數,然後獲得返回值(用函式提供修改變數的介面,並且告訴維護者這個變數可以被其他模組改變)。

  工廠函式(又稱閉合),是能記住巢狀作用域的變數值的函式。示例:

複製程式碼
>>> def maker(N):
...     def action(X):
...             return X ** N
...     return action
... 
>>> f = maker(2)
>>> f #顯示f的引用
<function action at 0xb7738294>
>>> f(3)
9
>>> f(4)
16
>>> g = maker(3)
>>> g(3)
27
>>> f(3)
9
複製程式碼

  對於函式引數,不可變引數是通過傳值來傳遞,可變物件(列表、字典等)是通過傳引用進行傳遞的。

  多個返回值的常用return方式是使用元組。

  函式引數的四種匹配方式:位置匹配、關鍵字引數、所有物件、所有關鍵字/值:

複製程式碼
>>>def f(a,b,c):print a,b,c #常規匹配
>>>f(1,2,3)
1 2 3

>>>f(c=3,b=2,a=1) #關鍵字匹配
1 2 3

>>>f(1,c=3,b=2) #位置+關鍵字匹配
1 2 3

>>>def f(a,b=2,c=3):print a,b,c #預設引數
>>>f(1)
1 2 3
>>>f(a=1)
1 2 3

>>>def f(*args):print args # 任意引數*
>>>f()
()
>>>f(1)
(1,)
>>>f(1,2,3,4)

>>>def f(**args):print args # 任意引數**,把引數組裝成為字典
>>>f()
{}
>>>f(a=1,b=2)
{'a'=1,'b'=2}
複製程式碼

相反地,呼叫函式在引數變數前面加*可以分解引數。

  引數匹配順序:呼叫和定義中先是非關鍵字引數(name)、然後是關鍵字引數(name=value)、*name最後是**name。

第17章 函式的高階話題

def f(x,y,z):return x+y+z和f= lambda x,y,z:x+y+z會達到同樣的效果。lambda是一個表示式,而不是語句,允許出現在def不能出現的地方。正是因為這個特點,lambda比def的使用更加靈活,比如編寫跳轉表(也即行為的列表或字典):L=[(lambda x:x**2),[(lambda x:x**3),[(lambda x:x**4)]。出於程式碼易讀性的考慮,應儘量避免巢狀的lambda。

  apply的介紹略過,它可以用*和**型引數代替。(似乎在Python3.0以上版本已廢棄,待確認)

  map(func,arg)可以很方便的用函式func處理列表型別的資料,而自己編寫類似的功能需要使用for來完成。

  filter和reduce這兩個函式工具分別用於列表過濾和列表全元素的逐個運算。

  關於列表解析,帶if條件的之前已提過,不再重複。for的應用示例:

>>> res = [x+y for x in [0,1,2] for y in [100,200,300]]
>>> res
[100, 200, 300, 101, 201, 301, 102, 202, 302]

更進一步的巢狀:

>>>[(x,y) for x in range(5) if x%2 ==0 for y in range(5) if y%2==0]

作者在這裡開了個小玩笑:“而map和filter的等效形式往往更復雜也會有深層的巢狀,這裡不進行說明,將這部分程式碼留給禪師、前LISP程式設計師以及犯罪神經病作為練習”。

  生成器函式與一般函式不同之處在於,它yield而不是return一個值,並把自己掛起,現場儲存在下一次呼叫。為與列表解析相區分,可以使用圓括號作為生成器表示式:

>>> for num in (x **2 for x in range(4)):
...     print '%s,%s' %(num,num/2.0)
... 
0,0.0
1,0.5
4,2.0
9,4.5

  一個測試不同的迭代方法的小程式。當然,對於不同的操作,不同方法的相對速度可能不一樣,不存在所有情況下都最快的“最優方法”:

複製程式碼
#file timerseqs.py
import time,sys
reps = 1000
size = 10000

def tester(func, *args):
    startTime = time.time()
    for i in range(reps):
        func(*args)
    elapsed = time.time() - startTime
    return elapsed

def forStatement():
    res = []
    for x in range(size):
        res.append(abs(x))

def listComprehension():
    res = [abs(x) for x in range(size)]

def mapFunction():
    res = map(abs, range(size))

def generatorExpression():
    res = list(abs(x) for x in range(size))

print sys.version
tests = (forStatement, listComprehension, mapFunction,generatorExpression)
for testfunc in tests:
    print testfunc.__name__.ljust(20), '=>',tester(testfunc)
複製程式碼

  陷阱:本地變數是靜態檢測的。這意味著如果在模組裡定義了X=99,def一個函式print X後又在函式裡X=88,那麼就會報錯。

  陷阱:預設物件在def時賦值,而不是呼叫函式時賦值。

第18章 模組:巨集偉藍圖

Python進行import時搜尋目錄的順序:主目錄、PYTHONPATH環境變數目錄、標準庫目錄、.pth目錄。

第19章 模組程式碼編寫基礎 

將會被用於匯入的模組檔案命名需要以.py做結尾。

  當兩個不同模組使用了相同的變數名時,不能用from,只能用import。

  (本章大部分內容都在第三章介紹過)

第20章 模組包

import時列出路徑名稱,以點號相隔:import dir1.dir2.mod。這與平臺無關,import不能使用平臺特定的路徑表達方式。同時,這也表明檔名省略了.py的原因。另外,dir1和dir2中必須包含一個__init__.py檔案(可以為空,Python首次進入其所在目錄時會執行它的內容)。每次使用路徑必須完整輸入,使用import dir1.dir2.mod as mod中定義的mod代替前面過長的路徑名可以解決這個問題。

  個人認為,模組包是為了方便同名模組的使用不發生混淆的方式,這是軟體開發時所需要的。

第21章 高階模組話題

_X的命名方式可以防止from *匯入這個變數,然而這種方法不能阻止其他匯入方式的匯入,並不是一些面嚮物件語言中的私有宣告。

  __all__會列出from *複製的變數名,與_X正相反。同樣只對from *有效,不是私有宣告。

  from __feature__ import featurename還不是很理解,好象是用選用擴充套件功能的方式開啟特殊的程式碼編譯。

  模組可以通過檢測自己的__name__是否為"__main__"確定它是在執行還是被匯入。這樣可以讓模組在扮演兩種不同角色時發揮不同功能。

  相對匯入:路徑以一個點開始,定位同一個包的模組。可以開啟__feature__中強迫匯入的絕對性。很類似於Linux,兩個點表示上一級路徑。

  陷阱一:頂層程式碼的語句次序。被import時模組的頂層程式碼會立即執行,此時它所引用後文定義的變數將無效。

  陷阱二:字串變數是不能直接用於import語句的。可以使用exec "import" + modname來使用字串modname。這樣做仍然有個缺點,每次執行時必須編譯import語句。更好的代替方案是string = __import__(modname),然後把string單列一行執行即可。

  陷阱三:from複製變數名而不是拷貝。

複製程式碼
#nested1.py
X = 99
def printer():print X

#nested2.py
from nested1 import X,printer
X = 88
printer()

%python nested2.py
99
複製程式碼

  陷阱四:reload不影響from匯入。為了更新變數,使用.運算子來匯入和修改其他模組的變數。

  陷阱五:reload、from及互動模式測試。這部分比較有啟發性,建議在原書仔細閱讀,簡要概括就還是:匯入後(模組)要過載(模組),過載後還要重新執行import(變數)。reload和from的合作並不完美,最佳原則是使用reload和import來啟動程式。

  陷阱六:過載沒有傳遞性。過載A不會過載A中import的B和C。需要這種功能時可以自己編寫一個通用工具。

reload_all

  陷阱七:遞迴匯入。

第22章 OOP:巨集偉藍圖

  屬性通常是在class語句中通過賦值語句新增在類中,而不是在定義類時嵌入。因此對沒有賦值的物件屬性的訪問會出錯。

  類方法函式第一個引數通常為self(呼叫時不指明),但不一定叫self,位置是關鍵(來自習題5)。作為類方法直接呼叫時,需指明例項的名稱(24章)。

  Python的OOP模型其實就是在物件樹中搜索屬性。

  (筆者有部分OOP基礎,因此本章具體理論和理解略去)

第23章 類程式碼編寫基礎

  類其實也是一種物件。

  在類定義外建立的函式也可以成為方法:

>>>def upperName(self):
...        return self.name.upper()

>>>rec.method = upperName

第24章 類程式碼編寫細節

  和def一樣,class也是可執行程式碼,執行時才會產生類物件。

  呼叫超類的構造器是可以的,在子類的構造方法中使用Super.__init__()即可。

  抽象超類有的方法沒有提供實現,而是由子類提供。

  類的運算子過載通過修改諸如__add__(對應於+)等方法來實現。具體細節請參考原書。下面是一個修改__iter__獲得使用者定義的迭代器的例子:

View Code

   __setattr__的修改可以模擬實現