1. 程式人生 > >Python基礎之類

Python基礎之類

一、摘要

面向物件程式設計 是最有效的軟體編寫方法之一。在面向物件程式設計中,你編寫表示現實世界中的事物和情景的類,並基於這些類來建立物件。編寫類時,你定義一大類物件都有的通用行為。基於類建立物件 時,每個物件都自動具備這種通用行為,然後可根據需要賦予每個物件獨特的個性。使用面向物件程式設計可模擬現實情景,其逼真程度達到了令你驚訝的地步。根據類來建立物件被稱為例項化 ,這讓你能夠使用類的例項。

二、建立和使用類

使用類幾乎可以模擬任何東西。

下面來編寫一個表示小狗的簡單類Dog ——它表示的不是特定的小狗,而是任何小狗。對於大多數寵物狗,我們都知道些什麼呢?它們都有名字和年齡;我們還知道,大多數小狗還會蹲下和打滾。由於大多數小狗都具備上述兩項資訊(名字和年齡)和兩種行為(蹲下和打滾),我們的Dog 類將包含它們。這個類讓Python知道如何建立表示小狗的物件。編寫這個類後,我們將使用它來建立表示特定小狗的例項。

class Dog():
    """一次模擬小狗的簡單嘗試"""
    def __init__(self, name, age):
        """初始化屬性name和age"""
        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!")

__init__() 是一個特殊的方法,每當你根據Dog 類建立新例項時,Python都會自動執行它。在這個方法的名稱中,開頭和末尾各有兩個下劃線,這是一種約定,旨在避免Python預設方法與普通方法發生名稱衝突

我們將方法__init__() 定義成了包含三個形參:self 、name 和age 。在這個方法的定義中,形參self 必不可少,還必須位於其他形參的前面。為何必須在方法定義中包含形參self 呢?因為Python呼叫這個__init__() 方法來建立Dog 例項時,將自動傳入實參self 。每個與類相關聯的方法呼叫都自動傳遞實參self ,它是一個指向例項本身的引用,讓例項能夠訪問類中的屬性和方法。我們建立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() 所做的有限,它們只是列印一條訊息,指出小狗正蹲下或打滾。但可以擴充套件這些方法以模擬實際情況:如果這個類包含在一個計算機遊戲中,這些方法將包含建立小狗蹲下和打滾動畫效果的程式碼。如果這個類是用於控制機器狗的,這些方法將引導機器狗做出蹲下和打滾的動作

根據類建立例項:

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.")

我們讓Python建立一條名字為'willie' 、年齡為6 的小狗。遇到這行程式碼時,Python使用實參'willie' 和6 呼叫Dog 類中的方法__init__() 。方法__init__() 建立一個表示特定小狗的示例,並使用我們提供的值來設定屬性name 和age 。方法__init__() 並未顯式地包含return 語句,但Python自動返回一個表示這條小狗的例項。我們將這個例項儲存在變數my_dog 中。在這裡,命名約定很有用:我們通常可以認為首字母大寫的名稱(如Dog )指的是類,而小寫的名稱(如my_dog )指的是根據類建立的例項

訪問屬性:

要訪問例項的屬性,可使用句點表示法,如下程式碼來訪問my_dog 的屬性name 的值:

my_dog.name

句點表示法在Python中很常用,這種語法演示了Python如何獲悉屬性的值。在這裡,Python先找到例項my_dog ,再查詢與這個例項相關聯的屬性name 。在Dog 類中引用這個屬性時,使用的是self.name 。我們使用同樣的方法來獲取屬性age 的值。在前面的第1條print 語句中,my_dog.name.title() 將my_dog 的屬性name 的值'willie' 改為首字母大寫的;在第2條print 語句中,str(my_dog.age) 將my_dog 的屬性age 的值6 轉換為字串。輸出是有關my_dog 的摘要:

My dog's name is Willie.
My dog is 6 years old.

呼叫方法:

根據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 is now sitting.
Willie rolled over!

