1. 程式人生 > >《Python程式設計從入門到實踐》基礎知識個人筆記

《Python程式設計從入門到實踐》基礎知識個人筆記

【美】Eric Matthes 2016年7月第1版

第2章 變數和簡單資料型別

2.2 變數

2.2.1 變數的命名規則

  • 變數名字只能包含字母、數字和下劃線
  • 且不能以數字開頭
  • 不能包含空格
  • 不要使用內建關鍵字和函式名
  • 變數名要見名識義
  • 慎用小寫字母l和大寫字母O

2.3 字串

2.3.1 使用方法修改字串的大小寫

  • title( )首寫字母大寫的方式顯示每個單詞
name = "peter"
print(name.title())
  • upper( )將字串改為全部大寫
  • lower( )將字串改為全部小寫

2.3.2 合併(拼接)字串

  • 可以直接使用+來合併字串

2.3.3 使用製表符或換行符來新增空白

  • 製表符\t(4個空格)
  • 換行符\n

2.3.4 刪除空白

  • strip( )刪除字串開頭和結尾多餘的空白
  • ltrip( )刪除字串開頭多餘的空白
  • rtrip( )刪除字串結尾多餘的空白

注意:這種刪除只是暫時的,再次訪問變數值時會恢復,要永久刪除空白,必須刪除操作過後存回變數中。

name = "  peter "
name = name.strip()

第3章 列表簡介

3.1 列表是什麼

  • 列表是一系列按特定順序排列的元素組合
  • 用方括號[ ]表示列表,並用逗號分隔其中的元素
  • 通常用複數的名稱命名,如names,letters

3.1.1 訪問列表元素

  • 通過元素索引並將其放在方括號內(注意索引差一的特徵)
    print(list[0])
  • 將索引指定為-1時,返回列表中最後一個元素

3.2.1 修改列表元素

  • 指定列表名和要修改的元素索引,再賦予新值
name[0] = "Jack"

3.2.2 在列表中新增元素

  • append( )在列表末尾新增元素
    name.append("Ken")
  • insert( )在列表的指定位置新增新元素
    name.insert(0,"Mark")

3.2.3從列表中刪除元素

1.使用del

語句刪除元素(前提要知道刪除元素的位置)

  • del name[0]值從列表刪除後無法再訪問

2、使用方法pop( )刪除元素

  • 該方法可以刪除列表末尾的元素,並接著使用它的值(彈出)
    name_pop = name.pop()

3、彈出列表中任何位置處的元素
name_pop = name.pop(0)

注意:每當使用pop( )時,被彈出的元素就不在該列表中了

4、根據值刪除元素

  • remove( )只有知道要刪除元素的值時使用
  • 使用 remove( )從列表中刪除元素時,也可以接著使用它的值

注意:方法remove( )只刪除第一個指定的值。如果要刪除的值可能在列表中出現多次,就需要使用迴圈來判斷是否刪除了所有這樣的值。

3.3 組織列表

3.3.1 使用方法sort()對列表進行永久性排序

  • 可以實現列表按字母順序排列,傳遞引數reverse = True可以相反順序排列元素
    name.sort(reverse = True)

3.3.2 使用函式sorted()對列表進行臨時排序

3.3.3 倒著列印列表

  • 使用方法reverse()反轉列表元素的排列順序,name.reverse()
  • 雖然也是永久性地修改列表元素的排列順序,但可以隨時再次呼叫reverse()恢復原來的排列順序

3.3.4 確定列表的長度

  • len( )函式可以快速獲悉列表的長度(列表包含多少個元素)

第4章 操作列表

4.1 遍歷(for迴圈)

4.3 建立數值列表

4.3.1 使用函式range( )

  • 該函式有差一特徵,要列印數字1~5,使用程式碼range(1,6)
  • 使用range()建立數字列表,並可以設定步長
    numnbers = list(range(2,11,2))

4.3.4 列表解析

  • 列表解析將for迴圈和建立新元素的程式碼合併成一行,並自動附加新元素
    squares = [value**2 for value in range(1,11)]

