天天看點

廖雪峰Python學習記錄

Python基礎

  1. Python程式是大小寫敏感的。Python使用縮進來組織代碼塊,請務必遵守約定俗成的習慣,堅持使用4個空格的縮進。在文本編輯器中,需要設定把Tab自動轉換為4個空格,確定不混用Tab和空格。
  2. Python允許在數字中間以_分隔,是以,寫成10_000_000_000和10000000000是完全一樣的。十六進制數也可以寫成0xa1b2_c3d4。
  3. 轉義字元\可以轉義很多字元,比如\n表示換行,\t表示制表符,字元\本身也要轉義,是以\表示的字元就是\,Python還允許用r’ ‘表示’ ’ 内部的字元串預設不轉義,為了簡化,Python允許用’’’…’’'的格式表示多行内容。
  4. 變量名必須是大小寫英文、數字和_的組合,且不能用數字開頭。
  5. 在計算機記憶體中,統一使用Unicode編碼,當需要儲存到硬碟或者需要傳輸的時候,就轉換為UTF-8編碼。用記事本編輯的時候,從檔案讀取的UTF-8字元被轉換為Unicode字元到記憶體裡,編輯完成後,儲存的時候再把Unicode轉換為UTF-8儲存到檔案。浏覽網頁的時候,伺服器會把動态生成的Unicode内容轉換為UTF-8再傳輸到浏覽器:
字元 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101
  1. Python對bytes類型的資料用帶b字首的單引号或雙引号表示:x = b’ABC’

    eg :>>> ‘ABC’.encode(‘ascii’)

    b’ABC’

    >>> ‘中文’.encode(‘utf-8’)

    b’\xe4\xb8\xad\xe6\x96\x87’

    >>> ‘中文’.encode(‘ascii’) (× 含有中文的str無法用ASCII編碼,因為中文編碼的範圍超過了ASCII編碼的範圍,Python會報錯。)

  2. 用%%來表示一個%,%x表示十六進制整數

    eg:>>> print(’%2d-%02d’ % (3, 1)) output: 3-01

  3. Python内置的一種資料類型是清單:list。list是一種有序的集合,可以随時添加和删除其中的元素。list中追加元素到末尾:classmates.append(‘Adam’);把元素插入到指定的位置,比如索引号為1的位置: classmates.insert(1, ‘Jack’);删除list末尾的元素,用pop()方法:>>> classmates.pop()
  4. 另一種有序清單叫元組:tuple。tuple和list非常類似,但是tuple一旦初始化就不能修改,隻有1個元素的tuple定義時必須加一個逗号,,來消除歧義:

    eg:>>> t = (1,) >>> t ouput (1,)

  5. break語句可以在循環過程中直接退出循環,而continue語句可以提前結束本輪循環,并直接開始下一輪循環。這兩個語句通常都必須配合if語句使用。要特别注意,不要濫用break和continue語句。break和continue會造成代碼執行邏輯分叉過多,容易出錯。大多數循環并不需要用到break和continue語句,上面的兩個例子,都可以通過改寫循環條件或者修改循環邏輯,去掉break和continue語句。有些時候,如果代碼寫得有問題,會讓程式陷入“死循環”,也就是永遠循環下去。這時可以用Ctrl+C退出程式,或者強制結束Python程序。
  6. 和list比較,dict有以下幾個特點:
  • 查找和插入的速度極快,不會随着key的增加而變慢;
  • 需要占用大量的記憶體,記憶體浪費多。

    而list相反:

  • 查找和插入的時間随着元素的增加而增加;
  • 占用空間小,浪費記憶體很少。

    是以,dict是用空間來換取時間的一種方法。

    dict可以用在需要高速查找的很多地方,在Python代碼中幾乎無處不在,需要牢記的第一條就是dict的key必須是不可變對象。

    這是因為dict根據key來計算value的存儲位置,如果每次計算相同的key得出的結果不同,那dict内部就完全混亂了。這個通過key計算位置的算法稱為雜湊演算法(Hash)。要保證hash的正确性,作為key的對象就不能變。在Python中,字元串、整數等都是不可變的,是以,可以放心地作為key。而list是可變的,就不能作為key.

  1. set和dict類似,也是一組key的集合但不存儲value。由于key不能重複,是以,在set中,沒有重複的key。