在命名上,如果給屬性和方法指定了合適的描述性名稱,如name 、age 、sit() 和roll_over() ,即便是從未見過的程式碼塊,我們也能夠輕鬆地推斷出它是做什麼的

建立多個例項:

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()

就算我們給第二條小狗指定同樣的名字和年齡,Python依然會根據Dog 類建立另一個例項。你可按需求根據一個類建立任意數量的例項,條件是將每個例項都儲存在不同的變數中,或佔用列表或字典的不同位置。

三、使用類和例項

你可以使用類來模擬現實世界中的很多情景。類編寫好後,你的大部分時間都將花在使用根據類建立的例項上。你需要執行的一個重要任務是修改例項的屬性。你可以直接修改例項的屬性,也可以編寫方法以特定的方式進行修改。

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

給屬性指定預設值:

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.

修改屬性的值:

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

直接修改屬性的值:

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.

通過方法修改屬性的值:

如果有替你更新屬性的方法,將大有裨益。這樣,你就無需直接訪問屬性,而可將值傳遞給一個方法,由它在內部進行更新

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()

添加了方法update_odometer() 。這個方法接受一個里程值,並將其儲存到self.odometer_reading 中。

我們呼叫了update_odometer() ,並向它提供了實參23(該實參對應於方法定義中的形參mileage)。它將里程錶讀數設定為23;而方法read_odometer() 列印該讀數:

2016 Audi A4
This car has 23 miles on it.

 再看一個例子:

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 ),就將里程錶讀數改為新指定的里程

否則就發出警告,指出不能將里程錶往回撥

通過方法對屬性的值進行遞增:

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.

你可以輕鬆地修改這個方法,以禁止增量為負值,從而防止有人利用它來回撥里程錶。

注意:你可以使用類似於上面的方法來控制使用者修改屬性值(如里程錶讀數)的方式,但能夠訪問程式的人都可以通過直接訪問屬性來將里程錶修改為任何值。要確保安全,除了進行類似於前面的基本檢查外,還需特別注意細節。

四、繼承

編寫類時,並非總是要從空白開始。如果你要編寫的類是另一個現成類的特殊版本,可使用繼承 。一個類繼承 另一個類時,它將自動獲得另一個類的所有屬性和方法;原有的類稱為父類 ,而新類稱為子類 。子類繼承了其父類的所有屬性和方法,同時還可以定義自己的屬性和方法。

子類的方法__init__():

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):
        """初始化父類的屬性"""
        super().__init__(make, model, year)

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' 、'models' 和2016 

除方法__init__() 外,電動汽車沒有其他特有的屬性和方法。當前,我們只想確認電動汽車具備普通汽車的行為:

2016 Tesla Model S

給子類定義屬性和方法:

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 類只包含處理電動汽車特有屬性和行為的程式碼

重寫父類的方法:

對於父類的方法,只要它不符合子類模擬的實物的行為,都可對其進行重寫。為此,可在子類中定義一個這樣的方法,即它與要重寫的父類方法同名。這樣,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() ,轉而執行上述程式碼。使用繼承時,可讓子類保留從父類那裡繼承而來的精華,並剔除不需要的糟粕。

將例項用作屬性:

使用程式碼模擬實物時,你可能會發現自己給類新增的細節越來越多:屬性和方法清單以及檔案都越來越長。在這種情況下,可能需要將類的一部分作為一個獨立的類提取出來。你可以將大型類拆分成多個協同工作的小類。

例如,不斷給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.

