Python 面向對象編程(上篇)

2022-02-06 程式設計師zhenguo

我的施工計劃圖

已完成專題包括:

1我的施工計劃

2數字專題

3字符串專題

4列表專題

5流程控制專題

6編程風格專題

7函數使用專題

今天是面向對象編程的上篇:基礎專題

Python 面向對象編程

面向對象程序設計思想,首先思考的不是程序執行流程,它的核心是抽象出一個對象,然後構思此對象包括的數據,以及操作數據的行為方法。

本專題主要討論面向對象編程(OOP)的基礎和進階知識,實際開發模型中OOP的主要實踐,儘量使用最貼切的例子。

基礎專題1 類定義

動物是自然界一個龐大的群體,以建模動物類為主要案例論述OOP編程。

Python語言創建動物類的基本語法如下,使用class關鍵字定義一個動物類:

class Animal():
    pass

類裡面可包括數據,如下所示的Animal類包括兩個數據:self.name和self.speed:

class Animal():
   def __init__(self,name,speed):
       self.name = name # 動物名字
       self.speed = speed # 動物行走或飛行速度

注意到類裡面通過系統函數__init__為類的2個數據賦值,數據前使用self保留字。

self的作用是指名這兩個數據是實例上的,而非類上的。

同時注意到__init__方法的第一個參數也帶有self,所以也表明此方法是實例上的方法。

2 實例

理解什麼是實例上的數據或方法,什麼是類上的數據,需要先建立實例的概念,類的概念,如下:

# 生成一個名字叫加菲貓、行走速度8km/h的cat對象
cat = Animal('加菲貓',8) 

cat就是Animal的實例,也可以一次創建成千上百個實例,如下創建1000隻蜜蜂:

bees = [Animal('bee'+str(i),5) for i in range(1000)]

總結:自始至終只使用一個類Animal,但卻可以創建出許多個它的實例,因此是一對多的關係。

實例創建完成後,下一步列印它看看:

In [1]: print(cat)                                                           
<__main__.Animal object at 0x7fce3a596ad0>

結果顯示它是Animal對象,其實列印結果顯示實例屬性信息會更友好,那麼怎麼實現呢?

3 列印實例

只需重新定義一個系統(又稱為魔法)函數__str__ ,就能讓列印實例顯示的更加友好:

class Animal():
   def __init__(self,name,speed):
       self.name = name # 動物名字
       self.speed = speed # 動物行走或飛行速度
  
   def __str__(self):
        return '''Animal({0.name},{0.speed}) is printed
                name={0.name}
                speed={0.speed}'''.format(self)

使用0.數據名稱的格式,這是類專有的列印格式。

現在再列印:

cat = Animal('加菲貓',8)
print(cat)

列印信息如下:

Animal(加菲貓,8) is printed
                name=加菲貓
                speed=8

以上就是想要的列印格式,看到實例的數據值都正確。

4 屬性

至此,我們都稱類裡的name和speed稱為數據,其實它們有一個專業名稱:屬性。

同時,上面還有一個問題我們沒有回答完全,什麼是類上的屬性?

如下,在最新Animal類定義基礎上,再添加一個cprop屬性,它前面沒有self保留字:

class Animal():
   cprop = "我是類上的屬性cprop"
   
   def __init__(self,name,speed):
       self.name = name # 動物名字
       self.speed = speed # 動物行走或飛行速度
  
   def __str__(self):
        return '''Animal({0.name},{0.speed}) is printed
                name={0.name}
                speed={0.speed}'''.format(self)

類上的屬性直接使用類便可引用:

In [1]: Animal.cprop                                                           
Out[1]: '我是類上的屬性cprop'

類上的屬性,實例同樣可以引用,並且所有的實例都共用此屬性值:

In [1]: cat = Animal('加菲貓',8)
In [2]: cat.cprop                                                              
Out[2]: '我是類上的屬性cprop'

Python作為一門動態語言,支持屬性的動態添加和刪除。

