首頁>技術>

本文希望讓程式設計師、資料科學家和python愛好者們更容易理解這個概念。我們去掉所有的行話,透過一些例子來做解說。

這篇文章是關於解釋OOP的外行方式。

什麼是物件和類

簡單地說,Python中的一切都是物件,類是物件的藍圖。所以當我們寫下:

a = 2b = "Hello!"

我們正在建立一個int類的物件a,該物件的值為2,str類的物件b的值為“Hello!”(在預設情況下,用兩個引號來提供字串)。

另外,我們常常無意識地使用到了類和物件的概念,例如在使用scikit-learn模型時,我們實際上是在使用一個類。

clf = RandomForestClassifier()clf.fit(X,y)

這裡的分類器clf是一個物件,fit是在RandomForestClassifier類中定義的一個方法。

為什麼要使用類

為什麼我們經常使用類呢?我們可以用函式實現同樣的功能嗎?

可以。但是與函式相比,類為我們提供了更多功能。舉個例子,str類有很多為物件定義的函式,只需按tab鍵就可以訪問這些函式。我們也可以編寫這些函式,但是隻按tab鍵不能使用自己編寫的函式。

類的這個屬性被稱為封裝。封裝是指將資料與操作該資料的方法捆綁在一起,或者限制對物件某些元件的直接訪問。

所以這裡str類綁定了資料(“Hello!”)以及所有對資料進行操作的方法。同樣,‘RandomForestClassifier’類將所有的方法(fit、predict等)捆綁在一起。

除此之外,使用類還可以使程式碼更加模組化和易於維護。假設我們要建立一個像Scikit-Learn這樣的庫,就需要建立許多模型,每個模型都有一個fit和predict方法,如果不使用類,我們需要為每個模型提供許多函式,例如:

RFCFitRFCPredictSVCFitSVCPredictLRFitLRPredict and so on.

這種程式碼結構簡直是一場噩夢,因此Scikit Learn將每個模型定義為一個具有fit和predict方法的類。

建立類

現在我們已經瞭解了為什麼要使用類,以及它們為何如此重要。那麼如何開始使用它們呢?建立一個類非常簡單,下面是編寫任何類的樣板程式碼:

class myClass:    def __init__(self, a, b):        self.a = a        self.b = b    def somefunc(self, arg1, arg2):        # 這裡有些程式碼

這裡有很多新的關鍵字。主要是class__init__self。這些是什麼呢?

假設你在一家有很多賬戶的銀行工作。我們可以建立一個名為account的類,用於處理任何帳戶。例如,下面我建立了一個基本的帳戶,它為使用者儲存資料,即帳戶名和餘額,它還為我們提供了兩種銀行存款/取款的方法。請通讀一遍以下程式碼,它遵循與上面程式碼相同的結構。

class Account:    def __init__(self, account_name, balance=0):        self.account_name = account_name        self.balance = balance    def deposit(self, amount):        self.balance += amount    def withdraw(self,amount):        if amount <= self.balance:            self.balance -= amount        else:            print("Cannot Withdraw amounts as no funds!!!")

我們使用以下方法建立一個名為Rahul、金額為100的帳戶:

myAccount = Account("Rahul",100)

使用以下方法訪問此帳戶的資料:

但是,如何將這些屬性balance和account_name分別設定為100和“Rahul”?我們從來沒有呼叫過__init__方法,為什麼物件會獲得這些屬性?答案是,只要我們建立物件,它就會執行。因此,當我們建立myAccount時,它會自動執行函式__init__

現在讓我們試著存一些錢到我們的賬戶裡:

我們的餘額上升到200英鎊。你有沒有注意到,函式deposit需要兩個引數,即self和amount,但我們只提供了一個引數,而且仍然有效。

那麼這個self是什麼?下面我呼叫屬於類account的同一個函式deposit,並向它提供myAccount物件和amount。現在函式需要兩個引數。

我們的賬戶餘額如預期增加了100。所以這是我們呼叫的同一個函式。只有self和myAccount是完全相同的物件時,才會發生這種情況。Python為函式呼叫提供與引數self相同的物件myAccount。這就是為什麼self.balance在函式定義中真正指的是myAccount.balance.

但是仍然存在一些問題

我們知道如何建立類,但是還有一個重要的問題我還沒有提到。

假設你正在與蘋果iPhone部門合作,且必須為每種iPhone型號建立一個不同的類。對於這個例子,假設我們的iPhone的第一個版本目前只做一件事——打電話並存儲。可以這樣寫:

class iPhone:    def __init__(self, memory, user_id):         self.memory = memory         self.mobile_id = user_id    def call(self, contactNum):         # 這裡有些實現

現在,蘋果計劃推出iPhone1,這款iPhone機型引入了一項新功能——拍照功能。一種方法是複製貼上上述程式碼並建立一個新的類iPhone1,如下所示:

class iPhone1:    def __init__(self, memory, user_id):         self.memory = memory         self.mobile_id = user_id         self.pics = []    def call(self, contactNum):         # 這裡有些實現    def click_pic(self):         # 這裡有些實現         pic_taken = ...         self.pics.append(pic_taken)

但正如你所看到的,這是大量不必要的程式碼重複(上面用粗體顯示),Python有一個消除程式碼重複的解決方案。編寫iPhone1類的一個好方法是:

Class iPhone1(iPhone):    def __init__(self,memory,user_id):         super().__init__(memory,user_id)         self.pics = []    def click_pic(self):         # 這裡有些實現         pic_taken = ...         self.pics.append(pic_taken)

這就是繼承的概念,繼承是將一個物件或類基於另一個保留類似實現的物件或類的機制。簡單地說,iPhone1現在可以訪問類iPhone中定義的所有變數和方法。

在本例中,我們不必進行任何程式碼複製,因為我們已經從父類iPhone繼承(獲取)了所有方法。因此,我們不必再次定義呼叫函式。另外,我們不使用super在函式中設定mobile_uid和記憶體。

super().__init__(memory,user_id)是什麼?

在現實生活中,你的初始函式不是這些漂亮的函式。你將需要在類中定義許多變數/屬性,並且複製並貼上子類(這裡是iphone1),會很麻煩。因此存在super(),這裡super().__init__()實際上是呼叫父iPhone類的__init__方法。因此當類iPhone1的__init__函式執行時,它會自動使用父類的__init__函式設定類的memory和user_id。

在ML/DS/DL中的哪裡可以看到?下面我們建立PyTorch模型,此模型繼承了nn.Module類,並使用super呼叫該類的__init__函式。

class myNeuralNet(nn.Module):    def __init__(self):        super().__init__()        # 在這裡定義所有層        self.lin1 = nn.Linear(784, 30)        self.lin2 = nn.Linear(30, 10)    def forward(self, x):        # 在此處連線層輸出以定義前向傳播        x = self.lin1(x)        x = self.lin2(x)        return x

那麼多型又是什麼?看下面的類:

import mathclass Shape:    def __init__(self, name):        self.name = name    def area(self):        pass    def getName(self):        return self.nameclass Rectangle(Shape):    def __init__(self, name, length, breadth):        super().__init__(name)        self.length = length        self.breadth = breadth    def area(self):        return self.length*self.breadthclass Square(Rectangle):    def __init__(self, name, side):        super().__init__(name,side,side)class Circle(Shape):    def __init__(self, name, radius):        super().__init__(name)        self.radius = radius    def area(self):        return pi*self.radius**2

這裡我們有基類Shape和其他派生類-Rectangle和Circle。另外,看看我們如何在Square類中使用多個級別的繼承,Square類是從Rectangle派生的,而Rectangle又是從Shape派生的。每個類都有一個名為area的函式,它是根據形狀定義的。

因此,透過Python中的多型性,一個同名函式可以執行多個任務。事實上,這就是多型性的字面意思:“具有多種形式的東西”。所以這裡我們的函式area有多種形式。

多型性與Python一起工作的另一種方式是使用isinstance方法。因此,使用上面的類,如果我們這樣做:

物件mySquare的例項型別是方形、矩形和形狀,因此物件是多型的,有很多好的特性。例如,我們可以建立一個與Shape物件一起工作的函式,它將透過使用多型性完全處理任何派生類(Square、Circle、Rectangle等)。

更多資訊

為什麼有些函式名或屬性名以單下劃線和雙下劃線開頭?有時我們想讓類中的屬性和函式私有化,而不允許使用者看到它們,這是封裝的一部分,我們希望“限制對物件某些元件的直接訪問”。例如,假設我們不想讓使用者看到我們的iPhone建立後的memory(RAM)。在這種情況下,我們使用變數名中的下劃線建立屬性。

因此,當我們以下面的方式建立iPhone類時,你將無法訪問你的memory或iphone私有函式,因為該屬性現在使用_

但你仍然可以使用(儘管不建議使用)更改變數值。

你還可以使用私有函式myphone._privatefunc()。如果要避免這種情況,可以在變數名前面使用雙下劃線。例如,在呼叫print(myphone.__memory)下面丟擲一個錯誤。此外,你無法使用myphone更改物件的內部資料myphone.__memory = 1

但是,正如你所見,你可以在類定義中的函式setMemory中訪問和修改self.__memory

結論

希望本文對你理解類很有用。總結一下在這篇文章中我們學習的OOP和建立類以及OOP的各種基礎知識:

封裝:物件包含自身的所有資料;

繼承:建立一個類層次結構,其中父類的方法傳遞給子類;

多型:函式有多種形式,或者物件可能有多種型別。

我們以一個練習結束本文,讓你去實現:建立一個類,使你可以使用體積和曲面面積管理三維物件(球體和立方體)。基本樣板程式碼如下所示:

15
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 分享一個實用指令碼—管理端批次SSH免密,值得收藏