模擬較複雜的物件(如電動汽車)時,需要解決一些有趣的問題。續航里程是電瓶的屬性還是汽車的屬性呢?如果我們只需描述一輛汽車,那麼將方法get_range() 放在Battery 類中也許是合適的;但如果要描述一家汽車製造商的整個產品線,也許應該將方法get_range() 移到ElectricCar 類中。在這種情況下,get_range() 依然根據電瓶容量來確定續航里程,但報告的是一款汽車的續航里程。我們也可以這樣做:將方法get_range() 還留在Battery 類中,但向它傳遞一個引數,如car_model ;在這種情況下,方法get_range() 將根據電瓶容量和汽車型號報告續航里程。這讓你進入了程式設計師的另一個境界:解決上述問題時,你從較高的邏輯層面(而不是語法層面)考慮;你考慮的不是Python,而是如何使用程式碼來表示實物。到達這種境界後,你經常會發現,現實世界的建模方法並沒有對錯之分。有些方法的效率更高,但要找出效率最高的表示法,需要經過一定的實踐。只要程式碼像你希望的那樣執行,就說明你做得很好!即便你發現自己不得不多次嘗試使用不同的方法來重寫類,也不必氣餒;要編寫出高效、準確的程式碼,都得經過這樣的過程。

五、匯入類

隨著你不斷地給類新增功能,檔案可能變得很長,即便你妥善地使用了繼承亦如此。為遵循Python的總體理念,應讓檔案儘可能整潔。為在這方面提供幫助,Python允許你將類儲存在模組中,然後在主程式中匯入所需的模組。

建立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

檔案的開頭,包含了一個模組級文件字串,對該模組的內容做了簡要的描述。你應為自己建立的每個模組都編寫文件字串。

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.

雖然同一個模組中的類之間應存在某種相關性,但可根據需要在一個模組中儲存任意數量的類。類Battery 和ElectricCar 都可幫助模擬汽車,因此下面將它們都加入模組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()    

我們就可以import ElectricCar類了

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.

可根據需要在程式檔案中匯入任意數量的類。如果我們要在同一個程式中建立普通汽車和電動汽車,就需要將Car 和ElectricCar 類都匯入:

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())

執行結果為:

2016 Volkswagen Beetle
2016 Tesla Roadster

六、匯入整個模組

你還可以匯入整個模組,再使用句點表示法訪問需要的類。這種匯入方法很簡單,程式碼也易於閱讀。由於建立類例項的程式碼都包含模組名,因此不會與當前檔案使用的任何名稱發生衝突。

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 訪問需要的類。

匯入模組中的所有類:

from module_name import *

不推薦使用這種匯入方式,其原因有二。首先,如果只要看一下檔案開頭的import 語句,就能清楚地知道程式使用了哪些類,將大有裨益;但這種匯入方式沒有明確地指出你使用了模組中的哪些類。這種匯入方式還可能引發名稱方面的困惑。如果你不小心匯入了一個與程式檔案中其他東西同名的類,將引發難以診斷的錯誤。這裡之所以介紹這種匯入方式,是因為雖然不推薦使用這種方式,但你可能會在別人編寫的程式碼中見到它。需要從一個模組中匯入很多類時,最好匯入整個模組,並使用 module_name.class_name 語法來訪問類。這樣做時,雖然檔案開頭並沒有列出用到的所有類,但你清楚地知道在程式的哪些地方使用了匯入的模組;你還避免了匯入模組中的每個類可能引發的名稱衝突。

在一個模組中匯入另一個模組:

有時候,需要將類分散到多個模組中,以免模組太大,或在同一個模組中儲存不相關的類。將類儲存在多個模組中時,你可能會發現一個模組中的類依賴於另一個模組中的類。在這種情況下,可在前一個模組中匯入必要的類。例如,下面將Car 類儲存在一個模組中,並將ElectricCar 和Battery 類儲存在另一個模組中。我們將第二個模組命名為electric_car.py 

car.py

"""A class that can be used to represent a car."""

