Python25 面向對象

該文章部分轉載,部分原創

引子

你現在是一家遊戲公司的開發人員,現在需要你開發一款叫做<人狗大戰>的遊戲,你就思考呀,人狗作戰,那至少需要2個角色,一個是人, 一個是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎麼描述這種不同的角色和他們的功能呢?

你搜羅了自己掌握的所有技能,寫出了下面的程式碼來描述這兩個角色

def person(name,age,sex,job):   #這類函數相當於模板      data = {          'name':name,          'age':age,          'sex':sex,          'job':job      }        return data    def dog(name,dog_type):      data = {          'name':name,          'type':dog_type      }      return data

上面兩個方法相當於造了兩個模子,遊戲開始,你得生成一個人和狗的實際對象吧,怎麼生成呢?

d1 = dog("李磊","京巴")    p1 = person("嚴帥",36,"F","運維")    p2 = person("林海峰",27,"F","Teacher")

兩個角色對象生成了,狗和人還有不同的功能呀,狗會咬人,人會打狗,對不對? 怎麼實現呢,。。想到了, 可以每個功能再寫一個函數,想執行哪個功能,直接 調用 就可以了,對不?

def bark(d):      print("dog %s:wang.wang..wang..."%d['name'])    def walk(p):      print("person %s is walking..." %p['name'])    d1 = dog("李磊","京巴")  p1 = person("嚴帥",36,"F","運維")  p2 = person("林海峰",27,"F","Teacher")    walk(p1) bark(d1)

上面的功能實現的簡直是完美!

但是仔細玩耍一會,你就不小心幹了下面這件事

p1 = person("嚴帥",36,"F","運維")  bark(p1) #把人的對象傳給了狗的方法

事實 上,這並沒出錯。很顯然,人是不能調用狗的功能的,如何在程式碼級別實現這個限制呢?