函數

(忘儲存了,懶得補)

進階特性

  1. 切片

    eg:前10個數,每兩個取一個:

>>> L[:10:2]
[0, 2, 4, 6, 8]
           

2.清單生成式則可以用一行語句代替循環生成上面的list:

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
           

for循環後面還可以加上if判斷,這樣我們就可以篩選出僅偶數的平方:

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
           

還可以使用兩層循環,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
           

運用清單生成式,可以寫出非常簡潔的代碼。例如,列出目前目錄下的所有檔案和目錄名,可以通過一行代碼實作:

>>> import os # 導入os子產品,子產品的概念後面講到
>>> [d for d in os.listdir('.')] # os.listdir可以列出檔案和目錄
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']
           
  1. 生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。把list、dict、str等Iterable變成Iterator可以使用iter()函數:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
           

! !生成器有點略不懂,之後碰到執行個體再補。

函數式程式設計

  1. 函數式程式設計就是一種抽象程度很高的程式設計範式,純粹的函數式程式設計語言編寫的函數沒有變量,是以,任意一個函數,隻要輸入是确定的,輸出就是确定的,這種純函數我們稱之為沒有副作用。而允許使用變量的程式設計語言,由于函數内部的變量狀态不确定,同樣的輸入,可能得到不同的輸出,是以,這種函數是有副作用的。函數式程式設計的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許傳回一個函數!Python對函數式程式設計提供部分支援。由于Python允許使用變量,是以,Python不是純函數式程式設計語言。
  2. 函數本身也可以指派給變量,即:變量可以指向函數。而函數名其實就是指向函數的變量。
  3. 既然變量可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
  4. Python内建了 map() 和 reduce() 函數。/filter()/sorted()
  • map()函數接收兩個參數,一個是函數,一個是Iterable,map将傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator傳回。map()作為高階函數,事實上它把運算規則抽象了,是以,我們不但可以計算簡單的f(x)=x*x,還可以計算任意複雜的函數,比如,把這個list所有數字轉為字元串:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
隻需要一行代碼。
           
  • reduce的用法。reduce把一個函數作用在一個序列[x1, x2, x3, …]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:

但是如果要把序列[1, 3, 5, 7, 9]變換成整數13579,reduce就可以派上用場:

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
           
  • filter()把傳入的函數依次作用于每個元素,然後根據傳回值是True還是False決定保留還是丢棄該元素。
def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 結果: ['A', 'B', 'C']
           
  • sorted()函數就可以對list進行排序:
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
           

sorted()函數也是一個高階函數,它還可以接收一個key函數來實作自定義的排序,例如按絕對值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
           

預設情況下,對字元串排序,是按照ASCII的大小比較的,由于’Z’ < ‘a’,結果,大寫字母Z會排在小寫字母a的前面。

現在,我們提出排序應該忽略大小寫,按照字母序排序。忽略大小寫來比較兩個字元串,實際上就是先把字元串都變成大寫(或者都變成小寫),再比較。

這樣,我們給sorted傳入key函數,即可實作忽略大小寫的排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
           

要進行反向排序,不必改動key函數,可以傳入第三個參數reverse=True:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
           

假設我們用一組 tuple 表示學生名字 和 成績:

L = [ ( ‘Bob,75’ ) , ( ‘Adam’,92 ) , ( ‘Bart’,66 ) , ( ‘Lisa’,88 ) ]

請用 sorted() 對上述清單分别按名字和成績高到低排序

def by_name(t):
    return t[0].lower()
    return t[0]
