面向對象編程

2021-01-07 人工智慧遇見磐創

面向對象編程(OOP)對於初學者來說可能是一個很難理解的概念。很多書籍都是從解釋OOP開始,討論三大術語:封裝、繼承和多態性,但是解釋的效果往往讓人失望。

本文希望讓程式設計師、數據科學家和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的各種基礎知識:

封裝:對象包含自身的所有數據;繼承:創建一個類層次結構,其中父類的方法傳遞給子類;多態:函數有多種形式,或者對象可能有多種類型。我們以一個練習結束本文,讓你去實現:創建一個類,使你可以使用體積和曲面面積管理三維對象(球體和立方體)。基本樣板代碼如下所示:

import mathclass Shape3d: def __init__(self, name): self.name = name def surfaceArea(self): pass def volume(self): pass def getName(self): return self.nameclass Cuboid(): passclass Cube(): passclass Sphere(): pass如果你想了解更多關於Python的知識,可以參考密西根大學(universityofmichigan)的一門關於學習中級Python的優秀課程:https://bit.ly/2XshreA。

相關焦點

  • TIA Portal面向對象編程入門
    儘管時至今日依然有少數人質疑面向對象的編程思想,但我們看到的是面向對象技術發展的越來越好,無論是後端語言(JAVA、C#)或者前端語言(JavaScript、TypeScript),無一不是完全的支持面向對象技術。現在高校的PLC教材基本上採用的還是五六十年前的編程理念,將PLC定位為傳統繼電器控制的替代,以軟元件、寄存器這種古老落後的概念來講授這一門日新月異的現代工業控制編程技術。
  • C風格的面向對象編程
    面向對象編程(OOP),最早是C++、java等面向對象語言的一個核心特點,之後發展成了一種編程思想。面向對象編程的主要特點是,把屬性(數據)與方法(函數)按照類型綁定,並按照類型之間的關係分層分模塊設計,通過基類指針和虛函數實現多態。
  • 高級篇PLC的面向對象編程
    繼承」,甚至於它根本就不具備面向對象程式語言的特點,但面向對象編程的基本概念就是類和類的實例(即對象),我們只需要使用這種概念就可以了。如果大家從面向對象編程的角度去理解,則可以很好的理解這種設計模式。
  • 再談JavaScript面向對象編程
    導讀:陳皓曾發表過一篇文章《Javascript 面向對象編程》,珠玉在前,作者還是忍不住再畫蛇添足的補上一篇文章,主要是因為JavaScript這門語言魅力。另外這篇文章是一篇入門文章,是作者才開始學習Javascript,有一點心得,才想寫一篇這樣文章,文章中難免有錯誤的地方,還請各位不吝吐槽指正。
  • Python面向對象編程的基本概念
    九道門商業數據分析學院提供介紹在學習面向對象的編程時。我決定深入了解它的歷史,結果令人著迷。術語「面向對象程序設計」(OOP)是艾倫·凱(Alan Kay)在1966年讀研究生時提出的。名為Simula的語言是第一種具有面向對象編程功能的程式語言。它是在1967年開發的,用於製作仿真程序,其中最重要的信息稱為對象。
  • Python面向對象程式語言
    Python (發音:[ 'paiθ(ə)n; (US) 'paiθɔn ]n.蟒蛇,巨蛇 ),是一種面向對象的解釋性的電腦程式設計語言,也是一種功能強大而完善的通用型語言,已經具有十多年的發展歷史面向對象————Python即支持面向過程的編程也支持面向對象的編程。在「面向過程」的語 言中,程序是由過程或僅僅是可重用代碼的函數構建起來的。在「面向對象」的語言中,程序是由數據和功能組合而成的對象構建起來的。與其他主要的語言如 C++和Java相比,Python以一種非常強大又簡單的方式實現面向對象編程。
  • 面向對象的Qt編程
    從BOP到OOP基於對象的Qt編程(不推薦)
  • 如何使用JavaScript -面向對象編程
    面向對象編程 —— Object Oriented Programming,簡稱 OOP ,是一種編程開發思想。它將真實世界各種複雜的關係,抽象為一個個對象,然後由對象之間的分工與合作,完成對真實世界的模擬。在面向對象程序開發思想中,每一個對象都是功能中心,具有明確分工,可以完成接受信息、處理數據、發出信息等任務。
  • R 的面向對象編程系統(S3、S4系統介紹)
    R 的面向對象編程R 語言中有四套面向對象編程系統:我們所熟悉的
  • 面向對象編程會被拋棄嗎?這五大問題不容忽視
    儘管這個想法很巧妙,但直到 1981 年,面向對象編程才成為主流。在那之後,它就沒有停止過吸引新的和經驗豐富的軟體開發者。面向對象的程式設計師市場一如既往地忙碌。 但是在最近幾年中,這種已有幾十年歷史的編程範式受到越來越多的批評。難道是在面向對象編程大行其道 40 年之後,技術已經超越了這種範式?
  • 面向對象編程從小白到王者系列-認識對象
    在學習面向對象程式語言時很多人都是被什麼是對象這個神一樣的概念給打敗的。被打敗後從此就開始過得渾渾噩噩了,聽課學習時都是迷迷糊糊地,開始變得像聽天書一樣,從而興趣一落千丈,慢慢被「程序猿」給淘汰。在所有的教材中或是教程中都是把一個很簡單對對象解釋搞得特別高大上。
  • Java面向對象編程開發認證考試項目介紹
    2、職業定義java是面向對象編程的語言,面向對象編程有四個特點:抽象,封裝,繼承,多態。面向對象區別於bai以前的面向過程,du像Java、vb、vc都是zhi採用面向dao對象編zhuan程,當然更高級的還有面向接口編shu程。
  • 一篇非常全的Python 面向對象編程
    轉自:浪子燕青from:  Python編程開發http://www.langzi.fun/Python面向對象編程
  • 史上最全Python面向對象編程
    學神IT教育:XueGod-IT最負責任的線上直播教育平臺面向對象編程中,將函數和變量進一步封裝成類,類才是程序的基本元素
  • 史上最全 Python 面向對象編程
    作者:浪子燕青    來自:http://www.langzi.fun/Python面向對象編程.html面向對象編程和函數式編程
  • 一步步分析:C語言如何面向對象編程
    這篇文章,我們就來聊聊如何在C語言中利用面向對象的思想來編程。也許你在項目中用不到,但是也強烈建議你看一下,因為我之前在跳槽的時候就兩次被問到這個問題。二、什麼是面向對象編程有這麼一個公式:程序=數據結構+算法。C語言中一般使用面向過程編程,就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步調用,在函數中對數據結構進行處理(執行算法),也就是說數據結構和算法是分開的。
  • Facebook 開源 Skip,面向對象+函數式程式語言
    而通過靜態類型系統追蹤可變性,Skip 完成了這個目標,同時它也支持現代程式語言特徵,例如 trait、泛型與子類型。Skip 是一種通用程式語言,它跟蹤副作用,提供反應失效的緩存、ergonomics 和安全的並行化以及高效的 GC。Skip 是靜態類型的,它使用 LLVM 提前編譯,生成高度優化的可執行文件。
  • 聊聊面向對象編程的幾個基本原則
    進行面向對象編程,有下面幾個原則:一. 面向抽象原則二. 開閉原則三. 多用組合少用繼承原則四. 高內聚-低耦合原則下面首先先介紹抽象類和接口,然後介紹面向抽象編程。watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFucGk0NjQ4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)面向抽象所謂面向抽象編程,是指當設計一個類時,不讓該類面向具體的類,而是面向抽象類或者接口,即所設計類中的重要數據是抽象類或接口聲明的變量
  • C 語言面向對象編程 - 繼承
    上一篇文章主要講述了 C 語言面向對象編程
  • 史上最全 Python 面向對象編程技巧!
    面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。面向過程編程:1. 導入各種外部庫2. 設計各種全局變量3. 寫一個函數完成某個功能4. 寫一個函數完成某個功能5. 寫一個函數完成某個功能6. 寫一個函數完成某個功能7.
  • 轉載 | 史上最全 Python 面向對象編程
    面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。導入各種外部庫設計各種全局變量決定你要的類給每個類提供完整的一組操作明確地使用繼承來表現不同類之間的共同點根據需要,決定是否寫一個 main 函數作為程序入口面向對象編程中,將函數和變量進一步封裝成類,類才是程序的基本元素,它將數據和操作緊密地連結在一起,並保護數據不會被外界的函數意外地改變。