4.4 列表切片

  • 差一行為:要輸出列表中的前三個元素,需要指定索引0~3,分別輸出為0,1和2的元素numbers[0:3]
  • 要提取列表的第2~4個元素,可將起始索引指定為1,終止索引指定為4number[1:4]
  • 若起始索引省略,代表從列表頭開始number[:3],同樣終止索引也可以省略,代表到列表尾number[2:]
  • 負數索引返回離列表末尾相應距離的元素,如輸出最後三個元素number[-3:]
  • 複製列表,方法是同時省略起始索引和終止索引number[:]

4.5 元組

  • 不能修改的值稱為不可變的,而不可變的列表被稱為元組,使用圓括號()標識

4.5.3 修改元組變數

  • 雖然不能修改元組的元素,但可以給儲存元組的變數賦值,因為給元組變數賦值是合法的
demensions = (200,50)
demensions = (400,100)

4.6 設定程式碼格式

  • Python改進提案(Python Enhancement Proposal,PEP),其中PEP8是最古老的PEP之一
  • 程式碼行長建議不超過80字元,註釋行長不超過72字元,不可逾越紅線99字元

第5章 if語句

5.2 條件測試

  • 檢查是否相等用==,一個等號是陳述,兩個等號是發問
  • 檢查是否相等時區分大小寫
  • 檢查是否不相等用=!
  • 檢查多個條件,age >= 10 and age<= 21
  • 檢查特定值是否包含在列表中,使用關鍵字in,如'paul' in names返回布林值
  • 檢查特定值是否不包含在列表中,使用關鍵字not in

5.3.3 if-elif-else語句

  • 該語句只執行結構中的一個程式碼塊,它依次檢查每個條件測試,直到遇到通過了的條件測試
  • 並不要求if-elif結構後面必須有else程式碼塊

5.3.6 測試多個條件

  • 當需要檢查你關係的所有條件時,應使用一些列不包含elif和else程式碼塊的簡單if語句

5.4 使用if語句處理列表

  • 檢查特殊元素,先用for語句遍歷,再用if語句判斷元素具體情況
  • 確定列表不為空,先用if語句判斷列表是否為空,再if程式碼塊中執行for迴圈,否則執行else程式碼塊
  • 使用多個列表,檢查列表1中的元素是否在列表2中
list_1 = [1,2,3,4,5]
list_2 = [1,2,5,6,7]
for i in list_1:
    if i in list_2:
        print("yes")
    else:
        print("no")

第6章 字典

6.2 使用字典

  • 用放在花括號{}中的一些列鍵-值對錶示,如student = {"name":"paul","age":28}
  • 可以利用字典的鍵名訪問相關聯的值,如student['name']
  • 指定字典名、用方括號括起來的鍵和相關聯的值,新增鍵-值對,如student[score] = 100
  • 注意,鍵-值對的排列順序與新增順序不同,Python不關心鍵-值對的新增順序,而只關心鍵和值之間的關聯關係
  • 使用字典來儲存使用者提供的資料或在編寫自動生成大量鍵-值對的程式碼時,通常都需要先定義一個空字典
  • 要修改字典的值,直接用原字典名和鍵名來重新賦值,如student[score] = 99
  • 要刪除鍵-值對,可以使用del語句,但必須指定字典名和要刪除的鍵,如del student["name"],刪除的鍵-值對永遠消失
  • 使用較長的列表或字典時,使用以下格式設定方式:
student_score = {
    "a":80,
    "b":90,
    "c":100,
    }

6.3 遍歷字典

  • 有多種遍歷字典的方式:可以遍歷字典的所有鍵-值對,鍵或值
  • ①遍歷所有的鍵-值對,用for key,value in student_score.items():,注意,即便遍歷字典時,鍵-值對的返回順序也與儲存順序不同
  • ②遍歷字典中的所有鍵,用for key in student_score.keys():,遍歷字典時會預設遍歷所有鍵,將程式碼改為for key in student_score:輸出將不變
  • ③按順序遍歷字典中的所有鍵,用函式sorted()for name in sorted(student_score.keys()):
  • ④遍歷字典中的所有值,用for value in student_score.values():,該方法沒有考慮值是否重複,為剔除重複項,可使用集合set,它類似與列表,但每個元素都必須是獨一無二的for name in set(student_score.values()):

