python面向對象基礎-01
- 2019 年 10 月 7 日
- 筆記
面向對象(OOP)基本概念
前言
話說三國時期曹軍於官渡大敗袁紹,酒席之間,曹操詩興大發,吟道:喝酒唱歌,人生真爽! 眾將直呼:"丞相好詩",於是命印刷工匠刻板印刷以流傳天下; 待工匠刻板完成,交與曹操一看,曹操感覺不妥,說道:"喝酒唱歌,此話太俗,應改為'對酒當歌'較好",於是名工匠重新刻板,當時還沒有出現活字印刷術,如果樣板要改,只能重新刻板,工匠眼看連夜刻版之工,徹底白費,心中叫苦不迭。可也只得照辦。 版樣再次出來請曹操過目,曹操細細一品,覺得還是不好,說」人生真爽太過直接,應該改問語才夠意境,因此應改為『對酒當歌,人生幾何?』「,於是.... 在活字印刷術還沒出現之前,如果版樣有改動,只能重新雕刻。而且在印刷完成後,這個樣板就失去了它的價值,如果需要其他樣板只能重新雕刻。而活字印刷術的出現就大大改善了印刷技術。如上例」喝酒唱歌,人生真爽「,如果用活字印刷,只需要改四個字就可,其餘工作都未白做。豈不快哉!! 活字印刷也反應了OOP。當要改動時,只需要修改部分,此為 可維護;當這些字用完後,並非就完全沒有價值了,它完全可以在後來的印刷中重複使用,此乃 可復用;此詩若要加字,只需另刻字加入即可,這就是 可擴展;字的排列可以橫排,也可以豎排,此是 靈活性好。
# 上述案列反應了面向對象的優點,即可維護性高,擴展性強,復用性高! # 這些特點非常適用於用戶需求變化頻繁的互聯網應用程式,這是學習面向對象的重要原因 # 但是面向對象設計的程式需涉及類與對象,相應的複雜度會提高! # # 並非所有程式都需要較高的擴展性,例如系統內核,一旦編寫完成,基本不會再修改,使用面向過程來設計則更適用
面向對象
面向對象: # 是一種編程思想,是前輩們總結的編程經驗,指導程式設計師如何編寫出更好的程式
核心: # 對象
程式就是一系列對象的集合,程式設計師負責調度控制這些對象來交互著完成任務 在面向對象中程式設計師的角度發生了改變,從具體的操作者變成了指揮者。
強調:對象不是憑空產生的,需要我們自己設計
面向對象的優缺點
''' 優點: 1.可擴展性高 2.靈活性高 3.重用性高 缺點: 1.程式的複雜度提高了 2.無法準確預知結果 '''
使用場景: # 對擴展性要求較高的程式,通常是直接面向用戶的,例如qq 微信
注意點: # 不是所有的程式都要採用面向對象,要根據實際需求來選擇
面向對象的兩大核心: # 類與對象
類
''' 類即類型、類別,是一種 抽象概念 是一系列具備相同特徵和相同行為的對象的集合 '''
對象
''' 對象就是具體存在的某個事物,具備自己的特徵和行為 對象就是特徵和技能的結合體 '''
類和對象的關係
''' 類包含一系列對象 對象屬於某個類 在現實中先有對象再有類,程式中先有類再有對象 我們必須先告訴電腦這類的對象有什麼特徵有什麼行為 '''
結論: # 在面向對象編程時,第一步需要考慮需要什麼樣的對象,對象具備什麼樣的特徵和行為,從而根據這些資訊總結出需要的類
用面向對象思想編程
類的定義語法及類名書寫規範
class 類的名稱: # 類中的內容,描述屬性和技能 # 描述屬性用變數 # 描述行為用函數 ''' 類名書寫規範: 1.見名知意 2.名稱是大駝峰式(駝峰就是單詞首字母大寫,大駝峰就是第一個字母大寫,小駝峰是第一個字母小寫) 3.其他規範與變數大致相同 '''
屬性
''' 屬性可以寫在類中 類中的屬性,是對象公共的 也可以寫在對象中 對象中的屬性,每個對象獨特的(不一樣的) 如果類和對象中存在同樣的屬性,先訪問對象,如果沒有再訪問類 '''
屬性的增刪改查
''' 增加屬性 對象變數名稱.屬性名稱 = 屬性值 egon.male = 'male' 刪除屬性 del 對象的變數名稱.屬性名稱 del egon.male 修改屬性 對象.屬性 = 新的值 查看所有屬性,訪問的是對象的所有屬性 對象.__dict__ --> dict 可以訪問調用者自身的名稱空間 訪問他的類 對象.__class__ 返回類 '''
class Student: ''' 這是Student類的注釋 ''' def __init__(self, name): self.name = name class TeachOfOldBoy: company = 'oldboy' def __init__(self, name): self.name = name # ---------- 對象新增、修改、查看、刪除屬性 ------------- xuzhaolong = Student('xzl') print(xuzhaolong.name) # 對象.屬性 --> 訪問對象的屬性 # xzl # print(xuzhaolong.age) # 訪問不存在的屬性會報錯'Student' object has no attribute 'age' xuzhaolong.age = 18 # 對象.屬性 = 值 --> 為對象添加新屬性 print(xuzhaolong.age) # 18 xuzhaolong.age = 28 # 對象.屬性 = 新值 --> 修改屬性的值(如果該對象已有此屬性) print(xuzhaolong.age) # 28 del xuzhaolong.age # 刪除對象的屬性 # print(xuzhaolong.age) # 會報錯,屬性已被刪除,類中(屬性的查找順序:對象-->父類-->...如果還有父類,其他父類...-->Object)沒有這個屬性,AttributeError: 'Student' object has no attribute 'age' print(TeachOfOldBoy.__dict__) # __dict__查看調用者的名稱空間里的名字 # {'__module__': '__main__', 'company': 'oldboy', '__init__': <function TeachOfOldBoy.__init__ at 0x0000026D1AC8A9D8>, '__dict__': <attribute '__dict__' of 'TeachOfOldBoy' objects>, '__weakref__': <attribute '__weakref__' of 'TeachOfOldBoy' objects>, '__doc__': None} print(xuzhaolong.__dict__) # {'name': 'xzl', 'age': 28} # ---------- __class__ 查看調用者所屬類型(python里類也是一種數據類型) ------------- print(TeachOfOldBoy.__class__) # __class__ 查看調用者所屬類 # <class 'type'> print(xuzhaolong.__class__) # <class '__main__.Student'> # ---------- __doc__ 查看調用者的注釋 ------------- print(TeachOfOldBoy.__doc__) # __doc__ 查看調用者的注釋 # None print(Student.__doc__) # __doc__ 查看調用者所屬類的注釋 # # 這是Student類的注釋 # print(xuzhaolong.__doc__) # __doc__ 查看調用者所屬類的注釋 # # 這是Student類的注釋 # # ------------ 類中的屬性與對象 ----------------- egon = TeachOfOldBoy('egon') print(egon.company) # oldboy egon.company = 'OldBoy' print(egon.company) # 修改對象的繼承的屬性不影響類中的屬性 # OldBoy print(TeachOfOldBoy.company) # oldboy
屬性的查找順序: # 屬性的查找順序:先找自己的,再找類的,再父類。。。。一直往上找到object,再沒有就報錯
對象初始化__inint__
''' __init__方法 特點 1.當實例化對象時,會自動執行__init__方法 2.會自動將對象作為第一個參數傳入,對象參數名稱為self,self這個名字可以改,但不建議(一眼就知道是對象本身了) 功能 用戶給對象賦初始值 ''' # 初始化,不僅僅只是賦初值,還可以做一些其他操作,來保證對象的正常生成
''' 之前十個老師,每個老師都要輸姓名年齡等,而屬性的值又不一樣,就不能直接定義成類的屬性 每定義一個對象都要寫一次,很麻煩 那麼我們可以使用函數來簡化這個賦值操作 class Cls: # 定義一個類 pass def init(obj, kind, color, age): # 定義一個方法來簡化給對象添加(訂製)屬性 obj.kind = kind # 即 對象.屬性 = 值(變數的) --> 像什麼? 給對象添加屬性嘛 obj.color = color obj.age = age obj1 = Cls() # 實例化出一個對象 init(obj1, "泰日天", '棕色', 2) # 通過調用 init 函數來給該對象添加屬性 obj2 = Cls() init(obj2, "拆家犬", '黑白', 1) # 這樣子就有了兩個對象,且都已經有了各自的屬性了 ''' class Dog: def __init__(self, kind, color, age): # print(locals()) # {'age': 2, 'color': '棕色', 'kind': '泰日天', 'self': <__main__.Dog object at 0x000002985BE586D8>} self.kind = kind self.color = color self.age = age # __init__ 函數不允許寫返回值(不能寫,只能返回None,寫return None 沒有意思) 規定如此 ''' 不過在類裡面直接寫這個 init 函數會更方便(python內部做了一些處理) 如上,當實例化對象時,會自動執行這個 __init__ 方法 會自動將調用這個類實例化的對象作為第一個參數傳入,對象參數名稱為self ''' # 那麼在實例化的時候就可以簡寫成這樣了,四行變兩行 teidi = Dog("泰日天", '棕色', 2) # print(teidi.__dict__) # {'kind': '泰日天', 'color': '棕色', 'age': 2} erha = Dog("拆家犬", '黑白', 1) ''' 其實上面的寫法還可以有優化,那個__init__ 函數還可以寫得更簡潔一點(參數越多越明顯) def __init__(self, kind, color, age): lcs = locals() lcs.pop('self') self.__dict__.update(lcs) 上面寫法中的locals()內置函數在 __init__ 函數中,可以獲取 __init__ 函數名稱空間里的那些名字,它是一個字典 # print(locals()) # {'age': 2, 'color': '棕色', 'kind': '泰日天', 'self': <__main__.Dog object at 0x000002985BE586D8>} 我們將其賦給一個中間字典接收,將多餘的 self 鍵值對去掉,就是我們對象想要的屬性 而對象.__dict__正好也是一個字典 # print(teidi.__dict__) # {'kind': '泰日天', 'color': '棕色', 'age': 2} 都是字典?字典的update()方法還記得嗎? 將這個中間字典update()到對象的.__dict__ 中,即完成了對象的屬性添加 self.__dict__.update(lcs) '''

