[譯]Python提高:Python類和面向?qū)ο缶幊?/h2>

原文作者:Jeff Knupp

原文鏈接:這里

<font color=red>class</font>是Python的基礎(chǔ)構(gòu)建快。它是很多流行的程序和庫(kù),以及Python標(biāo)準(zhǔn)庫(kù)的基礎(chǔ)依托。理解類是什么,什么時(shí)候使用,以及它們?nèi)绾斡杏弥陵P(guān)重要,這也是本文的目的。在這個(gè)過程中,我們會(huì)探討“面向?qū)ο缶幊獭钡暮x,以及它與Python類之間的聯(lián)系。

一切都是對(duì)象...

<font color=red>class</font>關(guān)鍵字究竟是什么?跟它基于函數(shù)的<font color=red>def</font>表兄弟類似,它用于定義事物。<font color=red>def</font>用來定義函數(shù),<font color=red>class</font>用來定義。什么是類?就是一個(gè)數(shù)據(jù)和函數(shù)(在類中定義時(shí),通常叫做“方法”)的邏輯分組。

“邏輯分組”是什么意思?一個(gè)類可以包含我們希望的任何數(shù)據(jù)和函數(shù)(方法)。我們嘗試創(chuàng)建事物之間有邏輯聯(lián)系的類,而不是把隨機(jī)的事物放在“類”名下面。很多時(shí)候,類都是基于真實(shí)世界的物體(比如<font color=red>Customer</font>和<font color=red>Product</font>)。其它時(shí)候,類基于系統(tǒng)中的概念,比如<font color=red>HTTPRequest</font>和<font color=red>Owner</font>。

不管怎么樣,類是一種建模技術(shù),一種思考程序的方式。當(dāng)你用這種方式思考和實(shí)現(xiàn)你的系統(tǒng)時(shí),被稱為使用面向?qū)ο缶幊?/strong>?!邦悺焙汀皩?duì)象”經(jīng)?;Q使用,但實(shí)際上它們并不相同。理解它們是什么和它們是如何工作的關(guān)鍵是理解它們之間的區(qū)別。

..所以一切都有一個(gè)類?

類可以看做是創(chuàng)建對(duì)象的藍(lán)圖。當(dāng)我使用<font color=red>class</font>關(guān)鍵字定義一個(gè)Customer類時(shí),我并沒有真正創(chuàng)建一個(gè)顧客。相反,我創(chuàng)建的是構(gòu)建顧客對(duì)象的說明手冊(cè)。讓我們看以下示例代碼:

class Customer(object):
    """A customer of ABC Bank with a checking account. Customers have the
    following properties:

    Attributes:
        name: A string representing the customer's name.
        balance: A float tracking the current balance of the customer's account.
    """

    def __init__(self, name, balance=0.0):
        """Return a Customer object whose name is *name* and starting
        balance is *balance*."""
        self.name = name
        self.balance = balance

    def withdraw(self, amount):
        """Return the balance remaining after withdrawing *amount*
        dollars."""
        if amount > self.balance:
            raise RuntimeError('Amount greater than available balance.')
        self.balance -= amount
        return self.balance

    def deposit(self, amount):
        """Return the balance remaining after depositing *amount*
        dollars."""
        self.balance += amount
        return self.balance

<font color=red>class Customer(object)</font>并沒有創(chuàng)建一個(gè)新的顧客。我們只是定義了一個(gè)<font color=red>Customer</font>,并不意味著創(chuàng)建了一個(gè)顧客;我們僅僅勾勒出創(chuàng)建<font color=red>Customer</font>對(duì)象的藍(lán)圖。用正確的參數(shù)數(shù)量(去掉<font color=red>self</font>,我們馬上會(huì)討論)調(diào)用類的<font color=red>__init__</font>方法可以創(chuàng)建一個(gè)顧客。

因此,要使用通過<font color=red>class Customer</font>(用于創(chuàng)建<font color=red>Customer</font>對(duì)象)定義的“藍(lán)圖”,可以把類名看做一個(gè)函數(shù)來調(diào)用:<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>。這行代碼表示:使用<font color=red>Customer</font>藍(lán)圖創(chuàng)建一個(gè)新對(duì)象,并把它指向<font color=red>jeff</font>。

