Python書籍閱讀與記錄 6.16 II 類

我感覺這樣記錄,對於我來說挺好的。因爲我看兩端對齊的語句容易走神,這樣記錄閱讀的話,就很少出現之前的情況。

我寫的初衷,也是自己來看,所以感覺寫的不好的,請保留下意見,謝謝。

代碼縮進情況,字體重複情況,因爲我能看懂,就沒改。

 

 

裏面的每一個字我都看過,加粗 括號  下劃線 等均是我的筆記。

 

 

 

 
第9章 類
面向對象編程 是最有效的軟件編寫方法之一。在面向對象編程中,你編寫表示現實世界中的事物和情景的類,並基於這些類來創建對象。編寫類時,你定義一大類對
象都有的通用行爲。基於類創建對
象 時,每個對象都自動具備這種通用行爲,然後可根據需要賦予每個對象獨特的個性。使用面向對象編程可模擬現實情景,其逼真
程度達到了令你驚訝的地步。
根據類來創建對象被稱爲實
化 ,這讓你能夠使用類的實例。在本章中,你將編寫一些類並創建其實例。你將指定可在實例中存儲什麼信息,定義可對這些實例執行
哪些操作。你還將編寫一些類來擴展既有類的功能,讓相似的類能夠高效地共享代碼。你將把自己編寫的類存儲在模塊中,並在自己的程序文件中導入其他程序員編
寫的類。
理解面向對象編程有助於你像程序員那樣看世界,還可以幫助你真正明白自己編寫的代碼:不僅是各行代碼的作用,還有代碼背後更宏大的概念。瞭解類背後的概念
可培養邏輯思維,讓你能夠通過編寫程序來解決遇到的幾乎任何問題。
隨着面臨的挑戰日益嚴峻,類還能讓你以及與你合作的其他程序員的生活更輕鬆。如果你與其他程序員基於同樣的邏輯來編寫代碼,你們就能明白對方所做的工作;
你編寫的程序將能被衆多合作者所理解,每個人都能事半功倍。
9.1 創建和使用類
使用類幾乎可以模擬任何東西。下面來編寫一個表示小狗的簡單類Dog ——它表示的不是特定的小狗,而是任何小狗。對於大多數寵物狗,我們都知道些什麼呢?它們都有名字
和年齡;我們還知道,大多數小狗還會蹲下和打滾。由於大多數小狗都具備上述兩項信息(名字和年齡)和兩種行爲(蹲下和打滾),我們的Dog 類將包含它們。這個類讓
Python知道如何創建表示小狗的對象。編寫這個類後,我們將使用它來創建表示特定小狗的實例。
9.1.1 創建Dog類
根據Dog 類創建的每個實例都將存儲名字和年齡。我們賦予了每條小狗蹲下(sit() )和打滾(roll_over() )的能力:
dog.py
class Dog():
"""一次模擬小狗的簡單嘗試"""
def __init__(self, name, age):
"""初始化屬性nameage"""
self.name = name
self.age = age
def sit(self):
"""模擬小狗被命令時蹲下"""
print(self.name.title() + " is now sitting.")
def roll_over(self):
"""模擬小狗被命令時打滾"""
print(self.name.title() + " rolled over!")
這裏需要注意的地方很多,但你也不用擔心,本章充斥着這樣的結構,你有大把的機會熟悉它。在處,我們定義了一個名爲Dog 的類。根據約定,在Python中,首字母大寫的
名稱指的是類。這個類定義中的括號是空的,因爲我們要從空白創建這個類。在處,我們編寫了一個文檔字符串,對這個類的功能作了描述。
1. 方法__init__()
類中的函數稱爲方
;你前面學到的有關函數的一切都適用於方法,就目前而言,唯一重要的差別是調用方法的方式。處的方法__init__() 是一個特殊的方法,每當你根
Dog 類創建新實例時,Python都會自動運行它。在這個方法的名稱中,開頭和末尾各有兩個下劃線,這是一種約定,旨在避免Python默認方法與普通方法發生名稱衝突。
我們將方法__init__() 定義成了包含三個形參:self name age 在這個方法的定義中,形參self 必不可少,還必須位於其他形參的前面。爲何必須在方法定義中包
含形參self 呢?因爲Python調用這個__init__() 方法來創建Dog 實例時,將自動傳入實參self 。每個與類相關聯的方法調用都自動傳遞實參self ,它是一個指向實例本身 #根據下面的例子,可以看出是隱式調用__init__()方法
的引用,讓實例能夠訪問類中的屬性和方法。我們創建Dog 實例時,Python將調用Dog 類的方法__init__() 。我們將通過實參向Dog() 傳遞名字和年齡;self 會自動傳遞,因此我們不需要傳遞它。每當我們根據Dog 類創建實例時,都只需給最後兩個形參(name 和age )提供值。
處定義的兩個變量都有前綴self 。以self 爲前綴的變量都可供類中的所有方法使用,我們還可以通過類的任何實例來訪問這些變量。self.name = name 獲取存儲在形
name 中的值,並將其存儲到變量name 中,然後該變量被關聯到當前創建的實例。self.age = age 的作用與此類似。像這樣可通過實例訪問的變量稱爲屬
屬性
Dog 類還定義了另外兩個方法:sit() roll_over() (見)。由於這些方法不需要額外的信息,如名字或年齡,因此它們只有一個形參self 。我們後面將創建的實例能
夠訪問這些方法,換句話說,它們都會蹲下和打滾。當前,sit() roll_over() 所做的有限,它們只是打印一條消息,指出小狗正蹲下或打滾。但可以擴展這些方法以模擬
實際情況:如果這個類包含在一個計算機遊戲中,這些方法將包含創建小狗蹲下和打滾動畫效果的代碼。如果這個類是用於控制機器狗的,這些方法將引導機器狗做出蹲下和打
滾的動作。
2. 在Python2.7中創建類
Python 2.7中創建類時,需要做細微的修改——在括號內包含單詞object
class ClassName(object):
--snip--
這讓Python 2.7類的行爲更像Python 3類,從而簡化了你的工作。
Python 2.7中定義Dog 類時,代碼類似於下面這樣:
class Dog(object):
--snip--
9.1.2 根據類創建實例
可將類視爲有關如何創建實例的說明。Dog 類是一系列說明,讓Python知道如何創建表示特定小狗的實例。
下面來創建一個表示特定小狗的實例:
class Dog():
--snip--
my_dog = Dog('willie', 6)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
這裏使用的是前一個示例中編寫的Dog 類。在處,我們讓Python創建一條名字爲'willie' 、年齡爲6 的小狗。遇到這行代碼時,Python使用實參'willie' 6 調用Dog
中的方法__init__() 。方法__init__() 創建一個表示特定小狗的示例,並使用我們提供的值來設置屬性name age 方法__init__() 並未顯式地包含return 語句,
但Python自動返回一個表示這條小狗的實例。我們將這個實例存儲在變量my_dog 中。在這裏,命名約定很有用:我們通常可以認爲首字母大寫的名稱(如Dog )指的是類,而
小寫的名稱(如my_dog )指的是根據類創建的實例。
1. 訪問屬性
要訪問實例的屬性,可使用句點表示法。在處,我們編寫了如下代碼來訪問my_dog 的屬性name 的值:
my_dog.name
句點表示法在Python中很常用,這種語法演示了Python如何獲悉屬性的值。在這裏,Python先找到實例my_dog ,再查找與這個實例相關聯的屬性name 。在Dog 類中引用這個屬
性時,使用的是self.name 。在處,我們使用同樣的方法來獲取屬性age 的值。在前面的第1print 語句中,my_dog.name.title() my_dog 的屬性name
'willie' 改爲首字母大寫的;在第2print 語句中,str(my_dog.age) my_dog 的屬性age 的值6 轉換爲字符串。
輸出是有關my_dog 的摘要:
My dog's name is Willie.
My dog is 6 years old.
2. 調用方法
根據Dog 類創建實例後,就可以使用句點表示法來調用Dog 類中定義的任何方法。下面來讓小狗蹲下和打滾:
class Dog():
--snip--
my_dog = Dog('willie', 6)
my_dog.sit()
my_dog.roll_over()
要調用方法,可指定實例的名稱(這裏是my_dog )和要調用的方法,並用句點分隔它們。遇到代碼my_dog.sit() 時,Python在類Dog 中查找方法sit() 並運行其代碼。
Python以同樣的方式解讀代碼my_dog.roll_over()
Willie按我們的命令做了:
Willie is now sitting.
Willie rolled over!
這種語法很有用。如果給屬性和方法指定了合適的描述性名稱,如name age sit() roll_over() ,即便是從未見過的代碼塊,我們也能夠輕鬆地推斷出它是做什麼的。
3. 創建多個實例 
可按需求根據類創建任意數量的實例。下面再創建一個名爲your_dog 的實例:
class Dog():
--snip--
my_dog = Dog('willie', 6)
your_dog = Dog('lucy', 3)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
my_dog.sit()
print("\nYour dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()
在這個實例中,我們創建了兩條小狗,它們分別名爲WillieLucy。每條小狗都是一個獨立的實例,有自己的一組屬性,能夠執行相同的操作:
My dog's name is Willie.
My dog is 6 years old.
Willie is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.
就算我們給第二條小狗指定同樣的名字和年齡,Python依然會根據Dog 類創建另一個實例。你可按需求根據一個類創建任意數量的實例,條件是將每個實例都存儲在不同的變量
中,或佔用列表或字典的不同位置。
動手試一試
 
9-1
餐館
館 :創建一個名爲Restaurant 的類,其方法__init__() 設置兩個屬性:restaurant_name cuisine_type 。創建一個名
describe_restaurant() 的方法和一個名爲open_restaurant() 的方法,其中前者打印前述兩項信息,而後者打印一條消息,指出餐館正在營業。
根據這個類創建一個名爲restaurant 的實例,分別打印其兩個屬性,再調用前述兩個方法。
9-2
三家
家餐
餐館
館 :根據你爲完成練習9-1而編寫的類創建三個實例,並對每個實例調用方法describe_restaurant()
9-3
用戶
戶 :創建一個名爲User 的類,其中包含屬性first_name last_name ,還有用戶簡介通常會存儲的其他幾個屬性。在類User 中定義一個名
describe_user() 的方法,它打印用戶信息摘要;再定義一個名爲greet_user() 的方法,它向用戶發出個性化的問候。
創建多個表示不同用戶的實例,並對每個實例都調用上述兩個方法。
9.2 使用類和實例
你可以使用類來模擬現實世界中的很多情景。類編寫好後,你的大部分時間都將花在使用根據類創建的實例上。你需要執行的一個重要任務是修改實例的屬性。你可以直接修改
實例的屬性,也可以編寫方法以特定的方式進行修改。
9.2.1 Car類
下面來編寫一個表示汽車的類,它存儲了有關汽車的信息,還有一個彙總這些信息的方法:
car.py
class Car():
"""一次模擬汽車的簡單嘗試"""
def __init__(self, make, model, year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
def get_descriptive_name(self):
"""返回整潔的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
處,我們定義了方法__init__() 。與前面的Dog 類中一樣,這個方法的第一個形參爲self ;我們還在這個方法中包含了另外三個形參:make model year
__init__() 接受這些形參的值,並將它們存儲在根據這個類創建的實例的屬性中。創建新的Car 實例時,我們需要指定其製造商、型號和生產年份。
處,我們定義了一個名爲get_descriptive_name() 的方法,它使用屬性year make model 創建一個對汽車進行描述的字符串,讓我們無需分別打印每個屬性的
值。爲在這個方法中訪問屬性的值,我們使用了self.make self.model self.year 。在處,我們根據Car 類創建了一個實例,並將其存儲到變量my_new_car
中。接下來,我們調用方法get_descriptive_name() ,指出我們擁有的是一輛什麼樣的汽車:
2016 Audi A4
爲讓這個類更有趣,下面給它添加一個隨時間變化的屬性,它存儲汽車的總里程。
9.2.2 給屬性指定默認值
類中的每個屬性都必須有初始值,哪怕這個值是0或空字符串。在有些情況下,如設置默認值時,在方法__init__() 內指定這種初始值是可行的;如果你對某個屬性這樣做
了,就無需包含爲它提供初始值的形參。下面來添加一個名爲odometer_reading 的屬性,其初始值總是爲0。我們還添加了一個名爲read_odometer() 的方法,用於讀取汽車的里程錶:
class Car():
def __init__(self, make, model, year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
--snip--
def read_odometer(self):
"""打印一條指出汽車裏程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
現在,當Python調用方法__init__() 來創建新實例時,將像前一個示例一樣以屬性的方式存儲製造商、型號和生產年份。接下來,Python將創建一個名
odometer_reading 的屬性,並將其初始值設置爲0(見)。在處,我們還定義了一個名爲read_odometer() 的方法,它讓你能夠輕鬆地獲悉汽車的里程。
一開始汽車的里程爲0
2016 Audi A4
This car has 0 miles on it.
出售時里程錶讀數爲0的汽車並不多,因此我們需要一個修改該屬性的值的途徑。
9.2.3 修改屬性的值
可以以三種不同的方式修改屬性的值:直接通過實例進行修改;通過方法進行設置;通過方法進行遞增(增加特定的值)。下面依次介紹這些方法。
1. 直接修改屬性的值
要修改屬性的值,最簡單的方式是通過實例直接訪問它。下面的代碼直接將里程錶讀數設置爲23
class Car():
--snip--
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
處,我們使用句點表示法來直接訪問並設置汽車的屬性odometer_reading 。這行代碼讓Python在實例my_new_car 中找到屬性odometer_reading ,並將該屬性的值
設置爲23
2016 Audi A4
This car has 23 miles on it.
有時候需要像這樣直接訪問屬性,但其他時候需要編寫對屬性進行更新的方法。
2. 通過方法修改屬性的值
如果有替你更新屬性的方法,將大有裨益。這樣,你就無需直接訪問屬性,而可將值傳遞給一個方法,由它在內部進行更新。
下面的示例演示了一個名爲update_odometer() 的方法:
class Car():
--snip--
def update_odometer(self, mileage):
"""將里程錶讀數設置爲指定的值"""
self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
Car 類所做的唯一修改是在處添加了方法update_odometer() 。這個方法接受一個里程值,並將其存儲到self.odometer_reading 中。在處,我們調用
update_odometer() ,並向它提供了實參23(該實參對應於方法定義中的形參mileage )。它將里程錶讀數設置爲23;而方法read_odometer() 打印該讀數:
2016 Audi A4
This car has 23 miles on it.
可對方法update_odometer() 進行擴展,使其在修改里程錶讀數時做些額外的工作。下面來添加一些邏輯,禁止任何人將里程錶讀數往回調:
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!")
現在,update_odometer() 在修改屬性前檢查指定的讀數是否合理。如果新指定的里程(mileage )大於或等於原來的里程(self.odometer_reading ),就將里程
表讀數改爲新指定的里程(見);否則就發出警告,指出不能將里程錶往回撥(見)。
3. 通過方法對屬性的值進行遞增
有時候需要將屬性值遞增特定的量,而不是將其設置爲全新的值。假設我們購買了一輛二手車,且從購買到登記期間增加了100英里的里程,下面的方法讓我們能夠傳遞這個增
量,並相應地增加里程表讀數:
class Car():
--snip--
def update_odometer(self, mileage):
--snip--
def increment_odometer(self, miles):
"""將里程錶讀數增加指定的量"""
self.odometer_reading += miles
my_used_car = Car('subaru', 'outback', 2013)
print(my_used_car.get_descriptive_name())
my_used_car.update_odometer(23500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()
處,新增的方法increment_odometer() 接受一個單位爲英里的數字,並將其加入到self.odometer_reading 中。在處,我們創建了一輛二手車
——my_used_car 。在處,我們調用方法update_odometer() 並傳入23500 ,將這輛二手車的里程錶讀數設置爲23 500。在處,我們調用increment_odometer()
並傳入100 ,以增加從購買到登記期間行駛的100英里:
2013 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.
你可以輕鬆地修改這個方法,以禁止增量爲負值,從而防止有人利用它來回撥里程錶。
你可以使用類似於上面的方法來控制用戶修改屬性值(如里程錶讀數)的方式,但能夠訪問程序的人都可以通過直接訪問屬性來將里程錶修改爲任何值。要確
保安全,除了進行類似於前面的基本檢查外,還需特別注意細節。
動手試一試
 
9-4
就餐
餐人
人數
數 :在爲完成練習9-1而編寫的程序中,添加一個名爲number_served 的屬性,並將其默認值設置爲0。根據這個類創建一個名爲restaurant 的實
例;打印有多少人在這家餐館就餐過,然後修改這個值並再次打印它。
添加一個名爲set_number_served() 的方法,它讓你能夠設置就餐人數。調用這個方法並向它傳遞一個值,然後再次打印這個值。
添加一個名爲increment_number_served() 的方法,它讓你能夠將就餐人數遞增。調用這個方法並向它傳遞一個這樣的值:你認爲這家餐館每天可能接待的就
餐人數。
9-5
嘗試
試登
登錄
錄次
次數
數 :在爲完成練習9-3而編寫的User 類中,添加一個名爲login_attempts 的屬性。編寫一個名爲increment_login_attempts() 的方法,
它將屬性login_attempts 的值加1。再編寫一個名爲reset_login_attempts() 的方法,它將屬性login_attempts 的值重置爲0
根據User 類創建一個實例,再調用方法increment_login_attempts() 多次。打印屬性login_attempts 的值,確認它被正確地遞增;然後,調用方
reset_login_attempts() ,並再次打印屬性login_attempts 的值,確認它被重置爲0
9.3 繼承
編寫類時,並非總是要從空白開始。如果你要編寫的類是另一個現成類的特殊版本,可使用繼
承 。一個類繼
承 另一個類時,它將自動獲得另一個類的所有屬性和方法;原有的
類稱爲父
類 ,而新類稱爲子
類 。子類繼承了其父類的所有屬性和方法,同時還可以定義自己的屬性和方法。
9.3.1 子類的方法__init__()
創建子類的實例時,Python首先需要完成的任務是給父類的所有屬性賦值。爲此,子類的方法__init__() 需要父類施以援手。
例如,下面來模擬電動汽車。電動汽車是一種特殊的汽車,因此我們可以在前面創建的Car 類的基礎上創建新類ElectricCar ,這樣我們就只需爲電動汽車特有的屬性和行爲
編寫代碼。
下面來創建一個簡單的ElectricCar 類版本,它具備Car 類的所有功能:
electric_car.py
class Car():
"""一次模擬汽車的簡單嘗試"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
 
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
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
 
class ElectricCar(Car):
"""電動汽車的獨特之處"""
def __init__(self, make, model, year):   # 子類的__init__()方法
"""初始化父類的屬性"""
     ❹ super().__init__(make, model, year)   #這地方不縮進我真是看不懂了  父類的__init__()方法
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
首先是Car 類的代碼(見)。創建子類時,父類必須包含在當前文件中,且位於子類前面。處,我們定義了子類ElectricCar 。定義子類時,必須在括號內指定父類的
名稱。方法__init__() 接受創建Car 實例所需的信息(見❸)。
處的super() 是一個特殊函數,幫助Python將父類和子類關聯起來。這行代碼讓Python調用ElectricCar 的父類的方法__init__() ,讓ElectricCar 實例包含父類的所
有屬性。父類也稱爲超類 (superclass),名稱super因此而得名。
爲測試繼承是否能夠正確地發揮作用,我們嘗試創建一輛電動汽車,但提供的信息與創建普通汽車時相同。在處,我們創建ElectricCar 類的一個實例,並將其存儲在變
my_tesla 中。這行代碼調用ElectricCar 類中定義的方法__init__() ,後者讓Python調用父類Car 中定義的方法__init__() 。我們提供了實參'tesla' 'model
s' 2016
除方法__init__() 外,電動汽車沒有其他特有的屬性和方法。當前,我們只想確認電動汽車具備普通汽車的行爲:
2016 Tesla Model S
ElectricCar 實例的行爲與Car 實例一樣,現在可以開始定義電動汽車特有的屬性和方法了。
9.3.2 Python 2.7中的繼承
Python 2.7中,繼承語法稍有不同,ElectricCar 類的定義類似於下面這樣:
class Car(object):
def __init__(self, make, model, year):
--snip--
class ElectricCar(Car):
def __init__(self, make, model, year):
super(ElectricCar, self).__init__(make, model, year)   #此處self 是子類實例
--snip--
函數super() 需要兩個實參:子類名和對象self 。爲幫助Python將父類和子類關聯起來,這些實參必不可少。另外,在Python 2.7中使用繼承時,務必在定義父類時在括號內指
object
9.3.3 給子類定義屬性和方法
讓一個類繼承另一個類後,可添加區分子類和父類所需的新屬性和方法。
下面來添加一個電動汽車特有的屬性(電瓶),以及一個描述該屬性的方法。我們將存儲電瓶容量,並編寫一個打印電瓶描述的方法:
class Car():
--snip--
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""
電動汽車的獨特之處
初始化父類的屬性,再初始化電動汽車特有的屬性
"""
super().__init__(make, model, year)
self.battery_size = 70  #此處縮進
 
def describe_battery(self):
"""打印一條描述電瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kWh battery.")
 
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
處,我們添加了新屬性self.battery_size ,並設置其初始值(如70 )。根據ElectricCar 類創建的所有實例都將包含這個屬性,但所有Car 實例都不包含它。在
處,我們還添加了一個名爲describe_battery() 的方法,它打印有關電瓶的信息。我們調用這個方法時,將看到一條電動汽車特有的描述:
2016 Tesla Model S
This car has a 70-kWh battery.對於ElectricCar 類的特殊化程度沒有任何限制。模擬電動汽車時,你可以根據所需的準確程度添加任意數量的屬性和方法。如果一個屬性或方法是任何汽車都有的,而不是
電動汽車特有的,就應將其加入到Car 類而不是ElectricCar 類中。這樣,使用Car 類的人將獲得相應的功能,而ElectricCar 類只包含處理電動汽車特有屬性和行爲的代
碼。
9.3.4 重寫父類的方法 
對於父類的方法,只要它不符合子類模擬的實物的行爲,都可對其進行重寫。爲此,可在子類中定義一個這樣的方法,即它與要重寫的父類方法同名。這樣,Python將不會考慮這
個父類方法,而只關注你在子類中定義的相應方法。
假設Car 類有一個名爲fill_gas_tank() 的方法,它對全電動汽車來說毫無意義,因此你可能想重寫它。下面演示了一種重寫方式:
def ElectricCar(Car):
--snip--
def fill_gas_tank():
"""電動汽車沒有油箱"""
print("This car doesn't need a gas tank!")
現在,如果有人對電動汽車調用方法fill_gas_tank() Python將忽略Car 類中的方法fill_gas_tank() ,轉而運行上述代碼。使用繼承時,可讓子類保留從父類那裏繼
承而來的精華,並剔除不需要的糟粕。
9.3.5 將實例用作屬性
使用代碼模擬實物時,你可能會發現自己給類添加的細節越來越多:屬性和方法清單以及文件都越來越長。在這種情況下,可能需要將類的一部分作爲一個獨立的類提取出來。
你可以將大型類拆分成多個協同工作的小類。
例如,不斷給ElectricCar 類添加細節時,我們可能會發現其中包含很多專門針對汽車電瓶的屬性和方法。在這種情況下,我們可將這些屬性和方法提取出來,放到另一個名
Battery 的類中,並將一個Battery 實例用作ElectricCar 類的一個屬性
class Car():
--snip--
class Battery():
"""一次模擬電動汽車電瓶的簡單嘗試"""
def __init__(self, battery_size=70):
"""初始化電瓶的屬性"""
self.battery_size = battery_size
def describe_battery(self):
"""打印一條描述電瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kWh battery.")
class ElectricCar(Car):
"""電動汽車的獨特之處"""
def __init__(self, make, model, year):
"""
初始化父類的屬性,再初始化電動汽車特有的屬性
"""
super().__init__(make, model, year)
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
處,我們定義了一個名爲Battery 的新類,它沒有繼承任何類。處的方法__init__() self 外,還有另一個形參battery_size 。這個形參是可選的:如果沒有給
它提供值,電瓶容量將被設置爲70。方法describe_battery() 也移到了這個類中(見)。
ElectricCar 類中,我們添加了一個名爲self.battery 的屬性(見)。這行代碼讓Python創建一個新的Battery 實例(由於沒有指定尺寸,因此爲默認值70 ),並將
該實例存儲在屬性self.battery 中。每當方法__init__() 被調用時,都將執行該操作;因此現在每個ElectricCar 實例都包含一個自動創建的Battery 實例。
我們創建一輛電動汽車,並將其存儲在變量my_tesla 中。要描述電瓶時,需要使用電動汽車的屬性battery
my_tesla.battery.describe_battery()
這行代碼讓Python在實例my_tesla 中查找屬性battery ,並對存儲在該屬性中的Battery 實例調用方法describe_battery()
輸出與我們前面看到的相同:
2016 Tesla Model S
This car has a 70-kWh battery.
這看似做了很多額外的工作,但現在我們想多詳細地描述電瓶都可以,且不會導致ElectricCar 類混亂不堪。下面再給Battery 類添加一個方法,它根據電瓶容量報告汽車
的續航里程:
class Car():
--snip--
 
class Battery():
--snip--
def get_range(self):
"""打印一條消息,指出電瓶的續航里程"""
if self.battery_size == 70:
range = 240
elif self.battery_size == 85:
range = 270
message = "This car can go approximately " + str(range)message += " miles on a full charge."
print(message)
 
class ElectricCar(Car):
--snip--
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
處新增的方法get_range() 做了一些簡單的分析:如果電瓶的容量爲70kWh,它就將續航里程設置爲240英里;如果容量爲85kWh,就將續航里程設置爲270英里,然後報告
這個值。爲使用這個方法,我們也通過汽車的屬性battery 來調用它(見)。
輸出指出了汽車的續航里程(這取決於電瓶的容量):
2016 Tesla Model S
This car has a 70-kWh battery.
This car can go approximately 240 miles on a full charge.
9.3.6 模擬實物
模擬較複雜的物件(如電動汽車)時,需要解決一些有趣的問題。續航里程是電瓶的屬性還是汽車的屬性呢?如果我們只需描述一輛汽車,那麼將方法get_range()
Battery 類中也許是合適的;但如果要描述一家汽車製造商的整個產品線,也許應該將方法get_range() 移到ElectricCar 類中。在這種情況下,get_range() 依然
根據電瓶容量來確定續航里程,但報告的是一款汽車的續航里程。我們也可以這樣做:將方法get_range() 還留在Battery 類中,但向它傳遞一個參數,如car_model ;在
這種情況下,方法get_range() 將根據電瓶容量和汽車型號報告續航里程。
這讓你進入了程序員的另一個境界:解決上述問題時,你從較高的邏輯層面(而不是語法層面)考慮;你考慮的不是Python,而是如何使用代碼來表示實物。到達這種境界後,你
經常會發現,現實世界的建模方法並沒有對錯之分。有些方法的效率更高,但要找出效率最高的表示法,需要經過一定的實踐。只要代碼像你希望的那樣運行,就說明你做得很
好!即便你發現自己不得不多次嘗試使用不同的方法來重寫類,也不必氣餒;要編寫出高效、準確的代碼,都得經過這樣的過程。 
動手試一試
 
9-6
冰淇
淇淋
淋小
小店
店 :冰淇淋小店是一種特殊的餐館。編寫一個名爲IceCreamStand 的類,讓它繼承你爲完成練習9-1或練習9-4而編寫的Restaurant 類。這兩個版
本的Restaurant 類都可以,挑選你更喜歡的那個即可。添加一個名爲flavors 的屬性,用於存儲一個由各種口味的冰淇淋組成的列表。編寫一個顯示這些冰淇淋
的方法。創建一個IceCreamStand 實例,並調用這個方法。
9-7
管理
理員
員 :管理員是一種特殊的用戶。編寫一個名爲Admin 的類,讓它繼承你爲完成練習9-3或練習9-5而編寫的User 類。添加一個名爲privileges 的屬性,用
於存儲一個由字符串(如"can add post" "can delete post" "can ban user" 等)組成的列表。編寫一個名爲show_privileges() 的方法,它
顯示管理員的權限。創建一個Admin 實例,並調用這個方法。
9-8
權限
限 :編寫一個名爲Privileges 的類,它只有一個屬性——privileges ,其中存儲了練習9-7 所說的字符串列表。將方法show_privileges() 移到這
個類中。在Admin 類中,將一個Privileges 實例用作其屬性。創建一個Admin 實例,並使用方法show_privileges() 來顯示其權限。
9-9
電瓶
瓶升
升級
級 :在本節最後一個electric_car.py版本中,給Battery 類添加一個名爲upgrade_battery() 的方法。這個方法檢查電瓶容量,如果它不是85,就將它
設置爲85。創建一輛電瓶容量爲默認值的電動汽車,調用方法get_range() ,然後對電瓶進行升級,並再次調用get_range() 。你會看到這輛汽車的續航里程增
加了。
9.4 導入類
隨着你不斷地給類添加功能,文件可能變得很長,即便你妥善地使用了繼承亦如此。爲遵循Python的總體理念,應讓文件儘可能整潔。爲在這方面提供幫助,Python允許你將類存
儲在模塊中,然後在主程序中導入所需的模塊。
9.4.1 導入單個類
下面來創建一個只包含Car 類的模塊。這讓我們面臨一個微妙的命名問題:在本章中,已經有一個名爲car.py的文件,但這個模塊也應命名爲car.py,因爲它包含表示汽車的代
碼。我們將這樣解決這個命名問題:將Car 類存儲在一個名爲car.py的模塊中,該模塊將覆蓋前面使用的文件car.py。從現在開始,使用該模塊的程序都必須使用更具體的文件
名,如my_car.py。下面是模塊car.py,其中只包含Car 類的代碼:
car.py
"""一個可用於表示汽車的類"""
class Car():
"""一次模擬汽車的簡單嘗試"""
def __init__(self, make, model, year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
 
def get_descriptive_name(self):
"""返回整潔的描述性名稱"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
 
def read_odometer(self):
"""打印一條消息,指出汽車的里程"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
 
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
處,我們包含了一個模塊級文檔字符串,對該模塊的內容做了簡要的描述。你應爲自己創建的每個模塊都編寫文檔字符串。
下面來創建另一個文件——my_car.py,在其中導入Car 類並創建其實例:
my_car.py
from car import Car
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
處的import 語句讓Python打開模塊car ,並導入其中的Car 類。這樣我們就可以使用Car 類了,就像它是在這個文件中定義的一樣。輸出與我們在前面看到的一樣:
2016 Audi A4
This car has 23 miles on it.
導入類是一種有效的編程方式。如果在這個程序中包含了整個Car 類,它該有多長呀!通過將這個類移到一個模塊中,並導入該模塊,你依然可以使用其所有功能,但主程序文
件變得整潔而易於閱讀了。這還能讓你將大部分邏輯存儲在獨立的文件中;確定類像你希望的那樣工作後,你就可以不管這些文件,而專注於主程序的高級邏輯了。
9.4.2 在一個模塊中存儲多個類
雖然同一個模塊中的類之間應存在某種相關性,但可根據需要在一個模塊中存儲任意數量的類。Battery ElectricCar 都可幫助模擬汽車,因此下面將它們都加入模塊
car.py中:
car.py
"""一組用於表示燃油汽車和電動汽車的類"""
class Car():
--snip--
 
class Battery():
"""一次模擬電動汽車電瓶的簡單嘗試"""
def __init__(self, battery_size=60):
"""初始化電瓶的屬性"""
self.battery_size = battery_size
def describe_battery(self):
"""打印一條描述電瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kWh battery.")
def get_range(self):
"""打印一條描述電瓶續航里程的消息"""
if self.battery_size == 70:
range = 240
elif self.battery_size == 85:
range = 270
message = "This car can go approximately " + str(range)
message += " miles on a full charge."
print(message)
 
class ElectricCar(Car):
"""模擬電動汽車的獨特之處"""
def __init__(self, make, model, year):
"""
初始化父類的屬性,再初始化電動汽車特有的屬性
"""
super().__init__(make, model, year)
self.battery = Battery()
現在,可以新建一個名爲my_electric_car.py的文件,導入ElectricCar 類,並創建一輛電動汽車了:
my_electric_car.py
from car import ElectricCar
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
輸出與我們前面看到的相同,但大部分邏輯都隱藏在一個模塊中:
2016 Tesla Model S
This car has a 70-kWh battery.
This car can go approximately 240 miles on a full charge.
9.4.3 從一個模塊中導入多個類
可根據需要在程序文件中導入任意數量的類。如果我們要在同一個程序中創建普通汽車和電動汽車,就需要將Car ElectricCar 類都導入:
my_cars.py
from car import Car, ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
處從一個模塊中導入多個類時,用逗號分隔了各個類。導入必要的類後,就可根據需要創建每個類的任意數量的實例。
在這個示例中,我們在處創建了一輛大衆甲殼蟲普通汽車,並在處創建了一輛特斯拉Roadster電動汽車:
2016 Volkswagen Beetle
2016 Tesla Roadster
9.4.4 導入整個模塊
你還可以導入整個模塊,再使用句點表示法訪問需要的類。這種導入方法很簡單,代碼也易於閱讀。由於創建類實例的代碼都包含模塊名,因此不會與當前文件使用的任何名稱
發生衝突。
下面的代碼導入整個car 模塊,並創建一輛普通汽車和一輛電動汽車:
my_cars.py
import car
my_beetle = car.Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
處,我們導入了整個car 模塊。接下來,我們使用語法 module_name.class_name 訪問需要的類。像前面一樣,我們在處創建了一輛大衆甲殼蟲汽車,並在處創建
了一輛特斯拉Roadster汽車。
9.4.5 導入模塊中的所有類
要導入模塊中的每個類,可使用下面的語法:
from module_name import *
不推薦使用這種導入方式,其原因有二。首先,如果只要看一下文件開頭的import 語句,就能清楚地知道程序使用了哪些類,將大有裨益;但這種導入方式沒有明確地指出你
使用了模塊中的哪些類。這種導入方式還可能引發名稱方面的困惑。如果你不小心導入了一個與程序文件中其他東西同名的類,將引發難以診斷的錯誤。這裏之所以介紹這種導
入方式,是因爲雖然不推薦使用這種方式,但你可能會在別人編寫的代碼中見到它。
需要從一個模塊中導入很多類時,最好導入整個模塊,並使用 module_name.class_name 語法來訪問類。這樣做時,雖然文件開頭並沒有列出用到的所有類,但你清楚地知
道在程序的哪些地方使用了導入的模塊;你還避免了導入模塊中的每個類可能引發的名稱衝突。
9.4.6 在一個模塊中導入另一個模塊
有時候,需要將類分散到多個模塊中,以免模塊太大,或在同一個模塊中存儲不相關的類。將類存儲在多個模塊中時,你可能會發現一個模塊中的類依賴於另一個模塊中的類。
在這種情況下,可在前一個模塊中導入必要的類。
例如,下面將Car 類存儲在一個模塊中,並將ElectricCar Battery 類存儲在另一個模塊中。我們將第二個模塊命名爲electric_car.py (這將覆蓋前面創建的文件
electric_car.py),並將Battery ElectricCar 類複製到這個模塊中:
electric_car.py
"""一組可用於表示電動汽車的類"""
from car import Car
class Battery():
--snip--
class ElectricCar(Car):
--snip--
ElectricCar 類需要訪問其父類Car ,因此在處,我們直接將Car 類導入該模塊中。如果我們忘記了這行代碼,Python將在我們試圖創建ElectricCar 實例時引發錯誤。
我們還需要更新模塊car ,使其包含Car 類:
car.py
"""一個可用於表示汽車的類"""
class Car():
--snip--
現在可以分別從每個模塊中導入類,以根據需要創建任何類型的汽車了:
my_cars.py
from car import Car
from electric_car import ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
處,我們從模塊car 中導入了Car 類,並從模塊electric_car 中導入ElectricCar 類。接下來,我們創建了一輛普通汽車和一輛電動汽車。這兩種汽車都得以正確地
創建:
2016 Volkswagen Beetle
2016 Tesla Roadster
9.4.7 自定義工作流程
正如你看到的,在組織大型項目的代碼方面,Python提供了很多選項。熟悉所有這些選項很重要,這樣你才能確定哪種項目組織方式是最佳的,並能理解別人開發的項目。
一開始應讓代碼結構儘可能簡單。先儘可能在一個文件中完成所有的工作,確定一切都能正確運行後,再將類移到獨立的模塊中。如果你喜歡模塊和文件的交互方式,可在項目
開始時就嘗試將類存儲到模塊中。先找出讓你能夠編寫出可行代碼的方式,再嘗試讓代碼更爲組織有序。
動手試一試
 
9-10
導入
Restaurant
:將最新的Restaurant 類存儲在一個模塊中。在另一個文件中,導入Restaurant 類,創建一個Restaurant 實例,並調
Restaurant 的一個方法,以確認import 語句正確無誤。
9-11
導入
Admin
:以爲完成練習9-8而做的工作爲基礎,將User Privileges Admin 類存儲在一個模塊中,再創建一個文件,在其中創建一個Admin 實例
並對其調用方法show_privileges() ,以確認一切都能正確地運行。
9-12
多個
個模
模塊
塊 :將User 類存儲在一個模塊中,並將Privileges Admin 類存儲在另一個模塊中。再創建一個文件,在其中創建一個Admin 實例,並對其調用方
show_privileges() ,以確認一切都依然能夠正確地運行。
9.5 Python標準庫
Python庫 是一組模塊,安裝的Python都包含它。你現在對類的工作原理已有大致的瞭解,可以開始使用其他程序員編寫好的模塊了。可使用標準庫中的任何函數和類,爲此
只需在程序開頭包含一條簡單的import 語句。下面來看模塊collections 中的一個類——OrderedDict
字典讓你能夠將信息關聯起來,但它們不記錄你添加鍵值對的順序要創建字典並記錄其中的鍵值對的添加順序,可使用模塊collections 中的OrderedDict
類。OrderedDict 實例的行爲幾乎與字典相同,區別只在於記錄了鍵值對的添加順序。
我們再來看一看第6章的favorite_languages.py示例,但這次將記錄被調查者參與調查的順序:
favorite_languages.py
from collections import OrderedDict
favorite_languages = OrderedDict()
favorite_languages['jen'] = 'python'
favorite_languages['sarah'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'python'
for name, language in favorite_languages.items():
print(name.title() + "'s favorite language is " +
language.title() + ".")
我們首先從模塊collections 中導入了OrderedDict 類(見)。在處,我們創建了OrderedDict 類的一個實例,並將其存儲到favorite_languages 中。請注
意,這裏沒有使用花括號,而是調用OrderedDict() 來創建一個空的有序字典,並將其存儲在favorite_languages 。接下來,我們以每次一對的方式添加名字語言
對(見)。在處,我們遍歷favorite_languages ,但知道將以添加的順序獲取調查結果:
Jen's favorite language is Python.
Sarah's favorite language is C.
Edward's favorite language is Ruby.
Phil's favorite language is Python.
這是一個很不錯的類,它兼具列表和字典的主要優點(在將信息關聯起來的同時保留原來的順序)等你開始對關心的現實情形建模時,可能會發現有序字典正好能夠滿足需
求。隨着你對標準庫的瞭解越來越深入,將熟悉大量可幫助你處理常見情形的模塊。
你還可以從其他地方下載外部模塊。本書第二部分的每個項目都需要使用外部模塊,屆時你將看到很多這樣的示例。
動手試一試
 
9-13 使
使用
OrderedDict :在練習6-4中,你使用了一個標準字典來表示詞彙表。請使用OrderedDict 類來重寫這個程序,並確認輸出的順序與你在字典中添加鍵
值對的順序一致。
9-14
骰子
子 :模塊random 包含以各種方式生成隨機數的函數,其中的randint() 返回一個位於指定範圍內的整數,例如,下面的代碼返回一個1~6內的整數:
from random import randint
x = randint(1, 6)
請創建一個Die 類,它包含一個名爲sides 的屬性,該屬性的默認值爲6。編寫一個名爲roll_die() 的方法,它打印位於1和骰子面數之間的隨機數。創建一個6
的骰子,再擲10次。 創建一個10面的骰子和一個20面的骰子,並將它們都擲10次。
9-15 PythonModule of the Week :要了解Python標準庫,一個很不錯的資源是網站PythonModule ofthe Week。請訪問http://pymotw.com/ 並查看其中的目錄,在其中找一個你感興趣的模塊進行探索,或閱讀模塊collections random 的文檔。
9.6 類編碼風格
你必須熟悉有些與類相關的編碼風格問題,在你編寫的程序較複雜時尤其如此。
類名應採用峯命名法即將類名中的每個單詞的首字母都大寫,而不使用下劃線實例名和模塊名都採用小寫格式,並在單詞之間加上下劃線。
對於每個類,都應緊跟在類定義後面包含一個文檔字符串。這種文檔字符串簡要地描述類的功能,並遵循編寫函數的文檔字符串時採用的格式約定。每個模塊也都應包含一個文
檔字符串,對其中的類可用於做什麼進行描述。
可使用空行來組織代碼,但不要濫用。在類中,可使用一個空行來分隔方法;而在模塊中,可使用兩個空行來分隔類。
需要同時導入標準庫中的模塊和你編寫的模塊時,先編寫導入標準庫模塊的import 語句,再添加一個空行,然後編寫導入你自己編寫的模塊的import 語句。在包含多
import 語句的程序中,這種做法讓人更容易明白程序使用的各個模塊都來自何方。
9.7 小結
在本章中,你學習了:如何編寫類;如何使用屬性在類中存儲信息,以及如何編寫方法,以讓類具備所需的行爲;如何編寫方法__init__() ,以便根據類創建包含所需屬性的
實例。你見識瞭如何修改實例的屬性——包括直接修改以及通過方法進行修改。你還了解了:使用繼承可簡化相關類的創建工作;將一個類的實例用作另一個類的屬性可讓類更
簡潔。
你瞭解到,通過將類存儲在模塊中,並在需要使用這些類的文件中導入它們,可讓項目組織有序。你學習了Python標準庫,並見識了一個使用模塊collections
OrderedDict 類的示例。最後,你學習了編寫類時應遵循的Python約定。
在第10章中,你將學習如何使用文件,這讓你能夠保存你在程序中所做的工作,以及你讓用戶做的工作。你還將學習異
常 ,這是一種特殊的Python類,用於幫助你在發生錯誤時
採取相應的措施。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章