python面向對象的多態-類相關內置函數-類內置魔法函數-迭代器協議-上下文管理-04

  • 2019 年 10 月 7 日
  • 筆記

多態

一種事物具備不同的形態

例如:水 –> 固態、液態、氣態

多態:# 多個不同對象可以相應同一個對象,產生不同的結果

首先強調,多態不是一種特殊的語法,而是一種狀態,特性(多個不同對象可以相應同一個方法,長身不同的結果)

好處:對於使用者而言,使用成本降低

​ 之前的USB介面下的滑鼠,鍵盤,就屬於多態

介面抽象類 鴨子類型都可以寫出具備多態的程式碼(最簡單的就是鴨子類型)

'''  要管理 雞 鴨 鵝      如何能夠最方便的管理,就是我說同一句話,他們都能理解      他們擁有相同的方法  '''      class Chicken:      @staticmethod      def bark():          print("咯咯咯咯")        @staticmethod      def spawn():          print("下雞蛋...")      class Duck:      @staticmethod      def bark():          print("嘎嘎嘎")        @staticmethod      def spawn():          print("下鴨蛋...")      class E:      @staticmethod      def bark():          print("鵝鵝鵝鵝")        @staticmethod      def spawn():          print("下鵝蛋...")      j = Chicken()  y = Duck()  e = E()      def mange(obj):      obj.spawn()      mange(j)  # 下雞蛋...  mange(y)  # 下鴨蛋...  mange(e)  # 下鵝蛋...

python中常見的多態(不同的對象類型,擁有相同的方法,不同的結果)

# 不管什麼類型,他都與type這個方法  ---> python中多態的體現  # 多態在python中其實很常見,因為到處充斥著繼承與組合  a = 10  b = '10'  c = [10]    print(type(a))  print(type(b))  print(type(c))  # <class 'int'>  # <class 'str'>  # <class 'list'>

常見的內置函數

  • isinstance
 # isinstance()  # 判斷一個對象是不是某個類的實例   # 參數1 要判斷的對象,參數2 要判斷的類型  def add_num(a, b):      # if type(a) == type(b):      if isinstance(a, int) == isinstance(b, int):          return a+b      else:          print("數據類型不符")      add_num("100", 10)
  • issubclass
# issubclass() # 判斷一個類是不是另一個類的子類  #   參數一:子類,參數二:父類  class Animal:      @staticmethod      def eat():          print("動物得吃東西...")      class Pig(Animal):      @staticmethod      def eat():          print("豬吃東西...")      class Tree:      @staticmethod      def light():          print("植物光合作用...")      def mange(obj):      # if isinstance(obj, Animal):      if issubclass(type(obj), Animal):          obj.eat()      else:          print("不是動物...")      pig = Pig()  t = Tree  mange(pig)  # 豬吃東西...  mange(Tree)  # AttributeError: type object 'Tree' has no attribute 'eat'  # 不是動物...

面向對象的內置魔法函數

  • __str__
'''      __str__ 會在對象被轉為字元串時,轉換的結果就是這個函數的返回值      使用場景:我們可以利用該函數來自定義,對象是列印格式  '''  class Person:      def __str__(self):  # 重寫object中的 __str__          print("__str__ run")          return 'abc'  # abc下面的報錯那裡就變成了 abc      p = Person()  # 所有的類都可以轉成字元串  print(p)  # 列印了 __str__ run,又報錯了  # __str__ run  # abc  # 寫return 之前TypeError: __str__ returned non-string (type NoneType)  --> __str__ 必須要有一個str類型的返回值    str(p)  # 沒有寫print 在控制台也輸出了 __str__ run  # __str__ run

將對象以指定格式輸出

