1. 程式人生 > >第三章:Python高階程式設計-深入類和物件

第三章:Python高階程式設計-深入類和物件

# 第三章:Python高階程式設計-深入類和物件 [Python3高階核心技術97講](https://coding.imooc.com/class/200.html) **筆記** ## 3.1 鴨子型別和多型 ```python """ 當看到一直鳥走起來像鴨子、游泳起來像鴨子、叫起來像鴨子,那麼這隻鳥就可以被稱為鴨子。 這句話看上去有趣,卻不太容易理解。接下來用例項來說明。 """ # ============ Demo1 start ============= class Cat(object): def say(self): print("I am a cat") class Dog(object): def say(self): print("I am a dog") class Duck(object): def say(self): print("I am a duck") animal = Cat animal().say() # ============ Demo1 end =============== # ============ Java pseudocode contrast start ============= """ 在 Java中實現多型,需要子類繼承父類並重寫父類方法。並需要宣告型別 """ class Animal: def say(self): print("I am an animal") class Dog(Animal): def say(self): print("I am an Doy") Ainmal animal = Cat() animal.say() # ============ Java pseudocode contrast end ============= """ 在Python中就不一樣了,如Demo1所示,變數animal可以指向任意型別, 所有類不需要繼承父類,只需定義相同的方法say()就可以實現多型。再呼叫的時候, 只需呼叫共同say()方法。如下示例。 """ # ============== Demo2 start =================== class Cat(object): def say(self): print("I am a cat") class Dog(object): def say(self): print("I am a dog") class Duck(object): def say(self): print("I am a duck") animal_list = [Cat, Dog, Duck] for animal in animal_list: animal().say() # ============== Demo2 end =================== """ 有內感覺了嗎,反正我看到這,感觸較深。嘎嘎嘎..... 老師又來了個例子。 """ # ============== Demo3 start ==================== a = ["bobby1", "bobby2"] name_tuple = ("bobby3", "bobby4") name_set = set() name_set.add("bobby5") name_set.add("bobby6") name_list = ["bobby7", "bobby8"] a.extend(name_tuple) a.extent(name_list) a.extent(name_set) # =============== Demo4 end ====================== """ 在 Demo3 中不知你是否發現除了列表本身,元組和集合物件都可以傳入列表物件的 extend()方法。其實是extend()是接收一個可迭代物件,也就是前面章節所提到的 迭代型別,那麼好玩的就來了。 """ # =============== Demo5 start ===================== class Dog(object): def say(self): print("I am a dog") def __getitem__(self): print("loop!!!!!!!!") a = ["bobby1", "bobby2"] dog = Dog() # name_tuple = ("bobby3", "bobby4") # name_set = set() # name_set.add("bobby5") # name_set.add("bobby6") # name_list = ["bobby7", "bobby8"] # a.extend(name_tuple) # a.extend(name_list) a.extend(dog) """ 結果: loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! loop!!!!!!!! .... """ # =============== Demo5 end ======================= """ 在 Demo5 中程式陷入了死迴圈,傳入一個Dog物件也沒有報錯, 為什麼?因為魔法函式,前面章節提到的__getitem__()是的物件 變成了可迭代物件,因此傳入extend中,方法一直執行,知道丟擲異常, 但是示例中是不會丟擲異常的,因此會陷入死迴圈。 """ ``` ## 3.2 抽象基類(abc模組) ```python """ abc -> abstract base class 抽象基類相當於Java中的介面,Java無法實現多繼承, 但可以繼承多個介面,介面是不可以例項化的。所以說, Python中的抽象基類也是不可以例項化的。Python是 動態語言,是沒有變數型別的。實際上,變數只是一個符 號而已,它是可以指向任意型別的物件。動態語言不需要 指定型別,所以就少了一個編譯時檢查錯誤的環境,只有運 行時才知道錯誤。 與Java最大的一個區別就是,在定義一個類的時候,是不 需要去繼承一個指定型別的。而要知道Python的一個類是 屬於哪個型別的,是去看實現了那些魔法函式,魔法函式賦予 了類的一些特性。在實現了某個魔法函式之後,使得物件變成了 一個指定的型別,這種方法,在Python中可以說是一種協議。 在寫程式碼是要儘量遵守這種協議,這樣寫出來的程式碼,才是 足夠Python的一種程式碼。 """ # ============ Demo1 start ============= class Company(object): def __init__(self, employee_list): self.employee = employee_list def __len__(self): return len(self.employee_list) com = Company(["bob", "jane"]) # 如何判斷物件的型別呢? # 第一種方案 print(hasattr(com, '__len__')) # 通過判斷是否有某個屬性而判斷屬於什麼型別,不夠直觀 # 通過抽象基類 from collections.abc import Sized print(isinstance(com, Sized)) # 這樣的方式更加直觀,易讀 # ============ Demo2 end ============= """ 抽象基類的兩個使用場景: 1. 我們在某些情況下希望判定某個物件的型別 2. 我們需要強制某個子類必須實現某些方法 """ # =============== Demo2 start ================= # 如何去模擬一個抽象基類 class CacheBase(): def get(self, key): raise NotImplementedError def set(self, key, value): raise NotImplementedError class RedisCache(CacheBase): pass redis_cahe = RedisCache() redis_cache.set("key", "value") # 會丟擲異常,因為子類沒有實現父類對應方法 # =============== Demo2 end ==================== """ Demo2 的方法雖實現了第二個場景的需求,但是不夠好, 只是在物件方法在呼叫是才丟擲異常,如果想要在物件在 初始化就丟擲異常,就需要使用我們的abc模組了。 """ # ================= Demo3 start =================== # 使用全域性的abc模組 import abc class CacheBase(metaclass=abc.ABCMeta): @abc.abstractmethod def get(self, key): pass @abc.abstractmethod def set(self, key, value): raise NotImplementedError class RedisCache(CacheBase): pass redis_cache = RedisCache() # 丟擲異常 # ================= Demo3 end ====================== ``` ## 3.3 使用instance而不是type ```python class A: pass class B(A): pass b = B() print(isinstance(b, B)) # True print(isinstance(b, A)) # True print(type(b) is B) # is 判斷是否是同一個物件 print(type(b) == B) # == 判斷的是值是否相等 print(type(b) is A) # False """ 注意isinstance比使用type好,type無法找到父類, 而isinstance可以。 同時注意 == 與 is 的區別。 """ ``` ## 3.4 類變數和物件變數 ```python # =========== Demo1 start ============== class A: aa = 1 # 類變數 def __init__(self, x, y): self.x = x self.y = y a = A(2, 3) print(a.x, a.y, a.aa) # 2 3 1 print(A.aa) # 1 print(A.x) # 丟擲異常 # =========== Demo1 end ================ """ 在 Demo1 中列印a.aa時,首先會在物件屬性中查詢, 若是找不到則在類屬性中查詢。以上 Demo 很好理解。 """ # ============ Demo2 start ================= class A: aa = 1 # 類變數 def __init__(self, x, y): self.x = x self.y = y a = A(2, 3) A.aa = 11 print(a.x, a.y, a.aa) # 2 3 11 A.aa = 111 a.aa = 100 print(a.x, a.y, a.aa) # 2 3 100 # ============= Demo2 end ================== """ 在對A.aa與a.aa同時賦值時,此時,物件屬性中 就有了aa屬性,所以在列印a.aa時,就會首先打 印物件裡的屬性啦。注意這個細節哦。類與例項的 變數是兩個獨立的存在。 """ # 在Demo3中加入以下程式碼 b = A(3, 4) print(b.aa) # 3 4 111 """ 可見類變數是所有例項共享的。 """ ``` ## 3.5 類屬性和例項屬性以及查詢順序 ```python """ 屬性就是在類或例項中定義的變數或方法。 """ class A: name = "A" def __init__(self): self.name = "obj" a = A() print(a.name) # obj """ 在單繼承這很簡單,但是在多繼承下,這些就會變得複雜起來。 """ ``` ### MRO演算法 Method Relation order Python3使用的演算法是C3,以下演算法是Python早些版本的屬性查詢演算法,均存在一些缺陷,下面一一介紹。 **深度優先搜尋** ![MRO演算法-1](https://images.cnblogs.com/cnblogs_com/xunjishu/1758619/o_200507050152MRO%E7%AE%97%E6%B3%95-1.png) 上圖的繼承關係中使用深度優先搜尋是沒有問題的,但是要是繼承關係是菱形,如下圖所示就會出現問題。需要使用廣度優先搜尋演算法,使得繼承順序為A->B->C->D。 問題就是,下圖中,如果C裡的方法重寫了D的方法。但是由於深度優先搜尋演算法會首先查詢D中的屬性,那麼C的重寫方法就不會生效。所有需要使用廣度優先搜尋演算法解決問題。 ![MRO演算法-2](https://images.cnblogs.com/cnblogs_com/xunjishu/1758619/o_200507050250MRO%E7%AE%97%E6%B3%95-2.png) **廣度優先** 廣度優先雖然解決了上述問題,但是呢,若果出現如下繼承關係,廣度優先演算法又出現問題了。就是,如果D,C都有一個同名的方法,而繼承D的B沒有實現這個同名方法。那麼在搜尋完B時,應該搜尋D,但是廣度優先演算法回去搜尋C,這邏輯上是不合理的。 ![MRO演算法-3](https://images.cnblogs.com/cnblogs_com/xunjishu/1758619/o_200507050254MRO%E7%AE%97%E6%B3%95-3.png) 所以Python3統一成了一種方法,C3使得這些問題都不復存在。 ```python # =============== 菱形繼承問題 ================== #新式類 class D: pass class B(D): pass class C(D): pass class A(B, C): pass print(A.__mro__) """ 結果: (, , , , ) """ # ================ 非菱形繼承問題 ================= class D: pass class B(D): pass class E: pass class C(E): pass class A(B, C): pass print(A.__mro__) """ 結果: (, , , , , ) """ ``` ## 3.6 靜態方法、類方法以及物件方法以及引數 ```python class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day def tomorrow(self): self.day += 1 @staticmethod def parse_from_string(data_str): year, month, day = tuple(date_str.split("-")) return Date(int(year), int(month), int(day)) # 出現硬編碼情況 @classmethod def from_string(cls, date_str): year, month, day = tuple(date_str.split("-")) return cls(int(year), int(month), int(day)) # 解決硬編碼 def __str__(self): return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) if __name__ == "__main__": new_day = Date(2020, 5, 7) new_day.tomorrow() print(new_day) date_str = "2020-5-7" new_day = Date.parse_from_string(date_str) print(new_day) ``` ## 3.7 資料封裝和私有屬性 ```python class Date: #建構函式 def __init__(self, year, month, day): self.year = year self.month = month self.day = day def tomorrow(self): self.day += 1 @staticmethod def parse_from_string(date_str): year, month, day = tuple(date_str.split("-")) return Date(int(year), int(month), int(day)) @staticmethod def valid_str(date_str): year, month, day = tuple(date_str.split("-")) if int(year)>0 and (int(month) >0 and int(month)<=12) and (int(day) >0 and int(day)<=31): return True else: return False @classmethod def from_string(cls, date_str): year, month, day = tuple(date_str.split("-")) return cls(int(year), int(month), int(day)) def __str__(self): return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day) class User: def __init__(self, birthday): self.__birthday = birthday # _User__birthday def get_age(self): return 2018 - self.__birthday.year if __name__ == "__main__": user = User(Date(1990, 2, 1)) print(user.birthday)) print(user.get_age()) ``` ## 3.8 Python物件自省機制 ```python """ 自省就是通過一定的機制查詢到物件的內部結構。 """ class Person: name = "User" class Student(Person): """ 文件 """ def __init__(self, school_name): self.school_name = school_name if __name__ == "__main__": stu = Student("家裡蹲") print(stu.__dict__) # {'school_name': '家裡蹲'} print(stu.name) # User """ stu的屬性字典裡沒有name,那麼是怎麼能夠得到User的呢? 實際上這個name在Person的屬性字典裡,類也是物件嘛!! stu沒有直譯器就往上層找 """ print(Person.__dict__) # {'__module__': '__main__', 'name': 'User', ...} print(Student.__dict__) # {... '__doc__': '\n 文件\n ', ... } print(dir(stu)) # ['__class__', '__delattr__', '__dict__', '__dir__', ...] stu.__dict__["city"] = "WC" print(stu.city) # WC ``` ## 3.9 super函式 ```python """ super函式並沒有那麼簡單... """ class A: def __init__(self): print("A") class B(A): def __init__(self): print("B") # super(B, self).__init__() # python2 super().__init__() class C(A): def __init__(self): print("C") super().__init__() class D(B, C): def __init__(self): print("D") super(D, self).__init__() # 既然我們重寫了B的建構函式,為什麼還要去呼叫super? """ 為了能夠重用父類的一些方法,避免編寫重複的邏輯 """ # super到底執行順序什麼樣? """ super並不是僅僅呼叫父類方法.... """ if __name__ == "__main__": d = D() """ 直觀結果: D B A """ """ 實際結果: D B C A """ # 所以super的查詢順序是根據mro順序來的 print(D.__mro__) ``` ## 3.10 Django rest framework 中對多繼承使用的經驗 Mixin模式 1. Mixin功能單一 2. 不和基類關聯,可以和任意基類組合,基類可以不和mixin關聯就能初始化 3. 在mixin中不要使用super這種用法 ## 3.11 Python中的with語句 ```python """ try expect finally 的用法 """ # ============== Demo1 start ==================== try: print("code started") raise KeyError except KeyError as e: print("key error") else: # 沒有異常再執行 print("other code") finally: print("finally") # 不管怎麼樣該行程式碼都會執行,用於關閉檔案物件等 # ============== Demo1 end ===================== # ================== Demo2 start ======================== def exe_try(): try: print("code start") raise KeyError return 1 except KeyError as e: print("Key error") return 2 else: print("other error") return 3 finally: print("finally") return 4 if __name__ == "__main__": result = exe_try() print(result) """ result 的結果會是什麼呢? 答案是: 4 那麼註釋 return 4 結果又是什麼呢? 答案是: 2 因為每次執行到return語句時, 其值都會壓入棧中,最終去棧頂的值。 """ # ================== Demo2 end ========================== ``` **上下文管理協議** ```python """ 基於: __enter__(self) __exit__(self, exc_type, exc_val, exc_tb) """ class Sample: def __enter__(self): # 獲取資源 print("enter") return self def __exit__(self, exc_type, exc_val, exc_tb): # 釋放資源 print("exit") def do_something(self): print("doing something") with Sample() as sample: sample.do_something() """ 執行結果: enter doing something exit """ ``` ## 3.12 contextlib實現上下文管理器 ```python import contextlib @contextlib.contextmanager def file_open(file_name): print("file open") yield {} print("file end") with file_open("bobby.txt") as f_opened: print("file processing") """ 執行結果: file open file processing file end