python面向對象反射-框架原理-動態導入-元類-自定義類-單例模式-項目的生命周期-05

  • 2019 年 10 月 7 日
  • 筆記

反射 reflect

反射(reflect)其實是反省,自省的意思

反省:指的是一個對象應該具備可以檢測、修改、增加自身屬性的能力

反射:通過字符串獲取對象或者類的屬性,進行操作

設計框架時需要通過反射去檢測類的屬性,去調用他們

反射涉及的四個函數

​ 這四個就是普通的內置函數,沒有雙下劃線,與print等等沒有區別

hasattr getattr setattr delattr

class Person:      def __init__(self, name, age, gender):          self.name = name          self.age = age          self.gender = gender      p = Person('jack', 16, 'man')  print(p.name)  # jack    # -----------------------------------------  # hasattr()  判斷某個對象中是否存在某個屬性  # -----------------------------------------  if hasattr(p, 'age'):      # -----------------------------------------      # getattr()  從對象中取出值      #   不存在時返回第三個參數      # -----------------------------------------      print(getattr(p, 'age', None))      # 16      # -----------------------------------------      # setattr()  為對象添加新的屬性、修改屬性      # -----------------------------------------      print(setattr(p, 'name', 'jackson'))      # None      print(getattr(p, 'name'))      # jackson      print(setattr(p, 'school', 'oldboy'))      # None      print(getattr(p, 'school'))      # oldboy      # -----------------------------------------      # delattr()  刪除對象的屬性      # -----------------------------------------      delattr(p, 'school')      # print(getattr(p, 'school'))  # 直接報錯,AttributeError: 'Person' object has no attribute 'school'

反射的使用場景

​ 反射其實就是對屬性的增刪改查,但是如果直接使用內置的__dict__來寫,語法繁瑣,不好理解;另一個主要問題是,如果對象不是自己寫的,而是另一方提供的,我就必須判斷這個對象是否滿足需求,也就是是否我需要的屬性和方法

"""  反射被稱為框架的基石,為什麼?      因為框架的設計者,不可能提前知道你的對象到底是怎麼設計的      所以你提供給框架的對象 必須通過判斷驗證之後才能正常使用      判斷驗證就是反射要做的事情,      當然通過__dict__也是可以實現的, 其實這些方法也就是對__dict__的操作進行了封裝  """

簡易框架代碼

執行文件代碼

myframework.py代碼

# myframework.py  """  需求:要實現一個用於處理用戶的終端指令的小框架      框架就是已經實現了最基礎的構架,就是所有項目都一樣的部分  """  from libs import plugins      # 框架已經實現的部分  def run(plugin):      while True:          cmd = input("請輸入指令:")          if cmd == "exit":              break          # 因為無法確定框架使用者是否傳入正確的對象所以需要使用反射來檢測          # 判斷對象是否具備處理這個指令的方法          if hasattr(plugin,cmd):              # 取出對應方法方法              func = getattr(plugin,cmd)              func() # 執行方法處理指令          else:              print("該指令不受支持...")      print("see you la la!")      # 創建一個插件對象 調用框架來使用它  # wincmd = plugins.WinCMD()  # 框架之外的部分就由自定義對象來完成  linux = plugins.LinuxCMD()  run(linux)

plugins代碼

libs/plugins.py代碼

# libs/plugins.py  class WinCMD:        def cd(self):          print("wincmd 切換目錄....")        def delete(self):          print("wincmd 要不要刪庫跑路?")        def dir(self):          print("wincmd 列出所有文件....")    class LinuxCMD:        def cd(self):          print("Linuxcmd 切換目錄....")        def rm(self):          print("Linuxcmd 要不要刪庫跑路?")        def ls(self):          print("Linuxcmd 列出所有文件....")

動態導入

上述框架代碼中,寫死了,固定了哪個類,這是不合理的,因為無法提前知道對方的類在什麼地方以及類叫什麼,所以我們應該為框架的使用者提供一個配置文件,要求對方將類的信息寫入配置文件,然後框架自己去加載需要的模塊

動態導入:不需要提前知道類,告訴路徑可以自己找

最後的搭框架代碼:

myframework.py代碼

# myframework.py  from conf import settings  import importlib  # 1.動態導入模塊    # 框架已經實現的部分  def run(plugin):      while True:          cmd = input("請輸入指令:")          if cmd == "exit":              break          # 因為無法確定框架使用者是否傳入正確的對象所以需要使用反射來檢測          # 判斷對象是否具備處理這個指令的方法          if hasattr(plugin,cmd):              # 取出對應方法方法              func = getattr(plugin,cmd)              func() # 執行方法處理指令          else:              print("該指令不受支持...")      print("see you la la!")      # 框架 得根據配置文件拿到需要的類  path = settings.CLASS_PATH  # 從配置中單獨拿出來 模塊路徑和 類名稱  module_path, class_name = path.rsplit(".", 1)  # 2.拿到並導入模塊  mk = importlib.import_module(module_path)  # 拿到類  cls = getattr(mk,class_name)  # 用類實例化出對象  obj = cls()  # 調用框架  run(obj)