def person(name, age, sex, job):      def walk(p):          print("person %s is walking..." % p['name'])        data = {          'name': name,          'age': age,          'sex': sex,          'job': job,          'walk': walk      }        return data    def dog(name, dog_type):      def bark(d):            print("dog %s:wang.wang..wang..." % d['name'])        data = {          'name': name,          'type': dog_type,          'bark': bark        #將bark這個函數的記憶體地址retur給dog      }        return data    d1 = dog("李磊","京巴")  p1 = person("嚴帥",36,"F","運維")  p2 = person("林海峰",27,"F","Teacher")    print (d1['bark'](d1))  #(d1['bark'](d1)) == (data['bark'](data))    # d1['bark'](p1) 把人的對象傳給了狗的方法    執行結果:  dog 李磊:wang.wang..wang...  None    #這裡是因為bark函數沒有被return,所以為空

此時p1無法傳入bark函數,因為bark函數存在dog函數中;且p1變數不能等於dog函數,因為p1的參數數量和dog函數的形參不一樣。

p2['walk'](p2)    執行結果:  person 林海峰 is walking...

目前已經解決了兩個 角色 之間的功能混用

你是如此的機智,這樣就實現了限制人只能用人自己的功能啦。

但,我的哥,不要高興太早,剛才你只是阻止了兩個完全 不同的角色 之前的功能混用, 但有沒有可能 ,同一個種角色,但有些屬性是不同的呢? 比如 ,大家都打過cs吧,cs里有警察和恐怖份子,但因為都 是人, 所以你寫一個角色叫 person(), 警察和恐怖份子都 可以 互相射擊,但警察不可以殺人質,恐怖分子可以,這怎麼實現呢? 你想了說想,說,簡單,只需要在殺人質的功能里加個判斷,如果是警察,就不讓殺不就ok了么。 沒錯, 這雖然 解決了殺人質的問題,但其實你會發現,警察和恐怖分子的區別還有很多,同時又有很多共性,如果 在每個區別處都 單獨做判斷,那得累死。

你想了想說, 那就直接寫2個角色吧, 反正 這麼多區別, 我的哥, 不能寫兩個角色呀,因為他們還有很多共性 , 寫兩個不同的角色,就代表 相同的功能 也要重寫了,是不是我的哥? 。。。

好了, 話題就給你點到這, 再多說你的智商 也理解不了了!

像以上這種情況就可以使用面向對象了。 面向對象用於實現更複雜的一些關係描述。

面向過程 VS 面向對象

編程範式

編程是 程式 員 用特定的語法+數據結構+演算法組成的程式碼來告訴電腦如何執行任務的過程 , 一個程式是程式設計師為了得到一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有很多種不同的方式, 對這些不同的編程方式的特點進行歸納總結得出來的編程方式類別,即為編程範式。 不同的編程範式本質上代表對各種類型的任務採取的不同的解決問題的思路, 大多數語言只支援一種編程範式,當然也有些語言可以同時支援多種編程範式。 兩種最重要的編程範式分別是面向過程編程和面向對象編程。

面向過程編程(Procedural Programming)

Procedural programming uses a list of instructions to tell the computer what to do step-by-step. 面向過程編程依賴 – 你猜到了- procedures,一個procedure包含一組要被進行計算的步驟, 面向過程又被稱為top-down languages, 就是程式從上到下一步步執行,一步步從上到下,從頭到尾的解決問題 。基本設計思路就是程式一開始是要著手解決一個大的問題,然後把一個大問題分解成很多個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟範圍內解決。

舉個典型的面向過程的例子, 資料庫備份, 分三步,連接資料庫,備份資料庫,測試備份文件可用性。

程式碼如下:

def db_conn():      print("connecting db...")    def db_backup(dbname):      print("導出資料庫...",dbname)      print("將備份文件打包,移至相應目錄...")    def db_backup_test():      print("將備份文件導入測試庫,看導入是否成功")    def main(): #main是一個大塊,這個大塊集成了很多小塊(將很多獨立的函數集成到一塊)      db_conn()      db_backup('my_db')      db_backup_test()    if __name__ == '__main__':      main()

這樣做的問題也是顯而易見的,就是如果你要對程式進行修改,對你修改的那部分有依賴的各個部分你都也要跟著修改, 舉個例子,如果程式開頭你設置了一個變數值 為1 , 但如果其它子過程依賴這個值 為1的變數才能正常運行,那如果你改了這個變數,那這個子過程你也要修改,假如又有一個其它子程式依賴這個子過程 , 那就會發生一連串的影響,隨著程式越來越大, 這種編程方式的維護難度會越來越高。 所以我們一般認為, 如果你只是寫一些簡單的腳本,去做一些一次性任務,用面向過程的方式是極好的,但如果你要處理的任務是複雜的,且需要不斷迭代和維護 的, 那還是用面向對象最方便了。

面向對象編程

OOP(object oriented programing)編程是利用「類」和「對象」來創建各種模型來實現對真實世界的描述,使用面向對象編程的原因一方面是因為它可以使程式的維護和擴展變得更簡單,並且可以大大提高程式開發效率 ,另外,基於面向對象的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。

類:相當於模板 對象:相當於角色 面向對象:相當於用類和對象創建各種模型

面向對象的幾個核心特性如下

Class 類

一個類即是對一類擁有相同屬性的對象的抽象、藍圖、原型(相當於模板,都有這個模板中定義的屬性)。在類中定義了這些對象的都具備的屬性(variables(data))、共同的方法(如人物模型,都有眼、手、腳等)

Object 對象

一個對象即是一個類的實例化後實例(之前人與狗的小遊戲程式中,將參數傳入到函數中,形成一個字典data,這個過程就是實例化,而實例化後的data就是實例),一個類必須經過實例化後方可在程式中調用(函數如果沒有被實例化是沒法調用的),一個類可以實例化多個對象(比如張三和李四兩個對象),每個對象亦可以有不同的屬性(一個叫張三,一個叫李四,名字的屬性就不同),就像人類是指所有人,每個人是指具體的對象,人與人之前有共性,亦有不同

面向對象的特性(能帶來什麼好處)

  • Encapsulation 封裝 在類中對數據的賦值、內部調用對外部用戶是透明的,這使類變成了一個膠囊或容器,裡面包含著類的數據和方法 封裝: 防止數據被篡改和泄露 使外部程式不需要關注對象的內部構造,只需要通過此對象對外提供的介面進行直接訪問即可(比如QQ,你只需要知道怎麼使用即可,不需要了解QQ內部的構造、程式碼等內容)
  • Inheritance 繼承 一個類可以派生出子類,在這個父類里定義的屬性、方法自動被子類繼承

通過父類-》子類的方式以最小程式碼量實現 不同角色的共同點和不通點

  • Polymorphism 多態 多態是面向對象的重要特性,簡單點說:「一個介面,多種實現」,指一個基類中派生出了不同的子類,且每個子類在繼承了同樣的方法名的同時又對父類的方法做了不同的實現,這就是同一種事物表現出的多種形態。 編程其實就是一個將具體世界進行抽象化的過程,多態就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。 對不同類的對象發出相同的消息將會有不同的行為。比如,你的老闆讓所有員工在九點鐘開始工作, 他只要在九點鐘的時候說:「開始工作」即可,而不需要對銷售人員說:「開始銷售工作」,對技術人員說:「開始技術工作」, 因為「員工」是一個抽象的事物, 只要是員工就可以開始工作,他知道這一點就行了。至於每個員工,當然會各司其職,做各自的工作。 多態允許將子類的對象當作父類的對象使用,某父類型的引用指向其子類型的對象,調用的方法是該子類型的方法。這裡引用和調用方法的程式碼編譯前就已經決定了,而引用所指向的對象可以在運行期間動態綁定

上面的術語比較難理解,其實多台就是繼承一個父類而展現的多種形態,比如人,人分為白人、黑人、黃種人,這三種皮膚的人都屬於人,但是卻以不通的形態展現出來,每一種皮膚的人呢又都會說話,而說話呢又分了很多種語言,這就是在不斷的繼承父類,然後又以多種形態展現出來的方式。

面向對象編程(Object-Oriented Programming )介紹

對於程式語言的初學者來講,OOP不是一個很容易理解的編程方式,大家雖然都按老師講的都知道OOP的三大特性是繼承、封裝、多態,並且大家也都知道了如何定義類、方法等面向對象的常用語法,但是一到真正寫程式的時候,還是很多人喜歡用函數式編程來寫程式碼,特別是初學者,很容易陷入一個窘境就是「我知道面向對象,我也會寫類,但我依然沒發現在使用了面向對象後,對我們的程式開發效率或其它方面帶來什麼好處,因為我使用函數編程就可以減少重複程式碼並做到程式可擴展了,為啥子還用面向對象?」。 對於此,我個人覺得原因應該還是因為你沒有充分了解到面向對象能帶來的好處,今天我就寫一篇關於面向對象的入門文章,希望能幫大家更好的理解和使用面向對象編程。

無論用什麼形式來編程,我們都要明確記住以下原則:

寫重複程式碼是非常不好的低級行為  你寫的程式碼需要經常變更 

開發正規的程式跟那種寫個運行一次就扔了的小腳本一個很大不同就是,你的程式碼總是需要不斷的更改,不是修改bug就是添加新功能等,所以為了日後方便程式的修改及擴展,你寫的程式碼一定要遵循易讀、易改的原則(專業數據叫可讀性好、易擴展)。

如果你把一段同樣的程式碼複製、粘貼到了程式的多個地方以實現在程式的各個地方調用 這個功能,那日後你再對這個功能進行修改時,就需要把程式里多個地方都改一遍,這種寫程式的方式是有問題的,因為如果你不小心漏掉了一個地方沒改,那可能會導致整個程式的運行都 出問題。 因此我們知道 在開發中一定要努力避免寫重複的程式碼,否則就相當於給自己再挖坑。

還好,函數的出現就能幫我們輕鬆的解決重複程式碼的問題,對於需要重複調用的功能,只需要把它寫成一個函數,然後在程式的各個地方直接調用這個函數名就好了,並且當需要修改這個功能時,只需改函數程式碼,然後整個程式就都更新了。

其實OOP編程的主要作用也是使你的程式碼修改和擴展變的更容易,那麼小白要問了,既然函數都能實現這個需求了,還要OOP干毛線用呢? 呵呵,說這話就像,古時候,人們打仗殺人都用刀,後來出來了槍,它的主要功能跟刀一樣,也是殺人,然後小白就問,既然刀能殺人了,那還要槍干毛線,哈哈,顯而易見,因為槍能更好更快更容易的殺人。函數編程與OOP的主要區別就是OOP可以使程式更加容易擴展和易更改。

小白說,我讀書少,你別騙我,口說無憑,證明一下,好吧,那我們就下面的例子證明給小白看。 相信大家都打過CS遊戲吧,我們就自己開發一個簡單版的CS來玩一玩。

暫不考慮開發場地等複雜的東西,我們先從人物角色下手, 角色很簡單,就倆個,恐怖份子、警察,他們除了角色不同,其它基本都 一樣,每個人都有生命值、武器等。 咱們先用非OOP的方式寫出遊戲的基本角色

#role 1  name = 'Alex'  role = 'terrorist'  weapon = 'AK47'  life_value = 100    #rolw 2  name2 = 'Jack'  role2 = 'police'  weapon2 = 'B22'  life_value2 = 100

上面定義了一個恐怖份子Alex和一個警察Jack,但只2個人不好玩呀,一干就死了,沒意思,那我們再分別建立一個恐怖分子和警察吧,

role 1  name = 'Alex'  role = 'terrorist'  weapon = 'AK47'  life_value = 100  money = 10000    #rolw 2  name2 = 'Jack'  role2 = 'police'  weapon2 = 'B22'  life_value2 = 100  money2 = 10000    #role 3  name3 = 'Rain'  role3 = 'terrorist'  weapon3 = 'C33'  life_value3 = 100  money3 = 10000    #rolw 4  name4 = 'Eric'  role4 = 'police'  weapon4 = 'B51'  life_value4 = 100  money4 = 10000

4個角色雖然創建好了,但是有個問題就是,每創建一個角色,我都要單獨命名,name1,name2,name3,name4…,後面的調用的時候這個變數名你還都得記著,要是再讓多加幾個角色,估計調用時就很容易弄混啦,所以我們想一想,能否所有的角色的變數名都是一樣的,但調用的時候又能區分開分別是誰?

當然可以,我們只需要把上面的變數改成字典的格式就可以啦。

roles = {      1:{'name':'Alex',         'role':'terrorist',         'weapon':'AK47',         'life_value': 100,         'money': 15000,         },      2:{'name':'Jack',         'role':'police',         'weapon':'B22',         'life_value': 100,          'money': 15000,         },      3:{'name':'Rain',         'role':'terrorist',         'weapon':'C33',         'life_value': 100,         'money': 15000,         },      4:{'name':'Eirc',         'role':'police',         'weapon':'B51',         'life_value': 100,         'money': 15000,         },  }    print(roles[1]) #Alex  print(roles[2]) #Jack

很好,這個以後調用這些角色時只需要roles1,roles2就可以啦,角色的基本屬性設計完了後,我們接下來為每個角色開發以下幾個功能:

被打中後就會掉血的功能  開槍功能  換×××  買槍  跑、走、跳、下蹲等動作  保護人質(僅適用於警察)  不能殺同伴  。。。

我們可以把每個功能寫成一個函數,類似如下:

def shot(by_who):      #開了槍後要減×××數      pass  def got_shot(who):      #中槍後要減血      who[『life_value』] -= 10      pass  def buy_gun(who,gun_name):      #檢查錢夠不夠,買了槍後要扣錢      pass

so far so good, 繼續按照這個思路設計,再完善一下程式碼,遊戲的簡單版就出來了,但是在往下走之前,我們來看看上面的這種程式碼寫法有沒有問題,至少從上面的程式碼設計中,我看到以下幾點缺陷:

1.每個角色定義的屬性名稱是一樣的,但這種命名規則是我們自己約定的,從程式上來講,並沒有進行屬性合法性檢測,也就是說role 1定義的代表武器的屬性是weapon, role 2 ,3,4也是一樣的,不過如果我在新增一個角色時不小心把weapon 寫成了wepon , 這個程式本身是檢測 不到的 這個我們可以通過建立統一模板來解決,不需要一個一個的來建立,就不容易出錯。 2.terrorist 和police這2個角色有些功能是不同的,比如police是不能殺人質的,但是terrorist可能,隨著這個遊戲開發的更複雜,我們會發現這2個角色後續有更多的不同之處, 但現在的這種寫法,我們是沒辦法 把這2個角色適用的功能區分開來的,也就是說,每個角色都可以直接調用任意功能,沒有任何限制。 3.我們在上面定義了got_shot()後要減血,也就是說減血這個動作是應該通過被擊中這個事件來引起的,我們調用get_shot(),got_shot()這個函數再調用每個角色里的life_value變數來減血。 但其實我不通過got_shot(),直接調用角色roles[role_id][『life_value』] 減血也可以呀,但是如果這樣調用的話,那可以就是簡單粗暴啦,因為減血之前其它還應該判斷此角色是否穿了防彈衣等,如果穿了的話,傷害值肯定要減少,got_shot()函數里就做了這樣的檢測,你這裡直接繞過的話,程式就亂了。 因此這裡應該設計 成除了通過got_shot(),其它的方式是沒有辦法給角色減血的,不過在上面的程式設計里,是沒有辦法實現的。 4.現在需要給所有角色添加一個可以穿防彈衣的功能,那很顯然你得在每個角色里放一個屬性來存儲此角色是否穿 了防彈衣,那就要更改每個角色的程式碼,給添加一個新屬性,這樣太low了,不符合程式碼可復用的原則

上面這4點問題如果不解決,以後肯定會引出更大的坑,有同學說了,解決也不複雜呀,直接在每個功能調用時做一下角色判斷啥就好了,沒錯,你要非得這麼霸王硬上弓的搞也肯定是可以實現的,那你自己就開發相應的程式碼來對上面提到的問題進行處理好啦。 但這些問題其實能過OOP就可以很簡單的解決。

之前的程式碼改成用OOP中的「類」來實現的話如下:

class Role(object):      def __init__(self,name,role,weapon,life_value=100,money=15000):          self.name = name          self.role = role          self.weapon = weapon          self.life_value = life_value          self.money = money          #以上程式碼是屬性        def shot(self):          print("shooting...")        def got_shot(self):          print("ah...,I got shot...")        def buy_gun(self,gun_name):          print("just bought %s" %gun_name)      #以上程式碼是功能    r1 = Role('Alex','police','AK47') #生成一個角色  r2 = Role('Jack','terrorist','B22')  #生成一個角色  #以上程式碼是角色

先不考慮語法細節,相比靠函數拼湊出來的寫法,上面用面向對象中的類來寫最直接的改進有以下2點:

程式碼量少了近一半  角色和它所具有的功能可以一目了然看出來

在真正開始分解上面程式碼含義之之前,我們現來了解一些類的基本定義 類的語法

class Dog(object):        print("hello,I am a dog!")  #這裡相當於一個功能    d = Dog() #實例化這個類,  #此時的d就是類Dog的實例化對象    #實例化,其實就是以Dog類為模版,在記憶體里開闢一塊空間,存上數據,賦值成一個變數名    執行結果:  hello, i am a dog.

上面的程式碼其實有問題,想給狗起名字傳不進去。

class Dog(object):        def sayhi(self):            print("hello,I am a dog" )  #功能不應該直接放在class下,應該定義在函數中    d = Dog()   #第一條狗  d2 = Dog()  #第二條狗    d.sayhi()  d2.sayhi()    執行結果:  hello,I am a dog  hello,I am a dog  #可以看到根本分不清是哪一條狗的資訊

class Dog(object):        def __init__(self,name):          self.name = name        def sayhi(self):          print("hello,I am a dog , my name is" name )    #在當前函數中沒有name這個參數和變數,而且也無法到__init__這個函數中去調用,name屬於局部參數    d = Dog("maomao")  d2 = Dog("lele")    d.sayhi()  d2.sayhi()    執行結果:  Traceback (most recent call last):    File "E:/Python/練習程式碼/003.py", line 14, in <module>      d.sayhi()    File "E:/Python/練習程式碼/003.py", line 8, in sayhi      print("hello,I am a dog , my name is" ,name)  NameError: name 'name' is not defined   #報錯name找不到

鳥屬於一個類,而百靈鳥屬於鳥的一個子類,鳥是百靈鳥的超類(父類)

class Person:        def setName(self,name):          self.abc = name        def getName(self):          return self.abc        def greet(self):          print ("hello ,world! I'm %s." % self.abc)    foo = Person()  #foo是Person()的對象,foo會將自己這個對象作為第一參數傳入函數中,也就是傳給self;  實例化後產生的對象(foo)叫 實例,所以self就是實例本身  bar = Person()  foo.setName('Luke') #將"Luke"當做第二參數傳入參數中,也就是傳給name;定義self.abc(相當於foo.abc)這個新變數==name("Luke");然後看到其他函數也都繼承了self這個參數,也就是說self.abc可以被該類中的其他任何函數(方法)繼承使用。  bar.setName('Amy')  #bar.setName('Amy')==Person.setName('Amy')  foo.greet() #可以看做Person.greet(foo)  bar.greet()    執行結果:  hello ,world! I'm Luke.  hello ,world! I'm Amy.
class Dog(object):        def __init__(self,name):    #構造函數,構造方法==初始化方法,__init__是創建類時必寫的內容,實例傳遞參數需要傳給構造函數。 這樣才能被其他類的方法所調用。            self.abc = name        def sayhi(self):    #類的方法,在創建任何一個方法時,self都會被自動創建。          print("hello,I am a dog , my name is" ,self.abc)    d = Dog("maomao")   # 這裡相當於調用了 def __init__(self,name),將d傳給self,將"maomao"傳給name  d2 = Dog("lele")    d.sayhi()   #相當於Dog.sayhi()  d2.sayhi()

class Dog(object):        def __init__(self,name,dog_type):          self.name = name          self.type = dog_type        def sayhi(self):            print("hello,I am a dog, my name is ",self.name)    d = Dog('LiChuang',"京巴")  #先去實例化,把Dog這個模板變成具體的對象,d就成為了實例化後的對象  d.sayhi()   #在調用這個對象中的sayhi功能

為什麼有init? 為什麼有self? 此時的你一臉蒙逼,相信不畫個圖,你的智商是理解不了的!  

畫圖之前, 你先注釋掉這兩句

# d = Dog('LiChuang', "京巴")  # d.sayhi()    print(Dog) 

沒實例直接列印Dog輸出如下

<class '__main__.Dog'>

這代表什麼?代表 即使不實例化,這個Dog類本身也是已經存在記憶體里的對不對, yes, cool,那實例化時,會產生什麼化學反應呢?


class Dog(object):        def __init__(self,name):          self.abc = name        def sayhi(self):          print("hello,I am a dog , my name is" ,self.abc)        def eat(self,food):          print ("%s is eating %s" %(self.abc,food)) #這裡的food在本函數中可以直接調用,只有定義了self.food=food這個變數,才可以使用self.food方式    d = Dog("maomao")  d2 = Dog("lele")    d.sayhi()  d2.sayhi()    d.eat('baozi')    執行結果:  hello,I am a dog , my name is maomao  hello,I am a dog , my name is lele  maomao is eating baozi

私有屬性

class Role(object):      def __init__(self,name,role,weapon,life_value=100,money=15000):          self.name = name    #這個name相當於屬性或者叫成員變數          self.role = role          self.weapon = weapon          self.life_value = life_value          self.money = money          self.__heart="Normal"   #私有屬性:對外是不可見的,是不能被訪問的          #以上程式碼是屬性        def shot(self): #定義其中一個方法,或者叫動態屬性          print("shooting...")        def got_shot(self):          print("ah...,I got shot...")          self.__heart = "Die"    #私有屬性可以被內部訪問          print (self.__heart)    #在內部訪問私有屬性        def get_heart(self):    #對外部提供只讀訪問介面          return self.__heart #讓私有屬性可以訪問,但不可以修改        def buy_gun(self,gun_name):          print("just bought %s" %gun_name)      #以上程式碼是功能    r1 = Role('Alex','police','AK47') #生成一個角色  r2 = Role('Jack','terrorist','B22')  #生成一個角色  #以上程式碼是角色
print (r1.name)  print (r1.__heart)  執行結果:  Alex    Traceback (most recent call last):    File "E:/python/程式碼練習/002.py", line 27, in <module>      print (r1.__heart)  AttributeError: 'Role' object has no attribute '__heart'  #私有屬性無法被外部訪問
print (r1.name)    r1.got_shot()    執行結果:  Alex  ah...,I got shot...  Die     #可以看到是可以被內部(got_shot)所訪問的
print (r1.get_heart())  執行結果:  Normal  #此時這個私有屬性可以被訪問,但不能被修改
print( r1._Role__heart) #強制訪問私有屬性  執行結果:  Normal    訪問got_shot方法中的私有屬性:  r1.got_shot()   #運行了r1.got_shot方法,這裡__heart就是"Die"了  print( r1._Role__heart) #在去取值__heart就是,取值的就是got_shot中的__heart了  執行結果:  ah...,I got shot...  Die  Die

公有屬性

所有實例化類的對象都可以訪問的屬性,叫做公有屬性

class Role(object):        nationality = "USA"     #公有屬性,在class下面直接定義的變數就是公有屬性        def __init__(self, name, role, weapon, life_value=100, money=15000):          self.name = name    #成員屬性,指定了具體參數,參數不同就表示是不同的成員          self.role = role          self.weapon = weapon          self.life_value = life_value          self.money = money          self.__heart = "Normal"          # 以上程式碼是屬性        def shot(self):     #這個函數也屬於公有屬性,不管有多少個實例化對象,都是到class下去調用這個函數(方法)          print("shooting...")        def got_shot(self):          print("ah...,I got shot...")          self.__heart = "Die"        def buy_gun(self, gun_name):          print("just bought %s" % gun_name)          # 以上程式碼是功能    r1 = Role('Alex', '警察', 'AK47') #生成一個角色  r2 = Role('dashan', '警犬', 'B22')  #生成一個角色
print( r1.weapon)  執行結果:  AK47    #可以看到這個AK47武器只能被r1這個實例化後的對象訪問,不能被r2訪問,所以就不滿足公有屬性的條件;  這個AK47屬於成員屬性,只屬r1;r2要訪問r1的成員屬性也可以,但必須經過r1這個對象來訪問
print( r1.nationality)  print( r2.nationality)  #r1和r2這兩個對象都可以訪問nationnality這個公有屬性
Role.nationality = 'JP' 修改類的公有屬性    print( r1.nationality)  #r1會現在當前局部去找nationality這個變數,如果找不到就會去超類(父類)中去找,這裡說明r1是引用這個公有屬性就是超類的nationality  print( r2.nationality)    執行結果:  JP  #可以看到r1和r2的公有屬性都被修改了,在類下會修改所有對象的公有屬性  JP
Role.nationality = 'JP'    print( r1.nationality)  print( r2.nationality)    r1.nationality = 'CN'   #這裡是r1新創建的一個變數叫nationality,雖然和超類中的nationality同名,但是它們兩個之間沒有任何關係;  此時局部已經有一個叫nationality的變數了,就不會到超類中(類似不會去全局中)找nationality變數了。   這個局部的nationality不是公有屬性,只有class下的nationality才是。  print( r1.nationality)  print( r2.nationality)    執行結果:  JP  JP  CN  #可以看到這裡被修改了  JP    #下面這幾個程式碼用於把公有屬性的方法 傳到外部  def shot2(abc):       print (abc)       print ('in the shot2')      # print (self)    r1.shot = shot2   #相當於將shot2代替r1.shot了    r1.shot(r1.nationality)  #所以這裡相當於直接調用shot2這個函數    shot2(r1.nationality)        #這裡相當於直接    r1.shot(r1.nationality) 和shot2(r1.nationality)執行結果:  CN  in the shot2  CN  in the shot2

好了,明白 了類的基本定義,接下來我們一起分解一下上面的程式碼分別 是什麼意思

class Role(object): #定義一個類, class是定義類的語法,Role是類名,(object)是新式類的寫法,必須這樣寫,以後再講為什麼      def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函數,在生成一個角色時要初始化的一些屬性就填寫在這裡          self.name = name #__init__中的第一個參數self,和這裡的self都 是什麼意思? 看下面解釋          self.role = role          self.weapon = weapon          self.life_value = life_value          self.money = money

上面的這個init()叫做初始化方法(或構造方法), 在類被調用時,這個方法(雖然它是函數形式,但在類中就不叫函數了,叫方法)會自動執行,進行一些初始化的動作,所以我們這裡寫的init(self,name,role,weapon,life_value=100,money=15000)就是要在創建一個角色時給它設置這些屬性,那麼這第一個參數self是干毛用的呢?

初始化一個角色,就需要調用這個類一次:

r1 = Role('Alex','police','AK47』) #生成一個角色 , 會自動把參數傳給Role下面的__init__(...)方法  r2 = Role('Jack','terrorist','B22』)  #生成一個角色

我們看到,上面的創建角色時,我們並沒有給init傳值,程式也沒未報錯,是因為,類在調用它自己的init(…)時自己幫你給self參數賦值了,

r1 = Role('Alex','police','AK47』) #此時self 相當於 r1 ,  Role(r1,'Alex','police','AK47』)  r2 = Role('Jack','terrorist','B22』)#此時self 相當於 r2, Role(r2,'Jack','terrorist','B22』)

為什麼這樣子?你拉著我說你有些猶豫,怎麼會這樣子? 你執行r1 = Role('Alex','police','AK47』) 時,python的解釋器其實幹了兩件事:

在記憶體中開闢一塊空間指向r1這個變數名 調用Role這個類並執行其中的init(…)方法,相當於Role.init(r1,'Alex','police',』AK47』),這麼做是為什麼呢? 是為了把'Alex','police',』AK47』這3個值跟剛開闢的r1關聯起來,是為了把'Alex','police',』AK47』這3個值跟剛開闢的r1關聯起來,是為了把'Alex','police',』AK47』這3個值跟剛開闢的r1關聯起來,重要的事情說3次, 因為關聯起來後,你就可以直接r1.name, r1.weapon 這樣來調用啦。所以,為實現這種關聯,在調用init方法時,就必須把r1這個變數也傳進去,否則init不知道要把那3個參數跟誰關聯呀。 明白了么哥?所以這個init(…)方法里的,self.name = name , self.role = role 等等的意思就是要把這幾個值 存到r1的記憶體空間里。

如果還不明白的話,哥,去測試一下智商吧, 應該不會超過70,哈哈。 為了暴露自己的智商,此時你假裝懂了,但又問, init(…)我懂了,但後面的那幾個函數,噢 不對,後面那幾個方法 為什麼也還需要self參數么? 不是在初始化角色的時候 ,就已經把角色的屬性跟r1綁定好了么? good question, 先來看一下上面類中的一個buy_gun的方法:

def buy_gun(self,gun_name):      print(「%s has just bought %s」 %(self.name,gun_name) )

上面這個方法通過類調用的話要寫成如下:

r1 = Role('Alex','police','AK47')  r1.buy_gun("B21」) #python 會自動幫你轉成 Role.buy_gun(r1,」B21")

執行結果 Alex has just bought B21 依然沒給self傳值 ,但Python還是會自動的幫你把r1 賦值給self這個參數, 為什麼呢? 因為,你在buy_gun(..)方法中可能要訪問r1的一些其它屬性呀, 比如這裡就訪問 了r1的名字,怎麼訪問呢?你得告訴這個方法呀,於是就把r1傳給了這個self參數,然後在buy_gun里調用 self.name 就相當於調用r1.name 啦,如果還想知道r1的生命值 有多少,直接寫成self.life_value就可以了。 說白了就是在調用類中的一個方法時,你得告訴人家你是誰。

好啦, 總結一下2點:

上面的這個r1 = Role('Alex','police','AK47』)動作,叫做類的「實例化」, 就是把一個虛擬的抽象的類,通過這個動作,變成了一個具體的對象了, 這個對象就叫做實例 剛才定義的這個類體現了面向對象的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體對象中,然後通過對象直接或者self間接獲取被封裝的內容


類的析構方法

class Role(object):        nationality = "USA"        def __init__(self, name, role, weapon, life_value=100, money=15000):          self.name = name          self.role = role          self.weapon = weapon          self.life_value = life_value          self.money = money          self.__heart = "Normal"          # 以上程式碼是屬性        def shot(self):          print("shooting...")        def got_shot(self):          print("ah...,I got shot...")          self.__heart = "Die"        def __del__(self):  #析構函數(析構方法)          print ("del.....run...")  #__del__在對象(r1)銷毀的時候被調用,當對象不再被使用時,__del__方法運行    r1 = Role('Alex', '警察', 'AK47')  # 生成一個角色    執行結果:  del.....run...  #程式碼只做了一個實例化,沒有主動去調用del這個方法依然自動執行了  析構函數是用來釋放記憶體空間的,比如一個全局變數被引用後那麼除非這個程式執行完成,那麼它就會一直佔用記憶體空間;  相關的引用被執行完成後依然會佔用記憶體空間,如果引用過多的話可能你得記憶體就不夠用了,所以需要將接下來不會再次調用,或者使用不到的引用佔用的記憶體空間給釋放出來,所以就用到了析構函數,它就是當程式執行完成後會自動調用該虛構函數,然後將記憶體釋放出來。    # 再次強調,析構函數只有在實例(對象)被銷毀的時候才會調用!!!   這個析構函數你是否寫都會被執行,因為默認有一個析構函數,只不過自己寫的話相當於將析構函數給重構了。

封裝

封裝最好理解了。封裝是面向對象的特徵之一,是對象和類概念的主要特性。

封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行資訊隱藏。


繼承

面向對象編程 (OOP) 語言的一個主要功能就是「繼承」。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。

通過繼承創建的新類稱為「子類」或「派生類」。

被繼承的類稱為「基類」、「父類」或「超類」。

繼承的過程,就是從一般到特殊的過程。(一般就是指能被繼承的都比較簡單,所以才更容易被繼承,繼承的子類就是比簡單要複雜一些,所以叫做一般到特殊的過程)

要實現繼承,可以通過「繼承」(Inheritance)和「組合」(Composition)來實現。

在某些 OOP 語言中,一個子類可以繼承多個基類。但是一般情況下,一個子類只能有一個基類,要實現多重繼承,可以通過多級繼承來實現。

多級繼承:父類>>子類>>子類……. 多重繼承:同時繼承多個類

繼承概念的實現方式主要有2類:實現繼承、介面繼承。 Ø 實現繼承是指使用基類的屬性和方法而無需額外編碼的能力; Ø 介面繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力(如:子類繼承父類的說話功能,但是會重置這個功能說不通的語言); 在考慮使用繼承時,有一點需要注意,那就是兩個類之間的關係應該是「屬於」關係。例如,Employee 是一個人,Manager 也是一個人,因此這兩個類都可以繼承 Person 類。但是 Leg 類卻不能繼承 Person 類,因為腿並不是一個人。

抽象類僅定義將由子類創建的一般屬性和方法。

OO開發範式大致為:劃分對象→抽象類→將類組織成為層次化結構(繼承和合成) →用類與實例進行設計和實現幾個階段。

繼承示例:

class Person(object):      #當不需要傳參數的時候,可以不創建構造函數      def talk(self):          print ("person is talking...1")    class BlackPerson(Person):  #繼承Person這個父類        pass    b = BlackPerson()  b.talk()    #繼承父類,會將其方法也繼承    執行結果:  person is talking...1
class Person(object):        def talk(self):          print ("person is talking...1")    class BlackPerson(Person):      #繼承後,自己也可以額外的創建其他方法      def walk(self):          print ("is walking......")    b = BlackPerson()  b.talk()  b.walk()    執行結果:  person is talking...1  is walking......
class Person(object):        def talk(self):          print ("person is talking...1")    class BlackPerson(Person):        def talk(self): #自己寫一個talk方法          print ("bala..bala.bala")        def walk(self):          print ("is walking......")    b = BlackPerson()  b.talk()  b.walk()    執行結果:  bala..bala.bala #如果本地局部有talk這個方法,就用本地的,沒有則用繼承的talk方法。  is walking......
class Person(object):        def __init__(self,name,age):    #創建構造函數          self.name = name          self.age = age        def talk(self):          print ("person is talking...1")    class BlackPerson(Person):      #子類 沒有寫構造函數,但是會繼承父類的構造函數      def talk(self):          print ("bala..bala.bala")        def walk(self):          print ("is walking......")    b = BlackPerson()  b.talk()  b.walk()    執行結果:  Traceback (most recent call last):    File "E:/python/程式碼練習/002.py", line 21, in <module>      b = BlackPerson()  TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'  #可以看到報錯了,這是因為子類繼承了父類的構造函數,繼承的構造函數需要傳參數    b = BlackPerson("smith",30) #修改該程式碼,傳入參數給繼承過來的構造函數中的形參    執行結果:  bala..bala.bala  is walking......  ##程式碼中並沒有列印參數,所以不會顯示
class Person(object):        def __init__(self,name,age):    #self==b,name和age等於子類(BlackPerson)的name和age          self.name = name          self.age = age          self.power = "Normal"   #self.power這裡並沒有傳入參數        def talk(self):          print ("person is talking...1")    class WhitePerson(Person):  #先創建一個白人函數方法備用      pass    class BlackPerson(Person):  #繼承Person父類        #現在需要給黑人函數增加一個獨有的功能「力量」,所以就需要創建構造函數;      #但是因為需要保持繼承父類的構造函數功能,這裡不能直接重構構造函數,否則就將繼承過來的構造函數給覆蓋了(局部優先引用),這樣父類的功能(name、age)就無法使用了; 所以最後需要不但能夠正常繼承父類的功能,自己還可以擴展;需要先繼承,在重構。        #class BlackPerson(Person)這裡已經相當於繼承了父類的構造函數,如果子類在創建__init__構造函數的話就是重構 構造函數        def __init__(self,name,age,strength): #重構 構造函數(重構後默認就就不繼承了)      #當實例化BlackPerson後候會將b傳給子類的self,將參數傳給子類形參;          Person.__init__(self,name,age)  #調用父類構造函數(同時繼承)          #因為重置了構造函數,所以需要調用父類的構造函數,調用了父類的構造函數後,相同名稱的屬性就會被父類覆蓋;在子類中新建且父類構造函數中沒有的,此時子類就實現了繼承,而且子類同時可以建立新的屬性。          #繼承構造函數,會調用Person的init方法,init方法要求有self、參數name和age。          print (self.name,self.age,self.power) #這裡print的都是Person這個父類的變數            self.strength = strength    #設置自己的擴展功能          print (self.strength)        def talk(self): #此處創建talk這個方法,就不會在繼承父類的talk方法,這裡相當於將talk方法也給重置了          Person.talk(self)   #既然不能繼承了,卻可以調用,我們這裡調用一下父類中的talk方法; 不過這樣沒有什麼意義,一般都是重構構造函數,很少有重構方法的。          print ("bala..bala.bala")        def walk(self):          print ("is walking......")    b = BlackPerson("smith",30,"strong") #實例化後的BlackPerson會繼承Person  b.talk()  b.walk()    執行結果:  smith 30 Normal  #雖然重構了構造函數,但調用了父類的構造函數,所以相同名稱的屬性就被覆蓋了(smith和30),而self.power並沒有重複,所以依然還是從父類繼承過來的結果("Normal")  strong  person is talking...1  bala..bala.bala  is walking......
#寫一個學校成員的程式碼,學生和老師都屬於成員,每個加進學校的成員都需要註冊  class SchoolMember(object): #類的命名首字母要大寫,這是命名規範      members = 0  # 初始學校人數為0,公有屬性,老師和學生都可以訪問      def __init__(self ,name ,age):          self.name = name          self.age = age          self.enroll()  # 成員註冊,用於被子類繼承        def enroll(self):          '''註冊'''          print ("just enrolled a new school member [%s]" % self.name)          SchoolMember.members +=1   #調用公有屬性(這裡不是繼承過來的)          #這裡不能使用self.member += 1,否則只會在實例中去相加了;不同的實例會在各自的實例中相加,這樣列印的時候肯定就不是所有成員總數了        def tell(self):          '''print成員資訊'''          print ('-------info:%s--------'%self.name)          for k,v in self.__dict__.items():              print ('t',k,v)            print ('-------end:%s--------')        def __del__(self):          print ('開出了[%s]...' % self.name)          SchoolMember.members -= 1    class Teacher(SchoolMember):      '''講師子類'''      def __init__(self,name,age,salary,course):          '''course教課'''          SchoolMember.__init__(self,name,age)          self.salary = salary          self.course = course        def teaching(self):          '''講課的功能'''          print ('Teacher [%s] is teaching [%s]' %(self.name,self.course))  class Student(SchoolMember):      '''學生子類'''      def __init__(self,name,age,course,tuition):          '''tuition學費'''          SchoolMember.__init__(self,name,age)          self.course = course          self.tuition = tuition          self.amount = 0 #amount交學費的功能,默認0元        def pay_tuition(self,amount):          '''交學費的功能'''          print ("student [%s] has just paied [%s]" %(self.name,amount))          self.amount += amount    t1 = Teacher("Wusir",28,3000,"Python")  s1 = Student("HaiTao",38,"PYS16",30000)  s2 = Student("LiChuang",12,"PYS16",11000)    print (SchoolMember.members)    del s2  #使用del函數會直接調用到s2父類中的析構函數__del__;  (del s2就會觸發Student繼承了SchoolMember下的def __del__(self),然後執行SchoolMember.members -= 1)  print (SchoolMember.members)    執行結果:  just enrolled a new school member [Wusir]  just enrolled a new school member [HaiTao]  just enrolled a new school member [LiChuang]  3  開出了[LiChuang]... #del s2時會直接跳到父類中的__del__(因為Student繼承了)  2  #下面兩個被刪除是因為在程式結束的時候會自動調用析構函數,如果使用debug一直執行的話就不會出現下面兩個資訊了  開出了[Wusir]...  開出了[HaiTao]...    print (t1.__dict__)  執行結果:  {'name': 'Wusir', 'age': 28, 'salary': 3000, 'course': 'Python'}  可以看到,可以將t1的形參和實際參數通過字典的方式列印出來    t1.tell()   #直接調用t1實例中的tell()方法  s1.tell()  s2.tell()  執行結果:  -------info:Wusir--------       name Wusir       age 28       salary 3000       course Python  -------end:%s--------  -------info:HaiTao--------       name HaiTao       age 38       course PYS16       tuition 30000       amount 0  -------end:%s--------  -------info:LiChuang--------       name LiChuang       age 12       course PYS16       tuition 11000       amount 0  -------end:%s--------
SchoolMember.__init__(self, name, age)    #經典類寫法  super(Teacher,self).__init__(name,age)  #新式類寫法,也是用來繼承父類的,效果一樣

多繼承

class SchoolMember(object):      members = 0      def __init__(self ,name, age):          self.name = name          self.age = age          self.enroll()        def enroll(self):          '''註冊'''          print("just enrolled a new school member [%s]" % self.name)          SchoolMember.members += 1        def tell(self):          '''print成員資訊'''          print('-------info:%s--------' % self.name)          for k, v in self.__dict__.items():              print('t', k, v)            print('-------end:%s--------')        def __del__(self):          print('開出了[%s]...' % self.name)          SchoolMember.members -= 1    class Second(object):   #新建一個父類      '''第二個父類'''        def test(self):          print ('test......................')    class Teacher(SchoolMember,Second): #繼承新的父類      '''講師子類'''        def __init__(self, name, age, salary, course):          '''course教課'''          super(Teacher,self).__init__(name,age)  #使用的是新式類          self.salary = salary          self.course = course        def teaching(self):          '''講課的功能'''          print('Teacher [%s] is teaching [%s]' % (self.name, self.course))    class Student(SchoolMember):      '''學生子類'''        def __init__(self, name, age, course, tuition):          '''tuition學費'''          SchoolMember.__init__(self, name, age)          self.course = course          self.tuition = tuition          self.amount = 0  # amount交學費的功能,默認0元        def pay_tuition(self, amount):          '''交學費的功能'''          print("student [%s] has just paied [%s]" % (self.name, amount))          self.amount += amount    t1 = Teacher("Wusir", 28, 3000, "Python")  t1.test()   #調用集成父類中的方法    s1 = Student("HaiTao", 38, "PYS16", 30000)  s2 = Student("LiChuang", 12, "PYS16", 11000)    print(SchoolMember.members)    del s2  print(SchoolMember.members)    執行結果:  just enrolled a new school member [Wusir]  test......................  #成功執行新父類中的test方法;  繼承多個父類這種方式實際中用的較少。  just enrolled a new school member [HaiTao]  just enrolled a new school member [LiChuang]  3  開出了[LiChuang]...  2  開出了[Wusir]...  開出了[HaiTao]...

#### 新式類 VS 經典類    class Person(object):  #新式類;  ,  class Person:   #經典類    SchoolMember.__init__(self, name, age)    #經典類寫法  super(Teacher,self).__init__(name,age)  #新式類寫法,也是用來繼承父類的,效果一樣

現在大家一般都寫新式類,新式類中的object是一個基類,通過Ctrl+object點進去可以看到如下:

這個object本身就是個基類,下面定義了很多方法,當更新python版本,如果在object加入了新的方法,我們默認就會繼承過來。

class A(object):      def __init__(self):          self.n = 'A'  class B(A): #繼承A      def __init__(self):          self.n = 'B'    class C(A): #繼承A      def __init__(self):          self.n = 'C'    class D(B,C):   #繼承B和C      def __init__(self):          self.n = 'D'    d = D()  print (d.n)    執行結果:  D   #每一個class都定義了self.n,肯定是局部的優先順序最高,所以結果是D

當前邏輯關係

class A(object):      def __init__(self):          self.n = 'A'  class B(A): #繼承A      def __init__(self):          self.n = 'B'    class C(A): #繼承A      def __init__(self):          self.n = 'C'    class D(B,C):   #繼承B和C      pass      #def __init__(self):          #self.n = 'D'    d = D()  print (d.n)    執行結果:  B   #從左到右先繼承了B中的self.n,與C中的self.n衝突的話,就不會再去繼承C的self.n了。
class A(object):      def __init__(self):          self.n = 'A'  class B(A):      pass      # def __init__(self):      #     self.n = 'B'    class C(A):      def __init__(self):          self.n = 'C'    class D(B,C):      pass      # def __init__(self):      #     self.n = 'D'    d = D()  print (d.n)    執行結果:  C   #從左到右,B去掉了,就會繼承C;  如果此時還有E、F什麼的,左面找不到的話,只要右邊存在就會一直向右查找,這叫做廣度查詢

總結繼承順序:D、B、C、A

class A:    #修改為經典類,然後復現上面程式碼的情況      def __init__(self):          self.n = 'A'  class B(A):      pass      # def __init__(self):      #     self.n = 'B'    class C(A):        def __init__(self):          self.n = 'C'    class D(B,C):      pass      # def __init__(self):      #     self.n = 'D'    d = D()  print (d.n)    執行結果:  C   #可以看到執行結果依然是C,好像看似經典類和新式類沒什麼區別,下面我們換到Python2的環境使用這一段程式碼測試
#依然是上面的程式碼  class A:      def __init__(self):          self.n = 'A'  class B(A):      pass      # def __init__(self):      #     self.n = 'B'    class C(A):        def __init__(self):          self.n = 'C'    class D(B,C):      pass      # def __init__(self):      #     self.n = 'D'    d = D()  print (d.n)

可以看到在linux系統中用python 2來執行該程式碼,結果卻是A,沒有進行廣度(橫著)查詢,而是進行了深度查找(豎著查找)。

class A:      pass      # def __init__(self):      #     self.n = 'A'  class B(A):      pass      # def __init__(self):      #     self.n = 'B'    class C(A):        def __init__(self):          self.n = 'C'    class D(B,C):      pass      # def __init__(self):      #     self.n = 'D'    d = D()  print (d.n)

可以看到將D、B、A都注釋掉以後,結果是C 在python3中,無論是經典類還是新式類,結果都廣度查找;在python2中經典類是深度查找,新式類是廣度查找。

最後建議:

    class創建方法和繼承方式都用新式類;      創建class用新式類,可以廣度查找,這樣查找時會更優。      繼承時使用super(新式類),可以同時繼承多重父類,不像經典類,必須指定父類的名稱(繼承多少個父類,就要指定幾個)才能繼承;當多繼承時要注意的是子類會從左到右的廣度查找順序來繼承,左邊的父類如果沒有找到則向右去找(廣度查找),這個廣度查找只是先去找構造函數和對應的屬性,否則就去找下一個父類的構造函數和屬性;   當繼承多個父類的時候,每個父類都有參數的話,只能繼承其中一個父類的參數,假如A父類有1、2兩個參數,B父類有3這一個參數,那麼1、2、3這個三個參數不能同時被繼承,所以在寫程式碼的時候,最好是寫只繼承一個父類即可!!!!!!!!!!  

如果一定要繼承多個父類的話,請按照下面舉例來寫程式碼:

class AA(object):   #父類AA      def __init__(self,A1,A2):          self.A1 = A1          self.A2 = A2  class BB(object):   #父類BB      def __init__(self,B1,B2):          self.B1 = B1          self.B2 = B2    class ZiLei(AA):      def __init__(self,A1,A2,name1,BB_obj): #先重構          super(ZiLei,self).__init__(A1,A2) #在繼承            self.BB = BB_obj    #這裡直接將BB這個類傳進來即可,通過這種方式關聯其他父類的內容          print (self.BB.B1)  #在self.BB後面再通過 點 來調用相應的內容          print (self.BB.B2)    BB_obj = BB('b111','b222')  #實例化BB這個類    zilei = ZiLei('a111','a222','name1111',BB_obj)  #將BB_obj這個實力傳入    執行結果:  b111  b222

多態

封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴展已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多態則是為了實現另一個目的——介面重用!多態的作用,就是為了類在繼承和派生的時候,保證使用「家譜」中任一類的實例的某一屬性時的正確調用。(舉例多態:比如都會說話,但是說的語言不同,這就是通過了不同的方式表現出來的,所以叫多態)

Pyhon 很多語法都是支援多態的,比如 len(),sorted(), 你給len傳字元串就返回字元串的長度,傳列表就返回列表長度。

在python中是不能直接支援多態的,需要間接的實現多態; 直接就是需要從父類調用子類中的方法,可是python實現不了,Java可以。

class Animal(object):      def __init__(self, name):  # Constructor of the class          self.name = name        def talk(self):              # Abstract method, defined by convention only          raise NotImplementedError("Subclass must implement abstract method")          #raise是主動使其發送一個錯誤到執行後的螢幕身上    class Cat(Animal):      def talk(self):          print('%s: 喵喵喵!' %self.name)    class Dog(Animal):      def talk(self):          print('%s: 汪!汪!汪!' % self.name)    c1 = Cat('小白')  d1 = Dog('小黑')    c1.talk()  d1.talk()    執行結果:  小白: 喵喵喵!  小黑: 汪!汪!汪!  #我們在執行的時候指定了c1.talk和d1.talk方法才會去執行,這裡需要指定兩個介面,而我們不想通過指定c1或d1才會執行兩個介面;  只想通過一個介面來實現方法的執行,只是在執行的時加入不同的形態(c1就是貓叫,加入d1就是狗叫),來實現一個介面,多種形態。
class Animal(object):      def __init__(self, name):  # Constructor of the class          self.name = name        def talk(self):              # Abstract method, defined by convention only          raise NotImplementedError("Subclass must implement abstract method")    class Cat(Animal):      def talk(self):          print('%s: 喵喵喵!' %self.name)    class Dog(Animal):      def talk(self):          print('%s: 汪!汪!汪!' % self.name)    def func(obj):  # 一個介面,多種形態      obj.talk()    c1 = Cat('小白')  d1 = Dog('小黑')    func(c1)    #將c1傳給obj參數,然後在執行obj.talk(),其實相當於變相執行c1.talk(),只不過可以利用這種方法實現一個借口,多種形態;   這只是一個這種的方法,並不是真正的多態實現方法。  func(d1)
class Animal(object):      def __init__(self, name):  # Constructor of the class          self.name = name        def talk(self):              # Abstract method, defined by convention only          raise NotImplementedError("Subclass must implement abstract method")    class Cat(Animal):      def talk(self):          print('%s: 喵喵喵!' %self.name)    class Dog(Animal):      def talk(self):          print('%s: 汪!汪!汪!' % self.name)    def func(obj):  # 一個介面,多種形態      obj.talk()    c1 = Cat('小白')  d1 = Dog('小黑')    Animal.talk(c1)  Animal.talk(d1)    執行結果:  Traceback (most recent call last):    File "E:/Python/練習程式碼/001.py", line 29, in <module>      Animal.talk(c1)    File "E:/Python/練習程式碼/001.py", line 7, in talk      raise NotImplementedError("Subclass must implement abstract method")  NotImplementedError: Subclass must implement abstract method    #可以看到並沒有執行成功,並報錯。    這是因為當使用Animal.talk(c1)時,在子類中talk這個方法已經被重寫了,重寫後只能通過c1.talk()這種方式調用了

面向對象補充

1、 什麼是面向對象編程?

– 之前在使用函數的時候,直接調用函數就會執行裡面程式碼實現想要的功能。 – 而面向對象是通過類 + 對象實現函數的調用。

2、 什麼是類、什麼是對象,它們之間又有什麼關係?

- class 類:      def 函數1():          pass      def 函數2():          pass    obj = 類() #obj是對象,對象就是將類實例化的過程  obj.函數1() #在面向對象中只不能直接調用函數,需要通過對象來調用函數(方法)    每創建一個對象,都會存在記憶體中,對象越多記憶體開銷越大;所以說從調用、使用的角度來看,直接調用函數是更方便的,而面向對象反而會更複雜。  在實際的工作當中面向對象(面向對象編程)能實現的功能,通過直接使用函數(函數式編程)都能實現。    有時候,通過函數式編程實現會比較麻煩,  而使用面向對象簡單、快速的就能實現。

3、 什麼時候適用面向對象?

  • 通過函數編程:

通過函數時編程創建可以連接伺服器、上傳文件、執行命令、關閉連接的功能。

def upload():   #該函數只能上傳文件      連接伺服器      上傳文件      關閉    def cmd():       #該函數只能執行命令      連接伺服器      執行命令      關閉    def cmd():      連接伺服器  #該函數既能上傳文件又能執行命令      上傳文件      執行命令      關閉

可以看到通過函數時編程,每做一個動作就需要連接伺服器,然後關閉伺服器,這樣不斷的進行了數次連接斷開操作。

  • 通過類來編程
class SSH:      def __init__(self,host,port,username,password):          self.host = ...        def connection(self):          # 去創建連接          self.conn = 和伺服器創建的連接對象()        def close(self):          # 關閉          self.conn.關閉        def upload(self):          self.conn  使用這個連接上傳文件        def cmd(self):          self.conn  使用這個連接執行命令    obj = SSH(參數)  #修改這裡的host(IP)和port參數就可以連接其他SSH 伺服器,所以面向對象的改造型比較強  obj.connection()  obj.cmd()  obj.upload()  obj.cmd()  obj.upload()  obj.cmd()  obj.upload()  obj.close()  #我們可以使用一個連接,做多次操作,而不需要反覆的許建立連接和關閉
  • 應用場景 1、可擴展/復用:面向對象中可以建立公共的功能,然後我們可以新建方法,新的方法都可以來調用這個功能;有多個新的方法要調用這個功能的話就可以實現復用,然後新的方法自己也可以單獨創建自己的功能實現擴展。 2、模板:比如創建一個人的模板,有眼睛能看東西,有嘴巴能說話等等,然後根據這個模板創建不同的人,不同的人眼睛可能顏色不同,然後嘴巴說的語言不同等等,但不管你新創造的人是什麼樣,都會具備眼睛、嘴巴這些功能。 3、 解決重複傳參數
def f1(host,port,pwd,arg):      pass    def f2(host,port,pwd,arg1,arg2):      pass    def f3(host,port,pwd,arg1,arg2,arg3):      pass    f1(1,2,3,4)  f2(1,2,3,4,5)  f3(1,2,3,4,5,6)

上面的程式碼可以看到在調用函數傳參數的時候,也是傳重複的內容到不同的函數中。

class FOO:      def __init__(self,host,port,pwd):          self.host = host          self.port = port          self.pwd = pwd      def f1(self,arg1):          self.host          self.port          self.pwd          pass        def f2(self,arg1,arg2):          self.host          self.port          self.pwd          pass        def f3(self,arg1,arg2,arg3):          self.host          self.port          self.pwd          pass    obj = FOO(1,1,1)  obj.f1(2)  obj,f2(3,4)  obj,f3(5,6,7)

上面的程式碼中在調用程式碼時可以看到,傳參數時可以避免傳入重複的參數到類的方法中,在類的方法中直接通過self.xxx就可以調用了。

4、self

self就是一個形參 self將對象當做參數傳入

class FOO:  #創建類,這個類在記憶體中創建一塊空間      def __init__(self,name):          self.NAME = name    #當實例化後,相當於將對象傳給了self這個形參,然後self.NAME相當於obj1.NAME,這個self.NAME屬性在obj1的記憶體空間中體現        def bar(self):  #類中的方法和類關聯,在類的記憶體空間中體現          pass    obj1 = FOO('alex')  #實例化的對象,這個對象會在記憶體中單獨創建一塊obj1空間  obj1.bar()    obj2 = FOO('eric')  #獨立創建一個obj2的記憶體空間  obj2.bar()    #目前為止有3個記憶體空間FOO、obj1、obj2
class FOO:      def __init__(self,name,count):          self.NAME = name    #這裡可以叫普通欄位或普通屬性          self.COUNT = count          self.Country = '中國'   #這個屬性會被賦予每一個對象,中國有32個省,如要針對每一個省寫一個對象,那麼就相當於有32個self.Country屬性被保存到每一個對象中了        def bar(self):          print (self.NAME)          print (self.COUNT)          print (self.Country)    obj1 = FOO('河南',12345)  obj1.bar()    obj2 = FOO('山東',131313)  obj2.bar()
class FOO:        country = '中國'    #公有屬性在記憶體中屬於class,不屬於對象      #在class下面創建一個變數叫做靜態欄位或公有屬性,這個公有屬性只會在class下面保存1份(32個省也是1份),class下面的每一個方法都可以調用該公有屬性;      #使用場景:當每一個方法都會用到同一個屬性的時候,就可以使用這個公有屬性。        def __init__(self,name,count):          self.NAME = name          self.COUNT = count        def bar(self):          print (self.NAME)          print (self.COUNT)    obj1 = FOO('河南',12345)  obj1.bar()    obj2 = FOO('山東',131313)  obj2.bar()

5、封裝

類中封裝了:欄位(公有屬性)、方法 對象中封裝了:普通欄位的值(就是通過對象傳進來的參數值)

class F1:      def __init__(self,n):          self.N = n          print ('F1')    class F2:      def __init__(self,arg1):          self.a = arg1          print ('F2')    class F3:      def __init__(self,arg2):          self.b = arg2          print ('F3')    o1 = F1('alex')  o2 = F2(o1) #o2這個對象封裝了o1這個對象  o3 = F3(o2) #o3這個對象封裝了o2這個對象    #o3.b=o2  #o2.a=o1  #o3.b.a=o1  #03.b.a.N=o1.N  #o3.b.a.N=alex    print (o3.b.a.N)  #o3.b相當於o2  #o3.b.a相當於o1    執行結果:  F1  F2  F3  alex

6、 繼承

class F1:      def __init__(self,n):          print ('F1')        def a1(self):          print ('F1:a1')    class F2(F1):      def __init__(self):          print ('F2')        def a1(self):          print ('F2:a1')    class F3(F2):      def __init__(self):          print ('F3')        def a1(self):          print ('F3:a1')    obj = F3()  obj.a1()    執行結果:  F3  F3:a1  #局部優先,所以取值F3類中的a1優先,如果沒有才會繼承
class F1:      def __init__(self,n):          print ('F1')        def a1(self):          print ('F1:a1')        def a2(self):          print ('F1:a2')    class F2(F1):      def __init__(self):          print ('F2')        def a1(self):          self.a2()          print ('F2:a1')        def a2(self):          print ('F2:a2')    class F3(F2):      def __init__(self):          print ('F3')        def a1(self):          print ('F3:a1')        def a2(self):    #此時self是obj這個對象,對象實例化的是F3,那麼a2也就是class F3中的方法a2          print ('F3:a2')    obj = F3()  obj.a2()

7、多態 多種形態(這裡沒有補充)

8、 普通欄位:普通屬性,保存在對象中 靜態欄位:公有屬性,保存在類中 方法:保存在類中,被對象調用(通過對象來訪問方法)

靜態方法:    class F1:      @staticmethod      def a1():   #在類中的函數正常必須有self,但是用了@staticmethod後就可以讓該類和類裡面的函數當做一個正常的函數來使用          print ('alex')    F1()    #這裡無需創建對象,直接調用類即可  F1.a1()    #以上程式碼,其實效果和函數一模一樣,沒什麼特別的,只是有這麼一種寫法而已。