備註:關於python內一些 __名字__ 這種屬性是什麼情況,可以參考這篇部落格哦(都說不重要,但總有我這樣的好奇寶寶嘛)~ Python常用內建方法:__init__,__new__,__class__的使用詳解
綁定方法與非綁定方法
對象的精髓所在就是將數據和處理數據的函數整合到一起了,這樣一來,拿到一個對象就同時拿到了需要處理的數據以及處理數據的函數
''' ******* 這一塊基礎概念是重點 ******* 對象綁定方法:self --> 默認傳入對象 默認情況下,類中的方法都是對象綁定方法 當使用對象調用時 其特殊之處在於調用該函數時會自動傳入對象本身作為第一個參數 當使用類名來調用時就是一個普通函數,有幾個參數就傳幾個參數 類綁定方法:@classmethod cls(加上@classmethod裝飾後自己將self改為cls(class是關鍵字)) --> 默認傳入類 給類的抽象方法,默認傳入類作為參數 特殊之處:不管用類還是對象調用,都會自動傳入類本身,作為第一個參數 何時綁定給對象:當函數邏輯需要訪問對象中的數據時 何時綁定給類:當函數邏輯需要訪問類中的數據時 非綁定方法:@staticmethod 裝飾成普通函數,不管誰調都一樣 既不需要訪問類的數據也不需要訪問對象的數據 定義時有幾個參數調用時就需要傳入幾個參數 ''' class Dog: species = 'dog' # 初始化方法 __init__ 也屬於對象綁定方法 def __init__(self, kind, nickname, skill): self.kind = kind self.nickname = nickname self.skill = skill # 這就屬於一個對象綁定方法,默認將對象作為第一個參數傳入(調用的時候不需要手動傳入) def introduce(self): print(f"我是{self.kind},我能{self.skill},人送外號{self.nickname}") # 類綁定方法 @classmethod def tell_species(cls): # 先寫裝飾器再寫函數,默認參數就是cls了,寫完函數再裝飾的話,記得把self換成cls,表示參數是一個類 print(f"我的物種是{cls.species}") # 非綁定方法(既不需要傳入類,也不需要傳入對象) @staticmethod def normal_func(arg1, arg2): print(f"參數一:{arg1} 參數二:{arg2},這只是一個普通方法啦~") taidi = Dog('泰迪', '泰日天', '日天') erha = Dog('二哈', '拆家犬', '拆家') # --------綁定對象方法的調用 taidi.introduce() erha.introduce() # 我是泰迪,我能日天,人送外號泰日天 # 我是二哈,我能拆家,人送外號拆家犬 Dog.introduce(erha) # 類調用對象的方法要手動把對象傳入進去 # 我是二哈,我能拆家,人送外號拆家犬 # --------類綁定方法調用 Dog.tell_species() taidi.tell_species() # 可以直接調用,無需手動傳入 class # 我的物種是dog # 我的物種是dog # --------非綁定方法調用 Dog.normal_func(1, 2) # 非綁定方法,定義時有幾個參數,調用時就要傳幾個參數 taidi.normal_func(1, 2) # 非綁定方法,定義時有幾個參數,調用時就要傳幾個參數 # 參數一:1 參數二:2,這只是一個普通方法啦~ # 參數一:1 參數二:2,這只是一個普通方法啦~
利用pickle模組將對象序列化到本地
如單機遊戲的存檔(把對象的數據等資訊存儲到文件中,下次啟動再讀取回來) –> 遊戲擴展
import pickle class Student: def __init__(self, name): self.name = name def say_hi(self): print("name:", self.name) def save(self): with open(self.name, "wb") as f: # pickle 模組操作文件必須是b 模式 pickle.dump(self, f) # 利用pickle 模組,將傳入的對象序列化到了文件中(json模組不支援對象這種類型) @staticmethod def get(name): with open(name, "rb") as f: obj = pickle.load(f) # 利用pickle 模組,將文件中的對象反序列化成python中的對象 return obj # 將rose 和 jack兩個從記憶體中對象序列化到文件中 stu = Student("rose") stu.save() stu2 = Student("jack") stu2.save() # 將他兩反序列化出來到記憶體中 obj = Student.get("rose") # 調用類中的方法(類綁定方法)從文件中將對象載入到記憶體中 print(obj.name) # 使用該對象獲取屬性值 # rose obj2 = Student.get("jack") print(obj2.name) # jack print(Student.__name__) # 獲取類名 # Student # 反序列化出來的對象和序列化的那個對象已經不是同一個對象了 print(id(stu), id(obj)) # 2161209937816 2161209971880 print(stu.name, obj.name) # rose rose print(id(stu2), id(obj2)) # 2161209937872 2161209971824 print(stu2.name, obj2.name) # jack jack obj2.name = 'jack2' print(stu2.name, obj2.name) # jack jack2
英雄大亂斗(隨機)案例
import random import time class Hero: def __init__(self, name, health, attack, q_hurt, w_hurt, e_hurt): lcs = locals() lcs.pop('self') self.__dict__.update(lcs) def attack(self, enemy): print(f"-- {self.name} --使用普通攻擊攻擊了-- {enemy.name} --,造成了 {self.attack} 點傷害,{enemy.name} 剩餘 {enemy.health} 點生命值。 33[0m") enemy.health -= self.attack def Q(self, enemy): print(f"