L=[('Dob',75),('Adam',92),('Bart',66),('Cisa',88)]  
L1=sorted(L,key=by_name)
print(L1)   
           
def by_scort(t):
    return 100-t[1]
L=[('Bob',75),('Adam',92),('Bart',66),('Lisa',88)]
L1=sorted(L,key=by_scort)
print(L1)  
           
傳回函數
  1. 傳回閉包時牢記一點:傳回函數不要引用任何循環變量,或者後續會發生變化的變量。
  2. 一個函數可以傳回一個計算結果,也可以傳回一個函數。傳回一個函數時,牢記該函數并未執行,傳回函數中不要引用任何可能會變化的變量。
匿名函數
  1. 關鍵字lambda表示匿名函數,冒号前面的x表示函數參數。
  2. 匿名函數有個限制,就是隻能有一個表達式,不用寫return,傳回值就是該表達式的結果。
  3. 用匿名函數有個好處,因為函數沒有名字,不必擔心函數名沖突。此外,匿名函數也是一個函數對象,也可以把匿名函數指派給一個變量,再利用變量來調用該函數:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
           

同樣,也可以把匿名函數作為傳回值傳回,比如:

def build(x, y):
    return lambda: x * x + y * y
           
裝飾器
  1. 函數對象有一個__name__屬性,可以拿到函數的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
           
  1. 現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日志,但又不希望修改now()函數的定義,這種在代碼運作期間動态增加功能的方式,稱之為“裝飾器”(Decorator)。
  2. 本質上,decorator就是一個傳回函數的高階函數。是以,我們要定義一個能列印日志的decorator,可以定義如下:
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
           
子產品
  1. 注意到這兩行代碼:
if __name__=='__main__':
    test()
           

當我們在指令行運作hello子產品檔案時,Python解釋器把一個特殊變量__name__置為__main__,而如果在其他地方導入該hello子產品時,if判斷将失敗,是以,這種if測試可以讓一個子產品通過指令行運作時執行一些額外的代碼,最常見的就是運作測試。

  1. 作用域

    在一個子產品中,我們可能會定義很多函數和變量,但有的函數和變量我們希望給别人使用,有的函數和變量我們希望僅僅在子產品内部使用。在Python中,是通過**_**字首來實作的。

  2. 類似__xxx__這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊變量,hello子產品定義的文檔注釋也可以用特殊變量__doc__通路,我們自己的變量一般不要用這種變量名;類似_xxx和__xxx這樣的函數或變量就是非公開的(private),不應該被直接引用,比如_abc,__abc等;之是以我們說,private函數和變量“不應該”被直接引用,而不是“不能”被直接引用,是因為Python并沒有一種方法可以完全限制通路private函數或變量,但是,從程式設計習慣上不應該引用private函數或變量。

    private函數或變量不應該被别人引用,那它們有什麼用呢?請看例子:

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)
           
面向對象
  1. 面向對象最重要的概念就是類(Class)和執行個體(Instance),必須牢記類是抽象的模闆,比如Student類,而執行個體是根據類建立出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的資料可能不同。
  2. 通過定義一個特殊的__init__方法,在建立執行個體的時候,就把name,score等屬性綁上去:
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score
           

注意: 特殊方法__init__ 前後分别有兩個下劃線!!!

注意到__init__方法的第一個參數永遠是self,表示建立的執行個體本身,是以,在__init__方法内部,就可以把各種屬性綁定到self,因為self就指向建立的執行個體本身。

有了__init__方法,在建立執行個體的時候,就不能傳入空的參數了,必須傳入與__init__方法比對的參數,但self不需要傳,Python解釋器自己會把執行個體變量傳進去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
           