被稱為實(shí)例的<font color=red>jeff</font>對(duì)象是<font color=red>Customer</font>的實(shí)現(xiàn)版本。我們調(diào)用<font color=red>Customer()</font>之前,不存在<font color=red>Customer</font>對(duì)象。當(dāng)然,我們可以創(chuàng)建任意多個(gè)<font color=red>Customer</font>對(duì)象。但是不管我們創(chuàng)建多少<font color=red>Customer</font>實(shí)例,仍然只有一個(gè)<font color=red>Customer</font>

<font color=red>self</font>?

對(duì)應(yīng)所有<font color=red>Customer</font>方法來說,<font color=red>self</font>參數(shù)是什么?當(dāng)然,它是實(shí)例。換一種方式,像<font color=red>withdraw</font>這樣的方法,定義了從某些抽象顧客的賬戶中取錢的指令。調(diào)用<font color=red>jeff.withdraw(1000.0)</font>把這些指令用在<font color=red>jeff</font>實(shí)例上。

所以,當(dāng)我們說:<font color=red>def withdraw(self, amount):</font>,我們的意思是:這是你如何從一個(gè)顧客對(duì)象(我們稱為<font color=red>self</font>)和一個(gè)美元數(shù)字(我們稱為<font color=red>amount</font>)取錢。<font color=red>self</font>是<font color=red>Customer</font>的實(shí)例,在它上面調(diào)用<font color=red>withdraw</font>。這也不是我做類比。<font color=red>jeff.withdraw(1000.0)</font>只是<font color=red>Customer.withdraw(jeff, 1000.0)</font>的簡(jiǎn)寫,也是完全有限的代碼。

<font color=red>__init__</font>

<font color=red>self</font>可能對(duì)其它方法也有意義,但是<font color=red>__init__</font>呢?當(dāng)我們調(diào)用<font color=red>__init__</font>時(shí),我們?cè)趧?chuàng)建一個(gè)對(duì)象的過程中,為什么已經(jīng)有了<font color=red>self</font>?盡管不完全適合,Python還是允許我們擴(kuò)展<font color=red>self</font>模式到對(duì)象的構(gòu)造。想象<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>等價(jià)于<font color=red>jeff = Customer(jeff, 'Jeff Knupp', 1000.0)</font>;傳入的<font color=red>jeff</font>也是同樣的結(jié)果。

這就是為什么調(diào)用<font color=red>__init__</font>時(shí),我們通過<font color=red>self.name = name</font>來初始化對(duì)象。記住,因?yàn)?lt;font color=red>self</font>是實(shí)例,所以它等價(jià)于<font color=red>jeff.name = name</font>,它等價(jià)于<font color=red>jeff.name = 'Jeff Knupp'</font>。同樣的,<font color=red>self.balance = balance</font>等價(jià)于<font color=red>jeff.balance = 1000.0</font>。這兩行代碼之后,我們認(rèn)為<font color=red>Customer</font>對(duì)象已經(jīng)“初始化”,可以被使用。

完成<font color=red>__init__</font>后,調(diào)用者可以假設(shè)對(duì)象已經(jīng)可以使用。也就是,調(diào)用<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>后,我們可以在<font color=red>jeff</font>上調(diào)用<font color=red>deposit</font>和<font color=red>withdraw</font>;<font color=red>jeff</font>是一個(gè)完全初始化的對(duì)象。

我們定義了另外一個(gè)稍微不同的<font color=red>Customer</font>類:

class Customer(object):
    """A customer of ABC Bank with a checking account. Customers have the
    following properties:

    Attributes:
        name: A string representing the customer's name.
        balance: A float tracking the current balance of the customer's account.
    """

    def __init__(self, name):
        """Return a Customer object whose name is *name*.""" 
        self.name = name

    def set_balance(self, balance=0.0):
        """Set the customer's starting balance."""
        self.balance = balance

    def withdraw(self, amount):
        """Return the balance remaining after withdrawing *amount*
        dollars."""
        if amount > self.balance:
            raise RuntimeError('Amount greater than available balance.')
        self.balance -= amount
        return self.balance

    def deposit(self, amount):
        """Return the balance remaining after depositing *amount*
        dollars."""
        self.balance += amount
        return self.balance