class Car():
    """A simple attempt to represent a car."""

    def __init__(self, manufacturer, model, year):
        """Initialize attributes to describe a car."""
        self.manufacturer = manufacturer
        self.model = model
        self.year = year
        self.odometer_reading = 0
        
    def get_descriptive_name(self):
        """Return a neatly formatted descriptive name."""
        long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
        return long_name.title()
    
    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print("This car has " + str(self.odometer_reading) + " miles on it.")
        
    def update_odometer(self, mileage):
        """
        Set the odometer reading to the given value.
        Reject the change if it attempts to roll the odometer back.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")
    
    def increment_odometer(self, miles):
        """Add the given amount to the odometer reading."""
        self.odometer_reading += miles

electric_car.py

"""A set of classes that can be used to represent electric cars."""

from car import Car

class Battery():
    """A simple attempt to model a battery for an electric car."""

    def __init__(self, battery_size=60):
        """Initialize the batteery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print("This car has a " + str(self.battery_size) + "-kWh battery.")  
        
    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 60:
            range = 140
        elif self.battery_size == 85:
            range = 185
            
        message = "This car can go approximately " + str(range)
        message += " miles on a full charge."
        print(message)
    
        
class ElectricCar(Car):
    """Models aspects of a car, specific to electric vehicles."""

    def __init__(self, manufacturer, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(manufacturer, model, year)
        self.battery = Battery()

ElectricCar 類需要訪問其父類Car ,因此我們直接將Car 類匯入該模組中

from car import Car
from electric_car import ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2015)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2015)
print(my_tesla.get_descriptive_name())

從模組car 中匯入了Car 類,並從模組electric_car 中匯入ElectricCar 類。接下來,我們建立了一輛普通汽車和一輛電動汽車。這兩種汽車都得以正確地建立:

2016 Volkswagen Beetle
2016 Tesla Roadster

正如你看到的,在組織大型專案的程式碼方面,Python提供了很多選項。熟悉所有這些選項很重要,這樣你才能確定哪種專案組織方式是最佳的,並能理解別人開發的專案。一開始應讓程式碼結構儘可能簡單。先儘可能在一個檔案中完成所有的工作,確定一切都能正確執行後,再將類移到獨立的模組中。如果你喜歡模組和檔案的互動方式,可在專案開始時就嘗試將類儲存到模組中。先找出讓你能夠編寫出可行程式碼的方式,再嘗試讓程式碼更為組織有序。

七、Python標準庫

Python標準庫 是一組模組,安裝的Python都包含它。你現在對類的工作原理已有大致的瞭解,可以開始使用其他程式設計師編寫好的模組了。可使用標準庫中的任何函式和類,為此只需在程式開頭包含一條簡單的import 語句。下面來看模組collections 中的一個類——OrderedDict 

字典讓你能夠將資訊關聯起來,但它們不記錄你新增鍵值對的順序。要建立字典並記錄其中的鍵—值對的新增順序,可使用模組collections 中的OrderedDict類。OrderedDict 例項的行為幾乎與字典相同,區別只在於記錄了鍵值對的新增順序。

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.

這是一個很不錯的類,它兼具列表和字典的主要優點(在將資訊關聯起來的同時保留原來的順序)。等你開始對關心的現實情形建模時,可能會發現有序字典正好能夠滿足需求。隨著你對標準庫的瞭解越來越深入,將熟悉大量可幫助你處理常見情形的模組。

八、編碼規範

  • 類名應採用駝峰命名法 ,即將類名中的每個單詞的首字母都大寫,而不使用下劃線。
  • 例項名和模組名都採用小寫格式,並在單詞之間加上下劃線。
  • 對於每個類,都應緊跟在類定義後面包含一個文件字串。這種文件字串簡要地描述類的功能,並遵循編寫函式的文件字串時採用的格式約定。每個模組也都應包含一個文件字串,對其中的類可用於做什麼進行描述。
  • 可使用空行來組織程式碼,但不要濫用。在類中,可使用一個空行來分隔方法;而在模組中,可使用兩個空行來分隔類。
  • 需要同時匯入標準庫中的模組和你編寫的模組時,先編寫匯入標準庫模組的import 語句,再新增一個空行,然後編寫匯入你自己編寫的模組的import 語句。在包含多條import 語句的程式中,這種做法讓人更容易明白程式使用的各個模組都來自何方。