如下cat實例原來不存在color屬性,但是賦值時不光不會報錯,相反會直接將屬性添加到cat上:

cat.color = 'grap'

那麼,如何驗證cat是否有color屬性呢?使用內置函數hasattr:

In [24]: hasattr(cat,'color') # cat 已經有`color`屬性                          
Out[24]: True

但是注意:以上添加屬性方法僅僅為cat實例本身添加,而不會為其他實例添加:

In [26]: monkey = Animal('大猩猩',2)                                            
In [27]: hasattr(monkey,'color')                                             
Out[27]: False

monkey實例並沒有color屬性,注意與__init__創建屬性方法的區別。

5 private,protected,public

像name和speed屬性,引用此實例的對象都能訪問到它們,如下:

# 模塊名稱:manager.py

import time

class Manager():
    def __init__(self,animal):
        self.animal = animal
        
    def recordTime(self):
        self.__t = time.time()
        print('feeding time for %s(行走速度為:%s) is %.0f'%(self.animal.name,self.animal.speed,self.__t))
    
    def getFeedingTime(self):
        return '%0.f'%(self.__t,) 

使用以上Manager類,創建一個cat實例,xiaoming實例引用cat:

cat = Animal('加菲貓',8)
xiaoming =  Manager(cat) 

xiaoming的recordTime方法引用裡,引用了animal的兩個屬性name和speed:

In[1]: xiaoming.recordTime()

Out[1]: feeding time for 加菲貓(行走速度為:8) is 1595681304

注意看到self.__t屬性,它就是一個私有屬性,只能被Manager類內的所有方法引用,如被方法getFeedingTime方法引用。但是,不能被其他類引用。

如果我們連speed這個屬性也不想被其他類訪問,那麼只需將self.speed修改為self.__speed:

同時Manager類的self.animal.speed修改為self.animal.__speed,再次調用下面方法時:

xiaoming.recordTime()

就會報沒有__speed屬性的異常,從而驗證了__speed屬性已經變為類內私有,不會暴露在外面。

總結:name屬性相當於java的public屬性,而__speed相當於java的private屬性。

下面在說繼承時,講解protected屬性,實際上它就是帶有1個_的屬性,它只能被繼承的類所引用。

6 繼承

上面已經講完了OOP三大特性中的封裝性,而繼承是它的第二大特性。子類繼承父類的所有public和protected數據和方法,極大提高了代碼的重用性。

如上創建的Animal類最新版本為:

class Animal():
   cprop = "我是類上的屬性cprop"
   
   def __init__(self,name,speed):
       self.name = name # 動物名字
       self.__speed = speed # 動物行走或飛行速度
  
   def __str__(self):
        return '''Animal({0.name},{0.__speed}) is printed
                name={0.name}
                speed={0.__speed}'''.format(self)

現在有個新的需求,要重新定義一個Cat貓類,它也有name和speed兩個屬性,同時還有color和genre兩個屬性,列印時只需要列印name和speed兩個屬性就行。

因此,基本可以復用基類Animal,但需要修改__speed屬性為受保護(protected)的_speed屬性,這樣子類都可以使用此屬性,而外部還是訪問不到它。

綜合以上,Cat類的定義如下:

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre

首先使用super()方法找到Cat的基類Animal,然後引用基類的__init__方法,這樣復用基類的方法。

使用Cat類,列印時,又復用了基類的 __str__方法:

jiafeimao = Cat('加菲貓',8,'gray','CatGenre')
print(jiafeimao)

列印結果:

Animal(加菲貓,8) is printed
                name=加菲貓
                speed=8

以上就是基本的繼承使用案例,繼承要求基類定義的數據和行為儘量標準、儘量精簡,以此提高代碼復用性。

7 多態

如果說OOP的封裝和繼承使用起來更加直觀易用,那麼作為第三大特性的多態,在實踐中真正運用起來就不那麼容易。有的讀者OOP編程初期,可能對多態的價值體會不深刻,甚至都已經淡忘它的存在。

那麼問題就在:多態到底真的有用嗎?到底使用在哪些場景?

