1. 程式人生 > 其它 >今日學習內容總結2.8

今日學習內容總結2.8

今日學習內容總結

      通過上週的學習,我們對面向程式設計的學習也已經走向正軌,對面向物件的三大特性的學習,只要做好複習。再配合上一些小練習對所學的知識進行一個靈活運用。就能從中體驗程式設計思想。而今日就是對面向程式設計學習內容的收尾了。

反射的實際應用

      反射的實際應用:

  # 利用面向物件編寫系統終端功能
  class WinCmd(object):
      def ls(self):
          print('windows系統正在執行ls命令')
      def dir(self):
          print('windows系統正在執行dir命令')
      def cd(self):
          print('windows系統正在執行cd命令')

  class LinuxCmd(object):
      def ls(self):
          print('Linux系統正在執行ls命令')
      def dir(self):
          print('Linux系統正在執行dir命令')
      def cd(self):
          print('Linux系統正在執行cd命令')

  # 建立物件
  obj1 = WinCmd()
  obj2 = LinuxCmd()

  # 反射的使用
  def run(obj):
      while True:
          cmd = input('請輸入您的指令>>>:')
          if hasattr(obj, cmd):
              func_name = getattr(obj, cmd)
              func_name()
          else:
              print('cmd command not found')
  run(obj1)
  run(obj2)
  # 反射提供了一種不需要考慮程式碼的前提下操作資料和功能。

      實際執行後會發現,在run(obj1)中,我們確實能夠根據輸入的指令來執行WinCmd類中的效果。並且判斷當WinCmd類中不存在您輸入的指令的時候返回一個 cmd command not found 。在run(obj2)中同樣如此,執行了 LinuxCmd 類中的方法。這也是反射的強大之處

面向物件中的各種雙下方法

      面向物件中有很多雙下方法,並且這些方法被一些人稱之為魔法方法。而某些雙下方法不需要可以呼叫,而是達到某個條件會自動觸發。比如:init 在物件例項化的時候會自動觸發。而現在我們就對這些雙下方法進行學習與總結。

1.str

      物件被執行列印(print、前端展示)操作的時候自動觸發,該方法必須返回字串型別的資料,很多時候用來更加精準的描述物件。案例:

  class A:
      def __init__(self,name,age):
          self.name = name
          self.age = age

      def __str__(self):
          return f'姓名:{self.name} 年齡:{self.age}'

  a = A('海狗',18)
  print(a) # 姓名:海狗 年齡:18  列印物件觸發__str__方法

2.del

      物件被執行(被動、主動)刪除操作之後自動執行。案例:

  class Foo:
      def __del__(self):
          print('執行我啦')
  f1=Foo()
  del f1  # 執行我啦

3.getattr

      物件查詢不存在名字的時候自動觸發。

4.setattr

      物件在執行新增屬性操作的時候自動觸發。比如:obj.變數名=變數值

5.call

      物件被加括號呼叫的時候自動觸發。案例:

  class A:
      def __init__(self):
          pass
      def __call__(self, *args, **kwargs):
          print('雞哥好帥')
  obj = A()
  obj()  # 雞哥好帥  物件() 自動觸發物件從屬於類(父類)的__call__方法

6.enterexit

      enter 是在物件被執行with上下文管理語法開始自動觸發,該方法返回什麼as後面的變數名就會得到什麼。exit 在物件被執行with上下文管理語法結束之後自動觸發。並且這兩個雙下方法是一組的,作用為上下文管理。案例:

  class A:
      def __init__(self,name):
          self.name = name

      def __enter__(self):
          print(111)
          return self  # 必須返回self

      def __exit__(self, exc_type, exc_val, exc_tb):
          print(333)

  with A('海狗') as obj:
      print(obj.name)
      
  # 列印結果為: 111 海狗  333

7.getattribute

      只要物件查詢名字無論名字是否存在都會執行該方法,如果類中有__getattribute__方法那麼就不會去執行__getattr__方法。