6.4 巢狀

  • 將一系列字典儲存在列表中,或將列表作為值儲存在字典中,也可以同種型別儲存
aliens = []
#建立30個綠色外星人
for alien_number in range(30):
    new_alien = {"color":"green","points":"5","speed":"slow"}
    aliens,append(new_alien)
    
#修改前3個外星人
for alien in aliens[:3]:
    if alien["color"] == "green":
        alien["color"] = "yellow"
        alien["points"] = 10
        alien["speed"] = "medium"
    elif alien["color"] == "yellow":
        alien["color"] = "red"
        alien["points"] = 15
        alien["speed"] = "fast"
  • 在字典中儲存列表,這種情況下遍歷該字典的for迴圈中,需要再使用一個for迴圈來遍歷
for name,languages in favorite_languages.items():
    print("\n" + name.title() + "'s favorite languages are:")
        for language in languages:
            print("\t" + language.title())
  • 在字典中儲存字典,儘量讓每個鍵包含字典結構都相同,否則for迴圈內部的程式碼將更為複雜

第7章 使用者輸入和while迴圈

7.1 函式input()的工作原理

  • 建立多行字串的方式
prompt = "If you tell us who you are, we can personalize the message you see ."
prompt += "\nwhat is your frist name?"
name = input(prompt)
print("\nHello," + name + "!")
  • 使用函式input()時,將使用者輸入解讀為字串,可用函式int()將字串轉化為數值
  • 求模運算子%,指出餘數是多少

7.2 while迴圈

  • 學會使用標誌控制程式執行:在要求很多條件都滿足才繼續執行的程式中,可定義一個變數,用於判斷整個程式是否處於活動狀態。這個變數被稱為標誌,充當程式的交通訊號燈,可讓程式在表示為True時繼續執行,並在任何事件導致標誌的值為False時讓程式停止執行。
active = True
while active:
    message = input()
    if message == "quit"
        active = False
    else:
        print(message)
  • 使用break退出迴圈
  • 使用continue返回到迴圈開頭,並根據條件測試結果決定是否繼續執行迴圈,不像break語句那樣不再執行餘下程式碼並退出整個迴圈
  • 避免無限迴圈,確認程式至少有一個這樣的地方能讓迴圈條件為False或讓break語句得以執行

7.3 使用while迴圈來處理列表和字典

  • for迴圈是一種遍歷列表的有效方式,但在for迴圈中不應修改列表,否則將導致Python難以跟蹤其中的元素,要在遍歷列表的同時對其進行修改,可使用while迴圈
#先建立一個待驗證使用者列表和一個用於儲存已驗證使用者的空列表
unconfirmed_user = ["a","b","c"]
confirm_users = []
#驗證每個使用者,直到沒有未驗證使用者為止,將每個驗證過的使用者移到已驗證使用者列表中
while unconfirmed_user:
    current_user = unconfirmed.pop()
    print("Verifying user" + current_user.title())
    confirmed_user.append(current.user)
#顯示所有已驗證的使用者
print("\nThe following users have been confirmed:")
for confirmed_user in confirmed_users:
    print(confirmed_user.title())
  • 刪除包含特定值的所有列表元素
while "cat" in pets:
    pets.remove("cat")
  • 使用使用者輸入來填充字典
responses = {}
#設定一個標誌,指出調查問卷是否繼續
polling_active = True
while polling_active:
    #提示輸入被調查者的名字和回答
    name = input("\nWhat is your name?")
    response = input("Which moutain would you like to climb someday")
    #將答卷儲存在字典中
    responses[name] = response
    #看看是否還有人要參加調查
    repeat = input("Would you like to let another person respond?(Y/N)")
    if repeat == "N":
        polling_active = False
#調查結束,顯示調查結果
print(\n---Poll Result---)
for name,response in responses.items():
    print(name + "would like to climb " + response + ".")

第8章 函式

8.1 向函式傳遞資訊

  • 向函式傳遞資訊
def greet_user(username):
    print("Hello," + username.title() + "!")
greet_user("Jesse")
  • 如上述變數username是一個形參,是函式完成其工作所需的一項資訊;值Jesse是一個實參,是呼叫函式時傳遞給函式的具體資訊