libs/plugins.py代碼

# libs/plugins.py  class WinCMD:        def cd(self):          print("wincmd 切換目錄....")        def delete(self):          print("wincmd 要不要刪庫跑路?")        def dir(self):          print("wincmd 列出所有文件....")    class LinuxCMD:        def cd(self):          print("Linuxcmd 切換目錄....")        def rm(self):          print("Linuxcmd 要不要刪庫跑路?")        def ls(self):          print("Linuxcmd 列出所有文件....")

conf/settings.py文件

# conf/settings.py  CLASS_PATH = "libs.plugins.WinCMD"

如此一來,框架就與實現代碼徹底解耦了,只剩下配置文件了

元類 metaclass

元類是用於創建類的類

在python中,萬物皆對象,類當然也是對象

推理:對象是通過類實例化產生的,如果類也是對象的話,必然類對象也是由另一個類實例化產生的

class Person:      pass      p = Person()  print(type(p), type(Person), type(object))  # <class '__main__.Person'> <class 'type'> <class 'type'>  # Person類是通過type類實例化產生的(object類內部是由C語言實現的)    # 直接調用type類來產生類對象(默認情況下所有類的元類都是type)  class Student:      pass      print(type(Student))  # <class 'type'>

一個類的基本組成 *****

  1. 類的名字(字符類型)
  2. 類的父類們 (是一個元組或列表)
  3. 類的名稱空間(字典)
cls_obj = type("ClassDog", (), {})  print(cls_obj)  # <class '__main__.ClassDog'>      class Dog:      pass      print(Dog)  # <class '__main__.Dog'>

學習元類目的

目的:可以高度地自定義類,例如控制一個類的名字必須以大駝峰的方式來書寫

類也是對象,也有自己的類

我們的需求是 創建類對象做一些限制

  • 想到了初始化方法,我們只要找到了類對象的類(元類),覆蓋其中__init__方法就能實現需求
  • 當然我們不能修改源代碼,所以應該繼承type來編寫自己的元類,同時覆蓋__init__來完成需求
'''  只要繼承了type, 那麼這個類就變成了一個元類    '''      class MyType(type):  # 定義一個元類      def __init__(self, cls_name, bases, dict):          print(cls_name, bases, dict)          # Pig () {'__module__': '__main__', '__qualname__': 'Pig'}          if not cls_name.istitle():              raise Exception("好好寫類名!")          super().__init__(cls_name, bases, dict)        pass      # class pig(metaclass=MyType):  # 為Pig類指定了元類為MyType      # 直接報錯,Exception: 好好寫類名!  class Pig(metaclass=MyType):        pass      # # MyType("pig", (), {})  # Exception: 好好寫類名!  print(MyType("Pig", (), {}))  # <class '__main__.Pig'>

__call__

元類中的call方法

# 當你調用類對象時,會自動執行元類中的__call__方法,並將這個類作為第一個參數傳入,以及後面的一堆參數    # 覆蓋元類中的call之後,這個類就無法產生對象對象無法實例化,必須調用super().__call__()來完成對象的創建並返回其返回值

__call__ 與 __init__的使用場景:

  1. 想要控制對象的創建過程__call__
  2. 想要控制類的創建過程__init__
'''  需求:想要把對象的所有屬性變成大寫  '''  class MyType(type):      def __call__(self, *args, **kwargs):          new_args = [item.upper() for item in args]          return super().__call__(*new_args, **kwargs)      class Person(metaclass=MyType):  # ---> Person = MyType("Person", (), {})      def __init__(self, name):          self.name = name      p = Person('jack')  print(p.name)  # JACK
# 要求創建對象時必須以關鍵字傳參  #   覆蓋元類的__call__  #   判斷有沒有傳非關鍵字參數,有就不行  class MyMeta(type):      # def __call__(self, *args, **kwargs):      #     obj = object.__new__(self)      #     self.__init__(obj, *args, **kwargs)      #     return obj        def __call__(self, *args, **kwargs):          if args:              raise Exception("非法傳參(只能以關鍵字的形式傳參!)")          return super().__call__(*args, **kwargs)      class MyClass(metaclass=MyMeta):      def __init__(self, name):          self.name = name      # my_class_obj = MyClass('123')  # # 報錯Exception: 非法傳參(只能以關鍵字的形式傳參!)  my_class_obj = MyClass(name='123')  print(my_class_obj)  # <__main__.MyClass object at 0x000002161DD187B8>

注意:一旦覆蓋了__call__必須調用父類的__call__方法來產生對象並返回這個對象