8.new

      物件是object類的__new__方法 產生了一個物件。例項化物件時,先觸發,object 的__new__方法,此方法在記憶體中開闢一個物件空間。再執行__init__方法,給物件封裝屬性。案例:

  class A:
      __instance = None
      def __init__(self,name):
          self.name = name

      def __new__(cls, *args, **kwargs):
          if not cls.__instance:
              cls.__instance = object.__new__(cls)
          return cls.__instance
  obj = A('liky')

      讓字典具備句點符查詢值的功能

  # 定義一個類繼承字典
  class MyDict(dict):
      def __getattr__(self, item):
          return self.get(item)

      def __setattr__(self, key, value):
          self[key] = value


  # 建立一個obj物件
  obj = MyDict({'name': 'jason', 'age': 18})

  # 具備句點符取v
  print(obj.name)  # jason
  print(obj.age)  # 18

  # 具備句點符新增k:v
  obj['gender'] = 'male'
  obj.pwd = 123  # 給字典名稱空間新增名字  不是資料k:v
  print(obj)  # {'name': 'jason', 'age': 18, 'gender': 'male', 'pwd': 123}

元類

元類簡介

      所有的物件都是例項化或者說是通過呼叫類而得到的,python中一切皆物件,通過class關鍵字定義的類本質也是物件,物件又是通過呼叫類得到的,因此通過class關鍵字定義的類肯定也是呼叫了一個類得到的,這個類就是元類。

      元類就是用來例項化產生類的類,因此我們可以得到如下的關係:

      檢視元類的方式

  class MyClass(object):
      pass
  obj = MyClass()
  print(type(obj))  # <class '__main__.MyClass'>
  print(type(MyClass))  # <class 'type'>  檢視MyClass類的型別

產生類的兩種表現形式

      產生類的兩種表現形式是class關鍵字和type元類實現。但其本質其實是一種。因為class關鍵建立類時,一定呼叫了元類。而這兩種產生類的表現形式的寫法:

   # class關鍵字
    class C1(object):
        pass
    print(C1)  # <class '__main__.C1'>
	
    # type元類
    type(類名,父類,類的名稱空間)
    res = type('C1', (), {})
    print(res)  # <class '__main__.C1'>

      由上述程式碼我們發現,在呼叫type時會依次傳入三個引數。這三個引數分別是:


  引數一:包含一系列符合python語法程式碼的字串;

  引數二:字典形式的全域性名稱空間中的名字及所對應的值;

  引數三:字典形式的區域性名稱空間中的名字及所對應的值;

      學習元類的目的

      元類能夠控制類的建立,也就意味著我們可以高度定製類的行為。比如:掌握了物品的生產過程,就可以在過程中做任何的額外操作。而元類可以高度制定類的行為,比如:要求類的名字必須首字母大寫。思考在哪裡編寫定製化程式碼等等。

元類的基本使用

      元類是不能通過繼承的方式直接指定的,需要通過關鍵字引數的形式修改。案例:

  class MyTypeClass(type):
      def __init__(cls, cls_name, cls_bases, cls_dict):
          # print(cls, cls_name, cls_bases, cls_dict)
          if not cls_name.istitle():
              raise Exception("類名的首字母必須大寫")
          super().__init__(cls_name, cls_bases, cls_dict)

  class C1(metaclass=MyTypeClass):
      school = '清華大學'
  # 建立成功,符合條件

  class a(metaclass=MyTypeClass):
      school = '清華大學'
  # 報錯,Exception: 類名的首字母必須大寫