8.2 傳遞實參

  • 向函式傳遞實參的方式很多,可是使用位置引數,這要求實參的順序與形參的順序相同;也可以使用關鍵字實參,其中每個實參都有變數名和值組成;還可以使用列表和字典。

    1、位置引數

    呼叫函式時,將函式呼叫中的每個實參都關聯到函式定義中的一個形參,這種關聯方是被稱為位置引數。優點是可以多次呼叫函式,但要注意實參的順序與函式定義中形參的順序一致。

    2、關鍵字實參

    是傳遞給函式的名稱-值對,向函式傳遞時不會混淆,因此無需考慮函式呼叫中的實參順序。

    3、預設值

    編寫函式時,可以給每個形參指定預設值。使用預設值時,在形參列表中必須先列出沒有預設值的形參,再列出有預設值的形參,這樣才能正確地解讀位置實參。

8.3 返回值

在函式中使用return語句將值返回到呼叫函式的程式碼行,返回值讓你能夠將程式的大部分繁重工作移到函式中去完成,從而簡化主程式。

  • 呼叫返回值的函式時,需要提供一個變數,用於儲存返回的值
  • 使用預設值讓實參變成可選的(末尾新增可選形參)
def get_formatted_name(first_name,last_name,middle_name=“ ”):
    if middle_name:         #python將非空字元解讀為True
        full_name = first_name + " " +middle_name + " " + last_name 
    else:
        full_name = first_name + " " + last_name
    return full_name.title()
musician = get_formatted_name("jimi","hendrix")
print(musician)

8.4 向函式傳遞列表

  • 將列表傳遞給函式後,函式就可以對其進行修改,並且函式對這個列表所做的任何修改都是永久性的,這樣能夠高效地處理大量的資料。
  • 使用函式與未使用函式的程式碼相比,組織更為有序,完成大部分工作的程式碼都移到了函式中,讓主程式更容易理解。
  • 禁止函式修改列表的方法,可向函式傳遞列表的副本而不是原件funcition_name(list_name[:]) #用切片的方法建立列表的副本,儘量用原始列表傳遞給函式,讓函式使用現成列表可避免花時間和記憶體建立副本,提高效率,尤其是在處理大型列表時。

8.5 傳遞任意數量的實參

  • 利用帶星號的形參如*toppings,建立一個名為toppings空元祖,並將所有值都封裝到這個元組中。def make_pizza(*toppings):
  • 利用帶雙星號的形參如**user_info,建立一個名為user_info空字典,將任意數量的關鍵字引數封裝到這個字典中。
def build profile(first,last,**user_info):
    profile = {}
    profile["first_name"] = first
    profile["last_name"] = last
    for key, value in user_info.items():
        profile[key] = value
    return profile
  • 當結合使用位置實數和任意數量實參時,python先匹位置實參,任意形參,任意關鍵字實參,預設形參。

8.6 將函式儲存在模組中

  • 函式的優點之一是,可以將程式碼塊與主程式分離,給函式指定描述性名稱,讓主程式更易於理解。
  • 匯入模組的方法

(1)匯入整個模組
編寫一條import並在其中指定模組名import pizza,模組的副檔名為.py的檔案,此時可以使用pizza中定義的所有函式,呼叫函式時需要使用句點pizza.make_pizza()

(2)匯入特定的函式
from module_name import function_01,function_02,from pizza import make_pizza呼叫函式時無需使用句點。

(3)使用關鍵字as給函式指定別名
from module_name import function_name as fn

(4)使用關鍵字as給模組指定別名
import module_name as mn

(5)匯入模組中的所有函式
from module_name import *

  • 呼叫函式時無需使用句點,但最好不要採用這種匯入方法,可能遇到多個名稱相同的函式或變數,進而覆蓋函式。

8.7 函式編寫指南

  • 函式指定描述性名稱,模組命名也同樣。
  • 每個函式都應包含簡要地闡述其功能的註釋,該註釋應緊跟在函式定義後面,並採用文件字串格式。
  • 給形參指定預設值時,等號兩邊不要有空格,對於函式呼叫中的關鍵字實參,也應該遵循這種約定。
  • 如果函式形參很多,可在函式定義中輸入左括號後按回車,下行寫,如:
def function_name(
        parameter_0,parameter_2,parameter_3,
        parameter_4,parameter_5,parameter_6)
    function body.....

第9章 類

9.1 建立和使用類

  • 定義一大類物件一般都有通用行為,基於類建立物件時,每個物件都自動具備這種通用行為,然後可根據需要賦予每個物件獨特的個性
  • 首字母大寫的名稱是指類

(1)方法__inif__()

  • 類中的函式稱為方法
  • Python呼叫__inif__()方法來建立某個例項時,將自動傳入實參self,每個與類相關聯的方法呼叫都自動傳遞實參self,它是一個指向例項本身的引用,讓例項能夠訪問類中的屬性和方法。
  • self為字首的變數都可以供類中的所有方法使用,可以通過類的任何例項來訪問這些變數。通過例項訪問的變數稱為屬性。

9.1.2 根據類建立例項

(1)訪問例項的屬性

  • 使用句點表示法:my_dog.name
  • (2)呼叫方法
  • 指定例項名稱和要呼叫的方法:my_dog.sit()

9.2 使用類和例項

(1)給屬性指定預設值

  • 類中的每個屬性都必須有初始值,有些情況下,可以在方法__init__()內指定這種初始值;如果這樣做了,就無需包含為它提供初始值的形參。
class Car():

    def __init__(self,make,model,year):
        """初始化描述汽車的屬性 """
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
    
my_new_car = Car('audi','a4','2016')

(2)修改屬性的值
可以用三種不同的方法修改屬性的值:直接通過例項進行修改;通過方法進行設定;通過方法進行遞增(增加特定的值)

  • 方法一:
    my_new_car.odometer_reading = 23
  • 方法二,編寫對屬性進行更新的方法,好處是無需直接訪問屬性,而可將值傳遞給一個方法,由它在內進行更新:
class Car():
    --snip--
    
    def update_odometer(self,mileage):
        """將里程錶讀數設定為指定值,並不允許里程錶往回撥"""
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")
  • 方法三:
def increment_odometer(self.miles):
    """將里程錶讀數增加指定的量"""
    self.odometer_reading += miles

9.3 繼承

一個類繼承另外一個類時,它將自動獲得另一個類的所有屬性和方法;原有的類稱為父類,而新類稱為子類。子類繼承了其父類的所有屬性和方法,同時還可以定義自己的屬性和方法。

(1)子類的方法__init__()

class ElectricCar(Car):
    """電動汽車的獨特之處"""
    
    def __init__(self,make,model,year):
        """初始化父類的屬性"""
        super().__init__(self,make,model,year)
  • 定義子類時,必須在括號內指定父類的名稱
  • super()函式能將父類和子類關聯起來,父類也稱為超類,此處super()等價於super(ElectricCar,self)

(2)給子類定義屬性和方法

  • 在方法__init__()中新增

(3)重寫父類的方法

  • 在子類中定義一個與要重寫父類方法同名的方法,Python將不會考慮父類的方法,而只關注在子類定義的相應方法。

(4)將例項用作屬性

  • 當類中的屬性和方法越來越多,可能需要將類的一部分作為一個獨立的類提取出來,並將Battery這個獨立類的例項作為另一個類ElectricCar的屬性。
class Battery():

    def __init__(self,battery_size=70):
        self.battery_size = battery_size

class ElectricCar(Car):

    def __init__(self,make,model,year):
        """初始化父類的屬性,再初始化電動汽車特有的屬性"""
        super()__init__(make,model,year)
        self.battery = Battery()

9.4 匯入類

Python允許將類儲存在模組中,然後在主程式中匯入所需的模組。

  • 匯入單個類
    from car import Car使主程式檔案變得整潔而易於閱讀
  • 從一個模組中匯入多個類
    from car import Car,ElectricCar
  • 匯入整個模組
    import Car
  • 匯入模組中的所有類(不推薦)
    from module_name import *這種方式首先沒有明確指出模使用了模組中的哪些類,其次容易重名引發報錯
  • 在一個模組中匯入另一個模組
    可能會發現一個模組中的類依賴於另外一個模組中的類,這種情況下,可在前一個模組中匯入必要的類