多態價值很大,使用場景很多,幾乎所有的系統或軟體,都能看到它的應用。這篇文章儘可能通過一個精簡的例子說明它的價值和使用方法。如果不用多態,方法怎麼寫;使用多態,又是怎麼寫。

為了一脈相承,做到一致性,仍然基於上面的案例,已經創建好的Cat類要有一個方法列印和返回它的爬行速度。同時需要再創建一個類Bird,要有一個方法列印和返回它的飛行速度;

如果不使用多態,為Cat類新增一個方法:

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre
    # 添加方法
    def getRunningSpeed(self):
        print('running speed of %s is %s' %(self.name, self._speed))
        return self._speed

重新創建一個Bird類:

class Bird(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre
    # 添加方法
    def getFlyingSpeed(self):
        print('flying speed of %s is %s' %(self.name, self._speed))
        return self._speed

最後,上面創建的Manager類會引用Cat和Bird類,但是需要修改recordTime方法,因為Cat它是爬行的,Bird它是飛行的,所以要根據對象類型的不同做邏輯區分,如下所示:

# 模塊名稱:manager.py

import time
from animal import (Animal,Cat,Bird)

class Manager():
    def __init__(self,animal):
        self.animal = animal
        
    def recordTime(self):
        self.__t = time.time()
        if isinstance(self.animal, Cat):
            print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
            self.animal.getRunningSpeed()
        if isinstance(self.animal,Bird):
            print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
            self.animal.getFlyingSpeed()

    def getFeedingTime(self):
        return '%0.f'%(self.__t,) 

如果再來一個類,我們又得需要修改recordTime,再增加一個if分支,從軟體設計角度講,這種不斷破壞封裝的行為不可取。

但是,使用多態,就可以保證recordTime不被修改,不必寫很多if分支。怎麼來實現呢?

首先,在基類Animal中創建一個基類方法,然後Cat和Bird分別重寫此方法,最後傳入到Manager類的animal參數是什麼類型,在recordTime方法中就會對應調用這個animal實例的方法,這就是多態

代碼如下:

animal2.py 模塊如下:

# animal2.py 模塊

class Animal():
   cprop = "我是類上的屬性cprop"
   
   def __init__(self,name,speed):
       self.name = name # 動物名字
       self._speed = speed # 動物行走或飛行速度
  
   def __str__(self):
        return '''Animal({0.name},{0._speed}) is printed
                name={0.name}
                speed={0._speed}'''.format(self)

   def getSpeedBehavior(self):
       pass 

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre
        
    # 重寫方法
    def getSpeedBehavior(self):
        print('running speed of %s is %s' %(self.name, self._speed))
        return self._speed
        

class Bird(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre

    # 重寫方法
    def getSpeedBehavior(self):
        print('flying speed of %s is %s' %(self.name, self._speed))
        return self._speed

manager2.py 模塊如下:

# manager2.py 模塊

import time
from animal2 import (Animal,Cat,Bird)

class Manager():
    def __init__(self,animal):
        self.animal = animal
        
    def recordTime(self):
        self.__t = time.time()
        print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
        self.animal.getSpeedBehavior()

    def getFeedingTime(self):
        return '%0.f'%(self.__t,)  

recordTime方法非常清爽,不需要任何if邏輯,只需要調用我們定義的Animal類的基方法getSpeedBehavior即可。

在使用上面所有類時,Manager(jiafeimao)傳入Cat類實例時,recordTime方法調用就被自動指向Cat實例的getSpeedBehavior方法;

Manager(haiying)傳入Bird類實例時,自動指向Bird實例的getSpeedBehavior方法,這就是多態和它的價值,Manager類的方法不必每次都修改,保證了類的封裝性。


if __name__ == "__main__":
    jiafeimao = Cat('jiafeimao',2,'gray','CatGenre')
    haiying = Bird('haiying',40,'blue','BirdGenre')

    Manager(jiafeimao).recordTime()
    print('#'*30)
    Manager(haiying).recordTime()  

總結

以上就是面向對象編程專題的基礎部分,大綱如下:

感謝華章出版社對原創文章的大力支持,贈送3本《機器學習:算法視角(原書第2版)》,機器學習領域暢銷教材知名媒體推薦的十大機器學習入門教材之一,作者都是名校教授大咖。

中獎讀者綜合考慮留言質量,參與度和讚賞情況,29日回覆中獎留言。長按下方二維碼查看此書詳情,明天京東有滿減活動。

全文4000多字,100%用心原創作品。歡迎點讚、在看和轉發支持,這樣我會更有動力寫好下一篇OOP編程的進階部分,謝謝。

相關焦點

  • Python黑帽編程2.9 面向對象編程
    硬體本質上處理的是信號,在此基礎上,我們給硬體賦予了一定的「邏輯思維」能力,為了方便硬體幫我們做事,抽象出了指令的概念,進而出現了彙編語言,然後有了Pascal和C這樣的標準的結構化語言。語言一路向上發展,都是根植於指令的,根植於指令就意味著流程和數據代表了一切,數據的變化成為我們表達和抽象這個世界的根本。
  • Python面向對象程式語言
    Python (發音:[ 'paiθ(ə)n; (US) 'paiθɔn ]n.蟒蛇,巨蛇 ),是一種面向對象的解釋性的電腦程式設計語言面向對象————Python即支持面向過程的編程也支持面向對象的編程。在「面向過程」的語 言中,程序是由過程或僅僅是可重用代碼的函數構建起來的。在「面向對象」的語言中,程序是由數據和功能組合而成的對象構建起來的。與其他主要的語言如 C++和Java相比,Python以一種非常強大又簡單的方式實現面向對象編程。
  • Python基礎知識——python面向對象的解釋型計算機程式語言
    python面向對象的解釋型計算機程式語言。,c語言,Java語言,Python語言,JavaScript語言高級計算機程式語言,分為:解釋型程式語言和編譯型程式語言Python(程序+翻譯器)翻譯軟體:一種稱為虛擬機,一種稱為解釋器c語言,c++語言,Go語言 屬於編譯型的寫好程序,通過編譯器把程序編程機器能執行的程序(含有機器碼),把程序給用戶即可。
  • 如何理解 Python 中的面向對象編程?
    現如今面向對象編程的使用非常廣泛,本文我們就來探討一下Python中的面向對象編程。
  • opencv-python獲取圖像:面向對象與面向過程
    下面是分別用面向過程與面向對象的編程方法實現讀取本地圖像和打開攝像頭兩段代碼:# -*- coding: utf-8 -*-"""面向過程的編程方法,用函數把解決問題的步驟一步一步實現。運行環境:win10系統 python==3.6 opencv-contrib-python== 4.1.0第一行「# -*- coding: utf-8 -*-」 告訴Python解釋器,按照UTF-8編碼讀取原始碼"""import
  • 史上最全 Python 面向對象編程
    作者:浪子燕青    來自:http://www.langzi.fun/Python面向對象編程.html面向對象編程和函數式編程
  • Python面向對象編程的基本概念
    九道門商業數據分析學院提供介紹在學習面向對象的編程時。我決定深入了解它的歷史,結果令人著迷。術語「面向對象程序設計」(OOP)是艾倫·凱(Alan Kay)在1966年讀研究生時提出的。名為Simula的語言是第一種具有面向對象編程功能的程式語言。它是在1967年開發的,用於製作仿真程序,其中最重要的信息稱為對象。
  • python面向對象,小白從零開始,python基礎入門,你會了嗎?
    Python,是一種面向對象的解釋型電腦程式設計語言,以簡單、易學、速度快等優點,是大部分想進入IT行業的從業人的選擇。IEEE發布2017年程式語言排行榜:Python超過JAVA程式語言排名第一。所以Python非常熱門的程式語言!
  • 一文看懂Python面向對象編程(Python學習與新手入門必看)-絕對原創
    def show(self):        print("Name: {}.__score = score    # 定義列印學生信息的方法    def show(self):        print("Name: {}. Score: {}".format(self.name, self.
  • 一篇非常全的Python 面向對象編程
    轉自:浪子燕青from:  Python編程開發http://www.langzi.fun/Python面向對象編程
  • 史上最全Python面向對象編程
    學神IT教育:XueGod-IT最負責任的線上直播教育平臺面向對象編程中,將函數和變量進一步封裝成類,類才是程序的基本元素
  • python入門——面向對象編程基礎到進階之枚舉類Enum
    ## 文章目錄 面向對象編程 @property 使用枚舉類 # 面向對象編程 ## @property 為了避免實例對象屬性的值被隨意修改
  • 使用 Python 學習面向對象的編程 | Linux 中國
    如果你已經聽過面向對象編程object-oriented programming(OOP)這個術語,那麼你可能會對類的用途有一些概念。程式設計師傾向於將類視為一個虛擬對象,有時與物理世界中的某些東西直接相關,有時則作為某種編程概念的表現形式。無論哪種表示,當你想要在程序中為你或程序的其他部分創建「對象」時,你都可以創建一個類來交互。
  • 史上最全 Python 面向對象編程技巧!
    面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。面向過程編程:1. 導入各種外部庫2. 設計各種全局變量3. 寫一個函數完成某個功能4. 寫一個函數完成某個功能5. 寫一個函數完成某個功能6. 寫一個函數完成某個功能7.
  • python面向對象三大特徵
    大概所有變成初學者初學者最頭疼的第一道坎就是面向對象的理解封裝從封裝本身去理解 就是把小貓小狗用袋子裝起來,然後把袋子的小口封上私有化方法:方法的私有化可以保護好一些核心的代碼,可以添加條件,是別人不能不滿足條件的更改,進行代碼的保護,python
  • 轉載 | 史上最全 Python 面向對象編程
    面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。導入各種外部庫設計各種全局變量決定你要的類給每個類提供完整的一組操作明確地使用繼承來表現不同類之間的共同點根據需要,決定是否寫一個 main 函數作為程序入口面向對象編程中,將函數和變量進一步封裝成類,類才是程序的基本元素,它將數據和操作緊密地連結在一起,並保護數據不會被外界的函數意外地改變。
  • 【AICAMP —— Python】入門系列!(6. 面向對象)
    面向對象(Object Oriented)目前我們大部分語言的設計都是面向對象的,說到面向對象,其實說起來挺容易理解,但是在實際運用的時候就會發現還是有很多講究的。在面向對象基礎之上,還包括了面向對象設計(OOD), 面向對象分析(OOA),面向對象編程(Object Oriented Programming),反正就是一句話,面向對象!
  • R用戶Python指南:面向對象編程
    本文為R用戶Python指南系列第8篇,Python代碼主要取自《Python編程:從入門到實踐》第9章,R代碼根據Python代碼複寫。本文使用rmarkdown和prettydoc模板生成格式,代碼塊中#之後的內容為注釋,##之後的內容為代碼輸出結果。本文使用的R包主要為stringr和R6,需預先加載。
  • TIA Portal面向對象編程入門
    儘管時至今日依然有少數人質疑面向對象的編程思想,但我們看到的是面向對象技術發展的越來越好,無論是後端語言(JAVA、C#)或者前端語言(JavaScript、TypeScript),無一不是完全的支持面向對象技術。現在高校的PLC教材基本上採用的還是五六十年前的編程理念,將PLC定位為傳統繼電器控制的替代,以軟元件、寄存器這種古老落後的概念來講授這一門日新月異的現代工業控制編程技術。
  • C風格的面向對象編程
    面向對象編程(OOP),最早是C++、java等面向對象語言的一個核心特點,之後發展成了一種編程思想。面向對象編程的主要特點是,把屬性(數據)與方法(函數)按照類型綁定,並按照類型之間的關係分層分模塊設計,通過基類指針和虛函數實現多態。