元類的進階操作

      在之前的 call 方法中,物件加括號會自動執行產生該物件的類裡面的__call__,並且該方法返回什麼物件加括號就會得到什麼。由此可以推匯出類加括號會執行元類的裡面的__call__該方法返回什麼其實類加括號就會得到什麼。

      那麼為什麼類加括號就可以被呼叫呢?類呼叫之後又是如何保證先執行類中的__new__方法再執行類中的__init__方法的?其實答案就在__call__方法中。如果想讓一個物件變成一個可呼叫物件(加括號可以呼叫),需要在該物件的類中定義__call__方法,呼叫可呼叫物件的返回值就是__call__方法的返回值。案例:

  class Test():

      def __init__(self):
          self.name = 'python'

      def __call__(self, *args, **kwargs):  # self是Test類的物件
          print(self)  # <__main__.Test object at 0x000001C78CE78FD0>
          print(self.name)


  t = Test()
  t()  # <__main__.Test object at 0x0000027912B19278>   python

      因此我們可以得到以下結論:

  物件加括號呼叫會呼叫該物件的類中定義的__call__方法;
  類加括號呼叫會呼叫內建元類或自定義元類中的__call__方法,取決於類的元類是什麼;
  自定義元類加括號呼叫會內建元類中的__call__方法。

      我們可以通過一個案例驗證一下上述結論:

  class MyMeta(type):
  	
      def __call__(self, *args, **kwargs): 
          print(self)
          print(args)
          print(kwargs)
          
          return 'test'

  class Test(metaclass=MyMeta):
      
      def __init__(self, name, age):
          self.name = name
          self.age = age

  # 呼叫Test就呼叫了
  t = Test()
  print(t)

  '''
    產生結果:
    <class '__main__.Test'>
    ('haha', '123')
    {}
    test
  '''

      通過上述案例我們可以推斷出呼叫Test時,會呼叫自定義元類中的__call__方法,並將返回值賦值給呼叫類產生的物件。

      我們可以通過__call__方法自定義元類來控制類的呼叫,也就是產生物件。案例:

  class Mymeta(type):

      def __init__(self, class_name, class_bases, class_dict):
          
          # 實現類名首字母必須大寫,否則丟擲異常
          if not class_name.istitle():
              raise NameError('類名的首字母必須大寫')
          # 實現建立的類必須有文件註釋,否則丟擲異常
          if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:
              raise TypeError('必須有文件註釋')

      def __new__(cls, *args, **kwargs):

          return type.__new__(cls, *args, **kwargs)

      def __call__(self, *args, **kwargs):
          people_obj = self.__new__(self)
          self.__init__(people_obj,*args, **kwargs)
          return people_obj
      
  Test = MyMeta(class_name, class_bases, class_dic)  # 呼叫的是內建元類type的__call__方法

  class Test(metaclass=Mymeta):
      def __new__(cls, *args, **kwargs):
          # 產生空物件--真正的物件,真正造物件的是object
          return object.__new__(cls)  # 這裡使用type也沒有問題
      
      def __init__(self,name):
          self.name = name
     
      
  t = Test()  # 呼叫的是自定義元類中的__call__方法

      通過自定義元類來建立類的時候,會呼叫type的__call__方法,該方法內部會做三件事情:

  1、先呼叫自定義元類中的__new__方法,產生一個空物件
  2、在呼叫自定義元類中的__init__方法,為空物件新增獨有屬性
  3、返回一個初始化好的自定義元類的物件,就是上述的Test類

      呼叫Test類時則會呼叫自定義元類MyMeta.__call__方法,同樣也會做三件事:

  1、先呼叫 Test類中的__new__方法產生一個空物件
  2、再呼叫Test 類中的__init__方法為空物件新增獨有屬性
  3、返回一個初始化好的Test類的物件賦值給t

      所以,我們可以得到這個道理:如果你想高度定製類的產生過程。如果你想高度定製物件的產生過程,那麼編寫元類裡面的__call__方法。

__new__方法

      建立類時先執行type的__init__方法,當一個類例項化時(建立一個物件)執行type的__call__方法,__call__方法的返回值就是例項化的物件。例項化物件是誰取決於__new__方法,__new__返回什麼就是什麼。而 new 方法的特性:

  __new__方法是在類準備將自身例項化時呼叫。
  __new__方法始終都是類的靜態方法,即使沒有被加上靜態方法裝飾器。
  

      __new__方法的作用主要有兩個:

  1.為物件分配記憶體空間

  2.返回物件的引用

      返回物件的引用的作用是:

      我們學習過python中__init__方法,__init__方法中的第一個引數self就是例項物件的引用,也就是說,哪個物件呼叫該方法,self就指向哪個物件。self中物件的引用就是從__new__方法中返回的,換句話說,__new__方法返回物件的引用,__init__方法用self引數接收了。因此我們可以知道,pyhon在建立物件時,首先呼叫了內建__new__方法,其次再呼叫__init__方法。

      其實我們在程式碼中寫了__new__方法相當於是對該__new__方法進行了一次重寫,重寫 __new__方法一定要 return super().new(cls),否則python直譯器得不到分配的記憶體空間的物件引用,就不會呼叫物件的初始化方法,不會吧物件的引用傳遞到__init__方法中的self中。

      同時,不是所有的地方都可以直接呼叫__new__ ,如果是在元類的__new__裡面,可以直接呼叫。案例:

  class Meta(type):
      def __new__(cls, *args, **kwargs):
          obj = type.__new__(cls,*args,**kwargs)
          return obj

      如果是在元類的__call__裡面,需要間接呼叫:

  class Mate(type):
      def __call__(self, *args, **kwargs):
         obj = object.__new__(self) # 建立一個空物件
         self.__init__(obj,*args,**kwargs) # 讓物件去初始化
         return obj