補充__new__方法

當你要創建類對象時(類 + ()),會首先執行元類中的__new__方法,拿到一個空對象,然後會自動調用__init__方法來對這個類進行初始化操作

class MyMeta(type):      # def __call__(self, *args, **kwargs):      #     obj = object.__new__(self)      #     self.__init__(obj, *args, **kwargs)      #     return obj      pass

注意:如果你覆蓋了__new__方法,則必須保證__new__方法必須有返回值,且必須是對應的類對象

class Meta(type):        def __new__(cls, *args, **kwargs):          print(cls)  # 元類自己          print(args)  # 創建類需要的幾個參數  類名,基類,名稱空間          print(kwargs)  # 空的          print("__new__ run")          # return super().__new__(cls,*args,**kwargs)  # 等同於下面這句          obj = type.__new__(cls, *args, **kwargs)          return obj        def __init__(self, a, b, c):          super().__init__(a, b, c)          print("__init__ run")      class A(metaclass=Meta):      pass      print(A)  # <class '__main__.Meta'>  # ('A', (), {'__module__': '__main__', '__qualname__': 'A'})  # {}  # __new__ run  # __init__ run  # <class '__main__.A'>

總結:__new____init__都可以實現控制類的創建過程,還是__init__更簡單

單例設計模式

'''  設計模式?      用於解決某種固定問題的套路      如:MVC、MTV.......    單例:指的是一個類只能產生一個對象,可以節省空間    為什麼要單例:      是為了節省空間,節省資源      當一個類的所有對象屬性全部相同時則沒有必要創建多個對象  '''
class Single(type):      def __call__(self, *args, **kwargs):          if hasattr(self, 'obj'):  # 判斷是否存在已經有了的對象              return getattr(self, 'obj')  # 有就把那個對象返回            obj = super().__call__(*args, **kwargs)  # 沒有則創建          print("new 了")          self.obj = obj  # 並存入類中          return obj      class Student(metaclass=Single):      def __init__(self, name):          self.name = name      class Person(metaclass=Single):      pass      # 只會創建一個對象  Person()  # 只會執行一次  # new 了  Person()  Person()  Person()  Person()

項目的生命周期

需求分析

​ 明確用戶需求,用戶到底需要什麼樣的程序,要實現什麼樣的功能,很多時候,用戶都是在意淫,邏輯上是不正確的,所以需要工程師,與用戶當面溝通以確定用戶的真實需求,以及需求的可實現性,並根據最終的需求,產生項目需求分析書

技術選型

​ 確定開發該項目採用什麼語言、框架、數據庫以及版本等等

​ 我們需要根據公司的實際情況考慮採用的框架技術,通常要做的業務業界用主流的實現方案,例如各種框架的版本,要考慮兼容性,流行程度,以及工程師的熟練程度

設計項目架構

​ 例如;數據庫設計,項目構架(MVC、MTV、三層架構等)的設計

​ 由於項目不可能一次開發完就完事了,後期需要維護擴展,所以良好的架構設計對後續的維護擴展有重要意義

​ 另外如果你的思路從一開始就不正確,那後期很有可能把整個項目推翻重寫

​ 項目的設計當然是越早越好,但是學習階段,直接按照某種架構來編寫,你會覺得非常抽象,為什麼要這麼設計,好處是什麼?會造成一種感覺是,還沒開始寫代碼就已經懵逼了 所以要先明確不進行設計前存在的問題,然後在找相應的解決方案

開發階段

​ 項目經理會分配任務給每個人,作為後台開發需要提供接口文檔,才能使雙方按照相同的協議來進行開發,協作開發需要用到的一些工具:git、svn

​ 項目開發其實是耗時相對較短的一個階段,前提是需求已經明確,項目設計沒有問題,然後根據需求分析書進行代碼編寫,公司一般都有多個工程師,項目經理負責分配任務,每個工程師開發完自己的模塊後提交自己的代碼,最後所有代碼合併到一起,項目開發完成

項目測試

​ 大公司通常會有專門的測試工程師,開發完成並不意味這,工作結束了,需要將完整的項目交個測試工程師,一些小公司可能沒有這個崗位,那就需要開發工程師自己完成測試

上線部署

​ 需要部署服務器,安裝相應的環境,發佈代碼,還需要為服務器申請一個域名

​ 在測試通過後項目就可以上線運行了,上線運行指的是是你的程序能夠真正的運行在互聯網上,在開發項目階段一般使用的都是局域網環境,普通用戶是無法訪問的,需要將項目部署到服務器上,再為服務器獲取一個公網ip,並給這個ip綁定域名,至此項目即正常運行了

更新維護

​ 後續都需要增加新功能或是修改各種bug,不斷地完善項目,進行版本的更新迭代,當然如果公司不幸倒閉了,那麼這個項目的生命周期也就結束了