它看起來是一個(gè)合理的替代者;在使用實(shí)例之前,只需要簡(jiǎn)單的調(diào)用<font color=red>set_balance</font>。但是,沒有一種方式可以告訴調(diào)用者這么做。即使我們?cè)谖臋n中說明了,也不能強(qiáng)制調(diào)用者在調(diào)用<font color=red>jeff.withdraw(100.0)</font>之前調(diào)用<font color=red>jeff.set_balance(1000.0)</font>。<font color=red>jeff</font>實(shí)例在調(diào)用<font color=red>jeff.set_balance</font>之前沒有<font color=red>balance</font>屬性,這意味著對(duì)象沒有“完全”初始化。

簡(jiǎn)單來說,不要在<font color=red>__init__</font>方法之外引入新的屬性,否則你會(huì)給調(diào)用一個(gè)沒有完全初始化的對(duì)象。當(dāng)然也有例外,但這是一個(gè)需要記住的原則。這是對(duì)象一致性這個(gè)大概念的一部分:不應(yīng)該有任何一系列的方法調(diào)用可能導(dǎo)致對(duì)象進(jìn)入沒有意義的狀態(tài)。

不變性(比如“賬戶余額總是非負(fù)數(shù)”)應(yīng)該在方法進(jìn)入和退出時(shí)都保留。對(duì)象不可能通過調(diào)用它的方法進(jìn)入無效狀態(tài)。不用說,一個(gè)對(duì)象也應(yīng)該從一個(gè)有效的狀態(tài)開始,這就是為什么在<font color=red>__init__</font>方法中初始化所有內(nèi)容是很重要的。

實(shí)例屬性和方法

定義在類中的函數(shù)稱為“方法”。方法可以訪問包含在對(duì)象實(shí)例中的所有數(shù)據(jù);它們可以訪問和修改之前在<font color=red>self</font>上設(shè)置的任何內(nèi)容。因?yàn)樗鼈兪褂?lt;font color=red>self</font>,所以需要使用類的一個(gè)實(shí)例?;谶@個(gè)原因,它們通常被稱為“實(shí)例方法”。

如果有“實(shí)例方法”,一定也會(huì)有其它類型的方法,對(duì)吧?是的,確實(shí)有,但這些方法有些深?yuàn)W。我們會(huì)在這里簡(jiǎn)略的介紹一下,但是可以更深入的研究這些主題。

靜態(tài)方法

類屬性是在類級(jí)別上設(shè)置的屬性,相對(duì)的是實(shí)例級(jí)別。普通屬性在<font color=red>__init__</font>方法中引入,但有些屬性適用于所有實(shí)例。例如,思考下面<font color=red>Car</font>對(duì)象的定義:

class Car(object):

    wheels = 4

    def __init__(self, make, model):
        self.make = make
        self.model = model

mustang = Car('Ford', 'Mustang')
print mustang.wheels
# 4
print Car.wheels
# 4

不管<font color=red>make</font>和<font color=red>model</font>是什么,一輛<font color=red>Car</font>總是有四個(gè)<font color=red>Wheels</font>。實(shí)例方法可以通過跟訪問普通屬性一樣訪問這些屬性:通過<font color=red>self</font>(比如,<font color=red>self.wheels</font>)。

有一種稱為靜態(tài)方法的方法,它們不能訪問<font color=red>self</font>。跟類屬性類似,它們不需要實(shí)例就能工作。因?yàn)閷?shí)例總是通過<font color=red>self</font>引用,所以靜態(tài)方法沒有<font color=red>self</font>參數(shù)。

下面是<font color=red>Car</font>類的一個(gè)有效的靜態(tài)方法:

class Car(object):
    ...
    def make_car_sound():
        print 'VRooooommmm!'