# print列印對象時記憶體地址,沒什麼意義,此時就可以利用__str__來自定義對象列印  class Person:      def __init__(self, name, age):          self.name = name          self.age = age        def __str__(self):  # 重寫object中的 __str__          return f"這是要給Person對象,name:{self.name},age:{self.age}"      p = Person('jack', 10)  # 所有的類都可以轉成字元串  print(p)  # 列印了  # 這是要給Person對象,name:jack,age:10
  • __del__
# del 析構函數   (__init__ 構造函數)  # 執行時機:手動刪除對象時立馬執行,或是程式運行結束時也會自動執行(垃圾回收機制?)  # 使用場景:當你的對象再使用過程中打開了不屬於解釋器的資源,例如文件,網路埠  import time      class Person:      def __init__(self, name, age):          self.name = name          self.age = age        def __del__(self):  # 重寫object中的 __str__          print("del run...")          return "del run"      p = Person("jack", 20)  # del p  # 刪除對象觸發 __del__函數執行  # # del run...  time.sleep(2)  print("over")  # over  # del run...  # 程式結束後會把名稱空間清除掉,清除時觸發了 __del__,列印出 del run...

結束使用自動關閉文件資源案例

class FileTool:      # 該類用於簡化文件的讀寫操作        def __init__(self, path):          self.file = open(path, 'rt', encoding='utf-8')        def read(self):          return self.file.read()  # rt模式不推薦直接讀位元組(漢字、英文位元組不同),可以一行一行讀        # 執行這個函數可以確定一個函數,這個對象肯定不用了,所以就可以放心的關心文件了      def __del__(self):          self.file.close()      tool = FileTool("a.txt")  print(tool.read())  # 文件屬於作業系統,不受垃圾回收機制管理  # aaaaaaaaaaaa    # 不知道什麼不使用該對象,那就寫在 __del__函數中,當其被刪除時,指定關閉資源
  • __call__
# call  調用對象時自動執行  # 執行時機:在調用對象時自動執行 ---> 對象()      class A:      # 調用對象時自動執行      def __call__(self, *args, **kwargs):          print("__call__ run...")          print(args)          print(kwargs)      a = A()    a(1, 2, a=100, c=300)  # 對象加括弧調用  # __call__ run...  # (1, 2)  # {'a': 100, 'c': 300}
  • __slots__