9.5 類編碼風格

  • 類名採用駝峰命名法,即類名的每個單詞的首字母都大寫,而不使用下劃線
  • 例項名和模組名使用小寫格式,並且單詞之間加上下劃線
  • 對於每個類,都應緊跟在定義後包含一個文件字串
  • 利用空行來組織程式碼,在類中,使用一個空行來分隔方法;在模組中,使用兩個空行來分隔類

第10章 檔案和異常

10.1 從檔案中讀取資料

  • 要使用文字檔案中的資訊,首先需要將資訊讀取到記憶體中。
with open("pi_digits.txt") as file_object:   #函式open()返回一個表示檔案的物件,有關鍵字with在,就不再需要訪問檔案後自動將其關閉
    contents = file_object.read()            #方法read()讀取檔案的全部內容
    print(contents)

10.2檔案路徑

  • 上述將檔名傳遞給函式open()時,Python將在當前執行的檔案(即.py程式檔案)所在的目錄中查詢檔案。
  • 在程式資料夾的其他子資料夾中,可以使用相對檔案路徑來開啟資料夾中的檔案,該位置是相對於當前執行的程式所在目錄的。在Linux和OS X中,檔案路徑使用斜槓(/)在Windows系統中,檔案路徑使用反斜槓(),如:`with open('text_files\filename.txt') as file_objetc:
  • 也可以使用絕對檔案路徑,通常比相對路徑更長,可以儲存在一個變數中,便於使用。
  • 由於反斜槓在Python中被視為轉義標記,為在Windows中確保萬無一失,應以原始字串的方式指定路徑,即在開頭的單引號上加上r

(1)逐行讀取
for line in file_object:

(2)建立一個包含檔案各行內容的列表

  • 使用關鍵字with時,open()返回的檔案物件只在with程式碼塊內使用,如果要在with程式碼塊外訪問檔案的內容,可在with程式碼塊內將檔案的各行儲存在一個列表,並在with程式碼塊外使用該列表。
with ope(filename) as file_object:
    lines = file_object.readlines()     #方法readlines()從檔案中讀取每一行,並將其儲存在一個列表中

10.3 寫入檔案

  • 在呼叫open()提供另一個實參with open(filename,"w") as file_object:,w表示以寫入模式開啟檔案,如果指定檔案已存在,將在返回檔案物件前清空該檔案;r表示以讀取模式開啟檔案,忽略實參時預設;a表示以附加模式開啟檔案,如果指定的檔案不存在,將建立一個空檔案。
  • Python只能將字串寫入文字檔案

10.4 異常

  • 異常使用try-except-else程式碼塊處理。
  • 好處:使異常避免崩潰,提供程式抵禦錯誤的能力,避免使用者看到traceback;避免惡意使用者看到程式的檔名稱和部分不能正確執行的程式碼。
print("Give me two numbers,and I'll divide them.")
print("Enter 'q' to quit.)

while True:
    first_number = input("\nFirst number:")
    if first_number == "q":
        break
    second_number = input("\nSecond number:")
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:
        print(answer)

10.4.1 分析文字

  • 方法split()以空格為分隔符將字串拆分成多個部分,並將這些部分都儲存到一個列表中。
>>> title = "Alice in Wonderland"
>>> title.split()
['Alice','in','Wonderland']

10.5 儲存資料

  • 使用模組json(JavaScript Object Notation)來儲存資料,JSON格式儲存的資料可以與使用其他程式語言的人分享。

10.5.1 使用json.dump()json.load()

  • 函式json.dump()接受兩個實參:要儲存的資料以及可用儲存資料的檔案物件,通常使用副檔名.json來指出檔案儲存的資料為JSON格式。
import json

numbers = [1,2,3,4,5]            #如何用`json.dump()`來儲存數字列表

filename = "numbers.json"
with open(filename,"w") as f_obj:
    json_dump(numbers,f_obj)
    new_numbers = json.load(f_obj)   #使用`json.load()`將這個列表讀取到記憶體中

10.5.2 程式碼重構

  • 例子:儲存和讀取使用者生成的資料remember_me.py
import json
#如果以前儲存了使用者名稱,就載入它;否則,提示使用者輸入使用者名稱並存儲它
filename = 'username.json'
try:
    with open(filename) as f_obj:
        username = json.load(f_obj)
except FileNotFoundError:
    username = input("what is your name?")
    with open(filename,"w") as f_obj:
        json.dump(username,f_obj)
        print("We'll remember you when you come back ," + username + "!")
else:
    print("Welcome back, " + username + "!")
  • 上述程式碼儘快能夠正確執行,但仍然可以進一步將程式碼劃分為一系列完成具體工作的函式,這樣的改進過程稱為重構。
  • 重構讓程式碼更清晰、更易於理解、更容易擴充套件,讓每個函式都執行單一而清晰的額任務,好好體會重構後的程式碼:
import json

def get_stored_username():
    '''如果儲存了使用者名稱,就獲取它'''
    filename = "username.json"
    try:
        with open(filename) as f_obj:
            username = json.load(f_obj)
    except FileNotFoundError:
        return None
    else:
        return username     
        #函式要麼返回None,要麼返回預期的值,易於使用函式的返回值做簡單測試

def get_new_username():
    '''提示使用者輸入使用者名稱'''
    username = input("what is your name?")
    filename = "username.json"
    with open(filename,"w") as f_obj:
        json.dump(username,f_obj)
    return username

def greet_user():
    '''問候使用者,並指出其名字'''
    username = get_stored_username()
    if username:
        print("Welcome back, " + username + "!")
    else:
        username = get_new_username()
        print("We'll remember you when you come back ," + username + "!")

greet_user()

第11章 測試程式碼

  • 主要使用標準庫中的unittest模組來測試程式碼(函式和類)

11.1 測試函式

  • 單元測試:用於核實函式的某個方面是否符合要求
  • 測試用例:是一組單元測試,用於一起核實函式在各種情況下的行為是否都符合要求
  • 全覆蓋式測試:包含一整套單元測試,涵蓋了各種可能得函式使用方式

test_name_function.py

import unittest
from name_function import get_formatted_name   #匯入模組unittest以及要測試的函式,再建立一個繼承unittest.TestCase的類

class NamesTestCase(unittest.TestCase):      #建立一個繼承unittest.TestCase的類,用於包含一系列針對get_formatted_name()的單元測試
    """測試name_function.py"""
    
    def test_first_last_name(self):
        """能夠正確地處理像Janis Joplin這樣的名字嗎?"""
        formatted_name = get_formatted_name("Janis","Joplin")
        self.assertEqual(formatted_name,'Janis Joplin')            #用斷言方法來核實得到的結果是否與期望結果一致

unittest.main()    
#讓Python執行檔案中的測試,所有test_打頭的方法都將自動執行,因此被測試的方法名必須以test_打頭
  • 執行測試用例時,每完成一個單元測試,Python都列印一個字元:測試通過列印一個句點,測試引發錯誤列印一個E,測試導致斷言失敗列印一個F
  • 測試未通過時,不要修改測試,而應修復導致測試不能通過的程式碼

11.2 測試類

  • unittest.TestCase類中提供了各種斷言方法,常用的6種:
方法 用途
assertEqual(a,b) 核實 a == b
assertNotEqual(a,b) 核實 a != b
assertTrue(x) 核實x為True
assertFalse(x) 核實x為False
assertIn(item,list) 核實item在list中
assertNotIn(item,list) 核實item不在list中
  • unittest.TestCase類包含方法setUp(),只需建立物件一次,就能在每個測試方法中使用它們,Python將先執行它,再執行各個以test_打頭的方法
import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """針對AnonymousSurvey類的測試"""
    
    def setUp(self):
        """建立一個調查物件和一組答案,供測試方法使用"""
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ["English","Spanish","Mandarin"]
    
    def test_store_single_response(self):
        """測試單個答案會被妥善地儲存"""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.response[0],self.my_survey.responses)
    
    def test_store_three_response(self):
        """測試三個答案會被妥善地儲存"""
        for response in self.responses:
            self.my_survey.store_response(response)
        for resopnse in self.response:
            self.assertIn(response,self.my_survey.responses)

unittest.main()