不管我們擁有什么類型的汽車,它總是發(fā)出相同的聲音。為了說明這個(gè)方法不應(yīng)該接收實(shí)例作為第一個(gè)參數(shù)(比如“普通”方法的<font color=red>self</font>),可以使用<font color=red>@staticmethod</font>裝飾器,把我們的定義變成:

class Car(object):
    ...
    @staticmethod
    def make_car_sound():
        print 'VRooooommmm!'

類方法

靜態(tài)方法的一個(gè)變種是類方法。它傳遞,而不是實(shí)例作為第一個(gè)參數(shù)。它也使用裝飾器定義:

class Vehicle(object):
    ...
    @classmethod
    def is_motorcycle(cls):
        return cls.wheels == 2

現(xiàn)在類方法可能沒有太大的意義,但它通常與下一個(gè)主題聯(lián)系在一起:繼承。

繼承

面向?qū)ο缶幊套鳛榻9ぞ叻浅S杏?,引?strong>繼承的概念后,它真正變強(qiáng)大了。

繼承是“子”類衍生“父”類的數(shù)據(jù)和行為的過程。有一個(gè)實(shí)例可以明確的幫助我們理解。

想象我們經(jīng)營(yíng)了一家汽車銷售店。我們銷售所有類型的車輛,從摩托車到卡車。我們通過價(jià)格與競(jìng)爭(zhēng)對(duì)手區(qū)分開來。特別是我們?nèi)绾未_定車輛的價(jià)格:$5000 * 一臺(tái)車輛擁有的車輪數(shù)。我們也喜歡回購(gòu)車輛。我們提供統(tǒng)一的價(jià)格 - 車輛行駛里程的10%。卡車的價(jià)格是$10,000,汽車是$8,000,摩托車是$4,000。

如果我們想用面對(duì)對(duì)象技術(shù)為汽車銷售店創(chuàng)建一個(gè)銷售系統(tǒng),應(yīng)該怎么做?對(duì)象是什么?我們可能有一個(gè)<font color=red>Sale</font>類,一個(gè)<font color=red>Customer</font>類,一個(gè)<font color=red>Inventor</font>類等等,但我們肯定有一個(gè)<font color=red>Car</font>,<font color=red>Truck</font>和<font color=red>Motorcycle</font>類。

這些類應(yīng)該是什么樣的?用我們已經(jīng)學(xué)會(huì)的知識(shí),以下是<font color=red>Car</font>類的一種實(shí)現(xiàn):

class Car(object):
    """A car for sale by Jeffco Car Dealership.

    Attributes:
        wheels: An integer representing the number of wheels the car has.
        miles: The integral number of miles driven on the car.
        make: The make of the car as a string.
        model: The model of the car as a string.
        year: The integral year the car was built.
        sold_on: The date the vehicle was sold.
    """

    def __init__(self, wheels, miles, make, model, year, sold_on):
        """Return a new Car object."""
        self.wheels = wheels
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on

    def sale_price(self):
        """Return the sale price for this car as a float amount."""
        if self.sold_on is not None:
            return 0.0  # Already sold
        return 5000.0 * self.wheels

    def purchase_price(self):
        """Return the price for which we would pay to purchase the car."""
        if self.sold_on is None:
            return 0.0  # Not yet sold
        return 8000 - (.10 * self.miles)

    ...

看起來非常合理。當(dāng)然,類中可能還有其它方法,但我已經(jīng)展示了兩個(gè)我們感興趣的方法:<font color=red>sale_price</font>和<font color=red>purchase_price</font>。我們之后會(huì)看到為什么這些很重要。

我們已經(jīng)有了<font color=red>Car</font>類,也許我們應(yīng)該創(chuàng)建<font color=red>Truck</font>類。我們按同樣的方式創(chuàng)建:

class Truck(object):
    """A truck for sale by Jeffco Car Dealership.

    Attributes:
        wheels: An integer representing the number of wheels the truck has.
        miles: The integral number of miles driven on the truck.
        make: The make of the truck as a string.
        model: The model of the truck as a string.
        year: The integral year the truck was built.
        sold_on: The date the vehicle was sold.
    """

    def __init__(self, wheels, miles, make, model, year, sold_on):
        """Return a new Truck object."""
        self.wheels = wheels
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on

    def sale_price(self):
        """Return the sale price for this truck as a float amount."""
        if self.sold_on is not None:
            return 0.0  # Already sold
        return 5000.0 * self.wheels

    def purchase_price(self):
        """Return the price for which we would pay to purchase the truck."""
        if self.sold_on is None:
            return 0.0  # Not yet sold
        return 10000 - (.10 * self.miles)

    ...

幾乎跟<font color=red>Car</font>類一模一樣。編程中最重要的原則之一(通常不只是處理對(duì)象時(shí))是“DRY”或者“Don't Repeat Yourself”。確定無疑,我們?cè)谶@里重復(fù)了。實(shí)際上,<font color=red>Car</font>類和<font color=red>Truck</font>類只有一個(gè)字符不同(除了注釋)。

出什么事了?我們哪里做錯(cuò)了?我們的主要問題是我們直奔概念:<font color=red>Car</font>和<font color=red>Truck</font>是真實(shí)的事物,直覺讓有形的對(duì)象成為類。但是它們共享這么多數(shù)據(jù)和功能,似乎我們可以在這里引入一個(gè)抽象。沒錯(cuò),它就是<font color=red>Vehicle</font>。

抽象類

<font color=red>Vehicle</font>不是真實(shí)世界的對(duì)象。而是一個(gè)概念,它包含某些真實(shí)世界中的對(duì)象(比如汽車,卡車和摩托車)。我們可以用這個(gè)事實(shí)來移除重復(fù)代碼,即每個(gè)對(duì)象都被看做是一臺(tái)車輛。通過定義<font color=red>Vehicle</font>類達(dá)到目的:

class Vehicle(object):
    """A vehicle for sale by Jeffco Car Dealership.

    Attributes:
        wheels: An integer representing the number of wheels the vehicle has.
        miles: The integral number of miles driven on the vehicle.
        make: The make of the vehicle as a string.
        model: The model of the vehicle as a string.
        year: The integral year the vehicle was built.
        sold_on: The date the vehicle was sold.
    """

    base_sale_price = 0

    def __init__(self, wheels, miles, make, model, year, sold_on):
        """Return a new Vehicle object."""
        self.wheels = wheels
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on


    def sale_price(self):
        """Return the sale price for this vehicle as a float amount."""
        if self.sold_on is not None:
            return 0.0  # Already sold
        return 5000.0 * self.wheels

    def purchase_price(self):
        """Return the price for which we would pay to purchase the vehicle."""
        if self.sold_on is None:
            return 0.0  # Not yet sold
        return self.base_sale_price - (.10 * self.miles)

通過替換<font color=red>class Car(object)</font>中的<font color=red>object</font>,我們可以讓<font color=red>Car</font>和<font color=red>Truck</font>類繼承<font color=red>Vehicle</font>類。括號(hào)中的類表示從哪個(gè)類繼承(<font color=red>object</font>實(shí)際上是“沒有繼承”。我們一會(huì)兒討論為什么這么寫)。

現(xiàn)在我們可以直截了當(dāng)?shù)亩x<font color=red>Car</font>和<font color=red>Truck</font>:

class Car(Vehicle):

    def __init__(self, wheels, miles, make, model, year, sold_on):
        """Return a new Car object."""
        self.wheels = wheels
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on
        self.base_sale_price = 8000


class Truck(Vehicle):

    def __init__(self, wheels, miles, make, model, year, sold_on):
        """Return a new Truck object."""
        self.wheels = wheels
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on
        self.base_sale_price = 10000

這樣可以工作了,但還有一些問題。首先我們?nèi)匀挥泻芏嘀貜?fù)的代碼。最終我們會(huì)處理完所有重復(fù)的代碼。其次,更大的問題是,我們引入了<font color=red>Vehicle</font>類,但我們真的允許調(diào)用者創(chuàng)建<font color=red>Vehicle</font>對(duì)象(而不是<font color=red>Car</font>和<font color=red>Truck</font>)?<font color=red>Vehicle</font>僅僅是一個(gè)概念,不是真實(shí)的事物,所以下面代碼的意義是:

v = Vehicle(4, 0, 'Honda', 'Accord', 2014, None)
print v.purchase_price()

<font color=red>Vehicle</font>沒有<font color=red>base_sale_price</font>,只有各個(gè)子類(比如<font color=red>Car</font>和<font color=red>Truck</font>)有。問題在于<font color=red>Vehicle</font>應(yīng)該是一個(gè)Abstract Base ClassAbstract Base Class是只可以被繼承的類;不能創(chuàng)建ABC的實(shí)例。這意味著如果<font color=red>Vehicle</font>是一個(gè)ABC,那么下面的代碼就是非法的:

v = Vehicle(4, 0, 'Honda', 'Accord', 2014, None)

禁止這一點(diǎn)是有意義的,因?yàn)槲覀儚膩聿粫?huì)直接使用<font color=red>Vehicle</font>。我們只想用它抽取一些通用的數(shù)據(jù)和行為。我們?nèi)绾巫屢粋€(gè)類成為ABC?很簡(jiǎn)單!<font color=red>abc</font>模塊包括一個(gè)稱為<font color=red>ABCMeta</font>的元類。設(shè)置一個(gè)類的元類為<font color=red>ABCMeta</font>,并讓其中一個(gè)方法為虛擬的,就能讓類成為一個(gè)ABCABC規(guī)定,虛擬方法必須在子類中存在,但不是必須要實(shí)現(xiàn)。例如,<font color=red>Vehicle</font>類可以如下定義:

from abc import ABCMeta, abstractmethod

class Vehicle(object):
    """A vehicle for sale by Jeffco Car Dealership.


    Attributes:
        wheels: An integer representing the number of wheels the vehicle has.
        miles: The integral number of miles driven on the vehicle.
        make: The make of the vehicle as a string.
        model: The model of the vehicle as a string.
        year: The integral year the vehicle was built.
        sold_on: The date the vehicle was sold.
    """

    __metaclass__ = ABCMeta

    base_sale_price = 0

    def sale_price(self):
        """Return the sale price for this vehicle as a float amount."""
        if self.sold_on is not None:
            return 0.0  # Already sold
        return 5000.0 * self.wheels

    def purchase_price(self):
        """Return the price for which we would pay to purchase the vehicle."""
        if self.sold_on is None:
            return 0.0  # Not yet sold
        return self.base_sale_price - (.10 * self.miles)

    @abstractmethod
    def vehicle_type():
        """"Return a string representing the type of vehicle this is."""
        pass

因?yàn)?lt;font color=red>vehicle_type</font>是一個(gè)<font color=red>abstractmethod</font>,所以我們不能直接創(chuàng)建<font color=red>Vehicle</font>實(shí)例。只要<font color=red>Car</font>和<font color=red>Truck</font>從<font color=red>Vehicle</font>繼承,定義了<font color=red>vehicle_type</font>,我們就能實(shí)例化這些類。

返回<font color=red>Car</font>類和<font color=red>Truck</font>類中的重復(fù)代碼,看看我們是否可以把通用的功能提升到基類<font color=red>Vehicle</font>中:

from abc import ABCMeta, abstractmethod
class Vehicle(object):
    """A vehicle for sale by Jeffco Car Dealership.


    Attributes:
        wheels: An integer representing the number of wheels the vehicle has.
        miles: The integral number of miles driven on the vehicle.
        make: The make of the vehicle as a string.
        model: The model of the vehicle as a string.
        year: The integral year the vehicle was built.
        sold_on: The date the vehicle was sold.
    """

    __metaclass__ = ABCMeta

    base_sale_price = 0
    wheels = 0

    def __init__(self, miles, make, model, year, sold_on):
        self.miles = miles
        self.make = make
        self.model = model
        self.year = year
        self.sold_on = sold_on

    def sale_price(self):
        """Return the sale price for this vehicle as a float amount."""
        if self.sold_on is not None:
            return 0.0  # Already sold
        return 5000.0 * self.wheels

    def purchase_price(self):
        """Return the price for which we would pay to purchase the vehicle."""
        if self.sold_on is None:
            return 0.0  # Not yet sold
        return self.base_sale_price - (.10 * self.miles)

    @abstractmethod
    def vehicle_type(self):
        """"Return a string representing the type of vehicle this is."""
        pass