python是動態語言,可以在運行期間動態修改對象的屬性,如何能存儲更多屬性呢? 需要開啟更大的記憶體區域,將原始的屬性賦值過去 問題:如果開啟的容量太大(為了效率犧牲了空間),將造成記憶體的浪費 解決方案:在創建對象是告訴系統這個對象只有哪些屬性,也就是固定了對象的屬性數量,這樣就可任意要多少開多少,減少空間浪費(使用__slots__

import sys      class Person:      __slots__ = ['name']  # 加了以後再添加屬性就不行了,限制屬性        # def __init__(self, name, age):      def __init__(self, name):          self.name = name          # self.age = age  # 未在__slots__中聲明,直接報錯 AttributeError: 'Person' object has no attribute 'age'      # p = Person("jck", 18)  p = Person("jck")    print(sys.getsizeof(p))  # 獲取對象佔用記憶體大小  # 56 ---> 48  ---> __slots__ 指定有哪些屬性,從而節省了記憶體空間(沒指定__slots__之前56,指定之後48)    # print(p.__dict__)  # 報錯,可變字典也被省掉了(名稱空間連開都不開了),AttributeError: 'Person' object has no attribute '__dict__'

該屬性是一個類屬性,用於優化對象記憶體

優化的原理:將原本不固定的屬性數量,變得固定了,這樣的解釋器就不會以這個對象創建名稱空間(所以__dict__也沒了),從而達到減少記憶體開銷的效果

另外當類中出現了__slots__時將導致這個類的對象不再添加__slots__定義之外的屬性

  • __getattr__ __setattr__ __delattr__ 及點語法原理
__getattr__ 用 .訪問屬性時,如果屬性不存在,執行  __setattr__ 用 .設置屬性時執行  __delattr__ 用del 對象.屬性 刪除屬性時,執行    這幾個函數反映了 python解釋器是如何實現 . 語法的原理    __getattribute__ 該函數也是用來獲取屬性  在獲取屬性時如果存在__getattribute__則先執行該函數,如果沒有拿到屬性則繼續調用__getattr__函數,如果拿到了則直接返回
class A:      def __getattr__(self, item):          print("__getattr__")          return self.__dict__.get(item)        def __setattr__(self, key, value):          super().__setattr__(key, value)  # 這個不寫將導致賦值不成功,得到None          print('__setattr__')        def __delattr__(self, item):          print('__delattr__')          print(item)          self.__dict__.pop(item)      a = A()  a.name = 'jack'  # __setattr__  print(a.name)  # 這個屬性存在,就沒有調用 __getattr__  # jack      b = A()  b.__dict__["name"] = 'jackson'  # 通過操作__dict__ 也可以操作屬性(. 語法的背後就是操作 __dict__)  print(b.name)  # 這個屬性存在,就沒有調用 __getattr__  # jackson    del b.name  # 觸發 __delattr__  # __delattr__  # name    print(b.name)  # b沒有name這個屬性了,就觸發了 __getattr__  # __getattr__  # None  # b沒有name這個屬性了
class B:      def __setattr__(self, key, value):  # 利用了 .語法賦值改值就會觸發這個函數          self.__dict__[key] = value          print(f"{key}:{value}")      b = B()  b.name = 'jerry'  # name:jerry  b.name = 'tom'  # name:tom  print(b.name)  # tom    b.__dict__['halo'] = 'hi'  # 直接通過操作 __dict__ 也可以完成屬性的增改  print(b.halo)  # hi
  • []的實現原理(__getitem__ __setitem__ __delitem__

任何的符號,都會被解釋器解釋稱特殊含義,例如 . [] ()

__getitem__ 當你用中括弧去獲取屬性時 執行  __setitem__ 當你用中括弧去設置屬性時 執行  __detitem__ 當你用中括弧去刪除屬性時 執行
'''  需求:      讓一個對象支援 點語法來取值,也支援括弧取值  '''      class MyDict(dict):      def __getattr__(self, key):          return self.get(key)          # return self[key]  # KeyError: 'name'        def __setattr__(self, key, value):          self[key] = value        def __delattr__(self, item):          del self[item]      # 繼承 dict 可以直接用字典的一些方式  a = MyDict()  a['name'] = 'jack'  print(a['name'])  # jack    # 使用 .語法(通過實現__getattr__ 、__setattr__、__delattr__來實現)  a.name = 'sum'  print(a.name, a['name'])  # sum sum  print(a['name'])  # sum  a.name = 'jackson'  print(a.name)  # jackson  del a.name  print(a.name)  # None  # 用的是 .get 所以不會報錯
  • > >= == != < <= 等比較運算符的的實現原理(運算符重載)(__gt__ __ge__ __eq__ __ne__ __lt__ __le__)

當我們在使用某個符號時,python解釋器都會為這個符號定義一個含義,同時調用對應的處理函數,當我們需要自定義對象的比較規則時,就可以在子類中覆蓋大於等於等的方法

案例

# 自定義對象的比較  # 對象直接無法直接比較大小      class Person:      def __init__(self, name, height, age):          self.name = name          self.height = height          self.age = age      p1 = Person('jason', 185, 18)  p2 = Person('tank', 179, 18)  # print(p1 > p2)  # TypeError: '>' not supported between instances of 'Person' and 'Person'      class Student:      def __init__(self, name, height, age):          self.name = name          self.height = height          self.age = age        # 自定義比較規則      def __gt__(self, other):          print(self)          print(other)          print("__gt__")            # 比身高          # if self.height > other.height:          #     return True          return self.height > other.height          # 沒有返回值默認返回 None 即 False        def __eq__(self, other):          print("eq------")          return self.name == other.name      stu1 = Student("jack", 180, 28)  stu2 = Student("rose", 165, 27)    print(stu1 > stu2)  # 直接報錯,TypeError: '>' not supported between instances of 'Student' and 'Student'  # <__main__.Student object at 0x000001992C7C8F60>  # <__main__.Student object at 0x000001992C7C8F98>  # __gt__  # True  print(stu1 < stu2)  # 大於和小於只要實現一個即可,符號如果不同解釋器會自動交換兩個對象的位置  # <__main__.Student object at 0x000001992C7C8F98>  # <__main__.Student object at 0x000001992C7C8F60>  # __gt__  # False  print(stu1)  # <__main__.Student object at 0x000001992C7C8F60>  print(stu2)  # <__main__.Student object at 0x000001992C7C8F98>

原本自定義對象無法直接使用大於小於來進行比較,我們可以自定義運算符來實現,讓自定義對象也支援比較符

上述程式碼中.other指的是另一個參與比較的對象

大於和小於只要實現一個即可,符號如果不同解釋器會自動交換兩個對象的位置

迭代器協議

迭代器:是指具有__iter____next__的對象

我們可以為對象增加這兩個方法來讓對象變成迭代器

class MyIter:      # num 傳入,用來指定迭代次數      def __init__(self, num):          self.num = num          self.c = 0        def __iter__(self):          return self        def __next__(self):          self.c += 1          if self.c <= self.num:              return "hahha"          raise StopIteration  # 拋出異常      for i in MyIter(3):      print(i)  # hahha  # hahha  # hahha

自定義range函數

class MyRange:      def __init__(self, start, end, step=1):          self.start = start - 1          self.end = end          self.step = step        def __iter__(self):          return self        def __next__(self):          self.start += self.step          if self.start < self.end:              return self.start          raise StopIteration      for i in MyRange(1, 3):      print(i)  # 1  # 2

上下文管理

上下文:這個概念屬於語言學科,指的是一段話的意義,要參考當前的場景,即上下文

在python中,上下文可以理解為一個程式碼區間,一個範圍,例如with open 打開的文件僅在這個上下文中有效

上下文涉及到的兩個方法

  • __enter__:表示進入上下文(進入某個場景了)
  • __exit__:表示退出上下文(離開了某個場景了)

案例

class MyOpen:        def __enter__(self):          print("enter....")        def __exit__(self, exc_type, exc_val, exc_tb):  # exc --> exception          print("exit.....")          print(exc_type, exc_val, exc_tb)      with MyOpen() as m:      print("start...")      # 1 + '123'  # enter....  # exit.....  # None None None

實現了上面的兩個方法就可以配合with語句用了,當執行with語句時,會先執行__enter__,當程式碼執行完畢後執行__exit__,或者程式碼遇到了異常會立即執行__exit__,並傳入錯誤資訊,包含錯誤的類型,錯誤的資訊,錯誤的追蹤資訊

class MyOpen:        def __enter__(self):          print("enter....")        def __exit__(self, exc_type, exc_val, exc_tb):  # exc --> exception          print("exit.....")          print(exc_type, exc_val, exc_tb)          return True  # return True 可以讓程式不報錯      with MyOpen() as m:      print("start...")      1 + '123'  # TypeError: unsupported operand type(s) for +: 'int' and 'str'  # enter....  # exit.....  # None None None  # 沒有報錯時列印這個  # <class 'TypeError'> unsupported operand type(s) for +: 'int' and 'str' <traceback object at 0x00000283F3EE0608>  # 有錯時列印這個,若__exit__ 返回為True則控制台不報錯,否則控制台也會報錯

注意點

__enter__ 函數應該返回對象自己  __exit__ 函數可以有返回值,是一個bool類型,用於表示異常是否被處理,僅在上下文中出現異常時有用  如果為True 則意味著,異常已經被處理了      False 異常未被處理,程式將中斷報錯