和普通的函數相比,在類中定義的函數隻有一點不同,就是第一個參數永遠是執行個體變量self,并且,調用時,不用傳遞該參數。除此之外,類的方法和普通函數沒有什麼差別,是以,你仍然可以用預設參數、可變參數、關鍵字參數和命名關鍵字參數。

  1. 類是建立執行個體的模闆,而執行個體則是一個一個具體的對象,各個執行個體擁有的資料都互相獨立,互不影響;方法就是與執行個體綁定的函數,和普通函數不同,方法可以直接通路執行個體的資料;通過在執行個體上調用方法,我們就直接操作了對象内部的資料,但無需知道方法内部的實作細節。和靜态語言不同,Python允許對執行個體變量綁定任何資料,也就是說,對于兩個執行個體變量,雖然它們都是同一個類的不同執行個體,但擁有的變量名稱都可能不同:
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
           
  1. 如果要讓内部屬性不被外部通路,可以把屬性的名稱前加上兩個下劃線__,在Python中,執行個體的變量名如果以__開頭,就變成了一個私有變量(private),隻有内部可以通路,外部不能通路。
  2. 繼承有什麼好處?最大的好處是子類獲得了父類的全部功能。
  3. 當子類和父類都存在相同的run()方法時,我們說,子類的run()覆寫了父類的run(),在代碼運作的時候,總是會調用子類的run()。這樣,我們就獲得了繼承的另一個好處:多态。多态的好處就是,當我們需要傳入Dog、Cat、Tortoise……時,我們隻需要接收Animal類型就可以了,因為Dog、Cat、Tortoise……都是Animal類型,然後,按照Animal類型進行操作即可。由于Animal類型有run()方法,是以,傳入的任意類型,隻要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多态的意思:對于一個變量,我們隻需要知道它是Animal類型,無需确切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運作時該對象的确切類型決定,這就是多态真正的威力:調用方隻管調用,不管細節,而當我們新增一種Animal的子類時,隻要確定run()方法編寫正确,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
  • 對擴充開放:允許新增Animal子類;
  • 對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
  1. 繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關系。而任何類,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒着的樹。繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類隻需要新增自己特有的方法,也可以把父類不适合的方法覆寫重寫。動态語言的鴨子類型特點決定了繼承不像靜态語言那樣是必須的。
  2. 我們要判斷class的類型,可以使用isinstance()函數。
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
并且還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
           
  • 如果要獲得一個對象的所有屬性和方法,可以使用dir()函數,它傳回一個包含字元串的list,比如,獲得一個str對象的所有屬性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
           
  1. 在編寫程式的時候,千萬不要對執行個體屬性和類屬性使用相同的名字,因為相同名稱的執行個體屬性将屏蔽掉類屬性,但是當你删除執行個體屬性後,再使用相同的名稱,通路到的将是類屬性。
面向對象的進階程式設計
  1. 使用__slots__

    如果我們想要限制執行個體的屬性怎麼辦?比如,隻允許對Student執行個體添加name和age屬性。為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class執行個體能添加的屬性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
           
然後,我們試試:
>>> s = Student() # 建立新的執行個體
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于'score'沒有被放到__slots__中,是以不能綁定score屬性,試圖綁定score将得到AttributeError的錯誤。
           
使用__slots__要注意,__slots__定義的屬性僅對目前類執行個體起作用,對繼承的子類是不起作用的:
           
>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
           
除非在子類中也定義__slots__,這樣,子類執行個體允許定義的屬性就是自身的__slots__加上父類的__slots__。
           
  1. Python内置的@property裝飾器就是負責把一個方法變成屬性調用的:
class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
           
  • 把一個getter方法變成屬性,隻需要加上@property就可以了,此時,@property本身又建立了另一個裝飾器@score.setter,負責把一個setter方法變成屬性指派,于是,我們就擁有一個可控的屬性操作:
>>> s = Student()
>>> s.score = 60 # OK,實際轉化為s.set_score(60)
>>> s.score # OK,實際轉化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!
           
  • 注意到這個神奇的@property,我們在對執行個體屬性操作的時候,就知道該屬性很可能不是直接暴露的,而是通過getter和setter方法來實作的。還可以定義隻讀屬性,隻定義getter方法,不定義setter方法就是一個隻讀屬性:
class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth
           
  • 上面的birth是可讀寫屬性,而age就是一個隻讀屬性,因為age可以根據birth和目前時間計算出來。
  1. 多重繼承——MixIn的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的複雜的繼承關系。
為了更好地看出繼承關系,我們把Runnable和Flyable改為RunnableMixIn和FlyableMixIn。類似的,你還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓某個動物同時擁有好幾個MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
           
  1. 定制類
  • __str__ / __iter__  /__getitem__  /__getattr__  /__call__
               
>>> def fn(self, name='world'): # 先定義函數
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
           
  • 要建立一個class對象,type()函數依次傳入3個參數:1. class的名稱;2. 繼承的父類集合,注意Python支援多重繼承,如果隻有一個父類,别忘了tuple的單元素寫法;3. class的方法名稱與函數綁定,這裡我們把函數fn綁定到方法名hello上。通過type()函數建立的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的文法,然後調用type()函數建立出class。正常情況下,我們都用class Xxx…來定義類,但是,type()函數也允許我們動态建立出類來,也就是說,動态語言本身支援運作期動态建立類,這和靜态語言有非常大的不同,要在靜态語言運作期建立類,必須構造源代碼字元串再調用編譯器,或者借助一些工具生成位元組碼實作,本質上都是動态編譯,會非常複雜。
  1. metaclass,直譯為元類,簡單的解釋就是:當我們定義了類以後,就可以根據這個類建立出執行個體,是以:先定義類,然後建立執行個體。 但是如果我們想建立出類呢?那就必須根據metaclass建立出類,是以:先定義metaclass,然後建立類。連接配接起來就是:先定義metaclass,就可以建立類,最後建立執行個體。 是以,metaclass允許你建立類或者修改類。換句話說,你可以把類看成是metaclass建立出來的“執行個體”。
錯誤、調試和測試
  1. 常見的錯誤類型和繼承關系看這裡:

    https://docs.python.org/3/library/exceptions.html#exception-hierarchy

  2. 斷言

    凡是用print()來輔助檢視的地方,都可以用斷言(assert)來替代:

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')
           
如果斷言失敗,assert語句本身就會抛出AssertionError:
           
$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!
           
程式中如果到處充斥着assert,和print()相比也好不到哪去。不過,啟動Python解釋器時可以用-O參數來關閉assert:
           
$ python -O err.py
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
           
  • 注意:斷言的開關“-O”是英文大寫字母O,不是數字0。關閉後,你可以把所有的assert語句當成pass來看。
  1. pdb.set_trace()

    這個方法也是用pdb,但是不需要單步執行,我們隻需要import pdb,然後,在可能出錯的地方放一個pdb.set_trace(),就可以設定一個斷點:

# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 運作到這裡會自動暫停
print(10 / n)
           
  • 運作代碼,程式會自動在pdb.set_trace()暫停并進入pdb調試環境,可以用指令p檢視變量,或者用指令c繼續運作:
$ python err.py 
> /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
  File "err.py", line 7, in <module>
    print(10 / n)
ZeroDivisionError: division by zero
           
這個方式比直接啟動pdb單步調試效率要高很多,但也高不到哪去。
           
IO程式設計
with open('/path/to/file', 'r') as f:
    print(f.read())
           
  • 調用read()會一次性讀取檔案的全部内容,如果檔案有10G,記憶體就爆了,是以,要保險起見,可以反複調用read(size)方法,每次最多讀取size個位元組的内容。另外,調用readline()可以每次讀取一行内容,調用readlines()一次讀取所有内容并按行傳回list。是以,要根據需要決定怎麼調用。如果檔案很小,read()一次性讀取最友善;如果不能确定檔案大小,反複調用read(size)比較保險;如果是配置檔案,調用readlines()最友善:
for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