現(xiàn)在<font color=red>Car</font>和<font color=red>Truck</font>類變成:

class Car(Vehicle):
    """A car for sale by Jeffco Car Dealership."""

    base_sale_price = 8000
    wheels = 4

    def vehicle_type(self):
        """"Return a string representing the type of vehicle this is."""
        return 'car'

class Truck(Vehicle):
    """A truck for sale by Jeffco Car Dealership."""

    base_sale_price = 10000
    wheels = 4

    def vehicle_type(self):
        """"Return a string representing the type of vehicle this is."""
        return 'truck'

這完全符合我們的直覺:就我們的系統(tǒng)而言,汽車和卡車之間的唯一區(qū)別是基礎(chǔ)售價(jià)。

定義一個(gè)<font color=red>Motocycle</font>類非常簡(jiǎn)單:

class Motorcycle(Vehicle):
    """A motorcycle for sale by Jeffco Car Dealership."""

    base_sale_price = 4000
    wheels = 2

    def vehicle_type(self):
        """"Return a string representing the type of vehicle this is."""
        return 'motorcycle'

繼承和LSP

盡管看起來我們用繼承處理了重復(fù),但我們真正做的是簡(jiǎn)單的提供適當(dāng)級(jí)別的抽象。抽象是理解繼承的關(guān)鍵。我們已經(jīng)看到使用繼承的一個(gè)附帶作用是減少重復(fù)的代碼,但從調(diào)用者的角度來看呢?使用繼承如何改變代碼?

事實(shí)證明有一點(diǎn)。想象我們有兩個(gè)類:<font color=red>Dog</font>和<font color=red>Person</font>,我們想寫一個(gè)函數(shù),它接收任何兩種對(duì)象類型,并打印該實(shí)例是否可以說話(狗不能,人可以)。我們可能這么編寫代碼:

def can_speak(animal):
    if isinstance(animal, Person):
        return True
    elif isinstance(animal, Dog):
        return False
    else:
        raise RuntimeError('Unknown animal!')

只有兩種類型的動(dòng)物時(shí)沒問題,但是如何有20種呢,或者200種?那么<font color=red>if...elif</font>會(huì)相當(dāng)長(zhǎng)。

這里關(guān)鍵是<font color=red>can_speak</font>不應(yīng)該關(guān)心處理的動(dòng)物類型,動(dòng)物類本身應(yīng)該告訴我們它能否說話。通過引入基類<font color=red>Animal</font>,其中定義<font color=red>can_speak</font>,可以避免函數(shù)的類型檢查。只要知道是傳進(jìn)來的是<font color=red>Animal</font>,確定能否說話很簡(jiǎn)單:

def can_speak(animal):
    return animal.can_speak()

這是因?yàn)?lt;font color=red>Person</font>和<font color=red>Dog</font>(或者其它任何從<font color=red>Animal</font>繼承的類)遵循Liskov Substitution Principle。這表示我們可以在希望父類(<font color=red>Animal</font>)的地方,使用子類(比如<font color=red>Person</font>或<font color=red>Dog</font>)替換。這聽起來很簡(jiǎn)單,但它是interface的基礎(chǔ)。

總結(jié)

希望你們學(xué)會(huì)了什么是Python類,為什么它們很有用,以及如何使用。類和面向?qū)ο缶幊毯苌願(yuàn)W。確實(shí),它涉及計(jì)算機(jī)科學(xué)的核心。本文不是對(duì)類的詳細(xì)研究,也不應(yīng)該是你的唯一參考。網(wǎng)絡(luò)上有數(shù)以千計(jì)的OOP和類的解釋,如果本文對(duì)你不合適,搜索會(huì)讓你找到更適合你的。

一如既往,歡迎在評(píng)論中更正和討論。只要保持禮貌就行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容