面向對象:元類、
元類:
python中一切皆對象,意味著:
1. 都可以被引用,如 x = obj
2. 都可以被當做函數的參數傳入
3. 都可以被當做函數的返回值
4. 都可以當做容器類的元素(列表、字典、元祖、集合),如: l = [func,time,obj,1]
換句話說,只要能滿足上述4點,它就是對象;例如,類也是對象(類也是由type實例化產生的)
class Foo: pass class Bar: pass print(type(Foo)) print(type(Bar)) # 運行結果: # <class ‘type‘> # <class ‘type‘>
定義: 產生類的類稱之為元類,默認所有用class定義的類,他們的元類都是type
定義類的兩種方式:
方式一:class方法(class方式也是利用了type這個元類)
方式二:直接利用type這個元類的方法
定義類的三要素: 類名、類的基類、類的名稱空間
"""利用class方法""" class Chinese: country = "China" def __init__(self,name,age): self.name = name self.age = age def talk(self):print("%s is talking" %self.name) """利用type自己定義一個類""" class_name = "Chinese" # 類名 class_base = (object,) # 它的基類;元祖的形式 # 類體(即 類內部的代碼) class_body = """ country = "China" def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) """class_dict = {} # 類的名稱空間 exec(class_body,globals(),class_dict) # 執行類體的代碼,並把類體的值放入到 class_dict中作為其名稱空間 print(class_dict) # 打印結果: # {‘country‘: ‘China‘, ‘__init__‘: <function __init__ at 0x000000D574701E18>, ‘talk‘: <function talk at 0x000000D57487BD90>} # 定義類 Chinese1 = type(class_name,class_base,class_dict) # 這樣就定義出了一個類 print(Chinese) print(Chinese1) # 打印結果: # <class ‘__main__.Chinese‘> # <class ‘__main__.Chinese‘> obj = Chinese("neo",18) obj1 = Chinese1("egon",22) print(obj,obj.name,obj.age) print(obj1,obj1.name,obj1.age) # 打印結果: # <__main__.Chinese object at 0x000000F1D54B67B8> neo 18 # <__main__.Chinese object at 0x000000F1D54B6710> egon 22
自定義元類控制類的行為:
利用class定義類(如上面的 Chinese)時,完整的寫法其實是: class Chinese(object,metaclass = type) # metaclass = type的含義是 元類是type ; “class Chinese:” 這行代碼在執行(即定義Chinese類)時,其實是 Chinese = type(...) ,也就是 type這個元類在實例化。
如下代碼:
"""不以type作為元類,而以自己定義的類作為元類,來控制類的行為""" """第二步的分析:創建 Mymeta 元類""" class Mymeta(type): # class 是調用type這個元類; type元類裏面已經定義了很多的方法,而我只是重新定義其中一小部分代碼,所以我創建的元類需要繼承type,從而Mymeta能夠調用type的其他方法 """ Chinese 的元類是這個Mymeta,在定義Chinese類的時候需要實例化Mymeta, 如: Chinese = Mymeta(class_name,class_bases,class_dict) 需要往Mymeta中傳入定義類的三要素,所以 Mymeta中需要有 __init__ 方法 """ def __init__(self,class_name,class_bases,class_dict): # 這個實例化的__init__ 肯定有這三個參數:class_name,class_bases,class_dict super(Mymeta,self).__init__(class_name,class_bases,class_dict) # type 的 __init__ 方法中有很多的功能,自己創建的元類 Mymeta 首先需要繼承type的__init__ , 同時再定義自己的方法 print(class_name) print(class_bases) print(class_dict) """第一步的分析:不以type作為元類,而以我自己創建的類 Mymeta 作為元類;所以我需要先定義一個元類 Mymeta""" class Chinese(object,metaclass=Mymeta): country = "China" def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) # 類體代碼會以字典的形式傳入到class_dict中作為 Chinese類的名稱空間 # 運行結果: # Chinese # (<class ‘object‘>,) # {‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Chinese‘, ‘country‘: ‘China‘, ‘__init__‘: <function Chinese.__init__ at 0x00000075D7EDBD90>, ‘talk‘: <function Chinese.talk at 0x00000075D7EDBE18>} """執行上述代碼,直接就執行了 Mymeta 中 __init__ 的三個print,是因為 class Chinese(object,metaclass=Mymeta) 就是Mymeta在進行實例化,而實例化時會自動調用 __init__"""
補充知識點: raise TypeError("類型錯誤") # raise 是主動拋出異常,程序就不往下走了
自定義元類控制類的行為示例2:(類名首字母大寫)
class Mymeta(type): def __init__(self,class_name,class_bases,class_dict): """目標功能:如果類名的首字母沒有大寫,直接報錯""" if not class_name.istitle(): # .istitle() 用於判斷字符串首字母是否大寫,返回bool值 raise TypeError("類名首字母必須大寫") # 首字母沒有大寫,直接拋出異常,程序終止 super(Mymeta,self).__init__(class_name,class_bases,class_dict) class chinese(metaclass=Mymeta): # 首字母沒有大寫 # 元類是 Mymeta pass # 執行結果: # 直接報錯 # TypeError: 類名首字母必須大寫
自定義元類控制類的行為示例2:(類體中需要有註釋且註釋不能為空)
補充知識: 類的名稱空間中有一個key 是 “__doc__”,它的含義是“類裏面的註釋”
class Mymeta(type): def __init__(self,class_name,class_base,class_dict): if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # class_dict 中沒有"__doc__"這個key 或者 class_dict["__doc__"]為空 # 空字符串和None自帶bool值為False raise TypeError("必須有註釋,且註釋不能為空") super().__init__(class_name,class_base,class_dict) class chinese(metaclass=Mymeta): pass # 運行結果: # 報錯 # TypeError: 必須有註釋,且註釋不能為空
自定義元類控制類的實例化行為:
補充知識點: __call__ 方法: 讓對象(如 people)也能有 people()的方法。(此方法是給對象用的)
class Foo: def __call__(self, *args, **kwargs): # 能讓對象變成一個可調用對象 # obj()能觸發 __call__ print(self) print(args) print(kwargs) obj = Foo() # 同理,Foo()能這樣調用, Foo的那個元類(type)裏面也有 __call__ 方法 obj(1,2,3,a="A",b="AB") # obj也能用類似於類Foo()的方法 # 運行結果: # <__main__.Foo object at 0x000000D889D0A240> # (1, 2, 3) # {‘a‘: ‘A‘, ‘b‘: ‘AB‘}
"""
元類type內部也應該有一個 __call__ 方法, 會在調用Foo時觸發執行
Foo(1,2,x=1) 即相當於 Foo.__call__(Foo,1,2,x=1) 同時,類的調用也是一個實例化的過程
"""
自定義元類控制類的實例化:
class Mymeta(type): def __init__(self,class_name,class_bases,class_dict): if not class_name.istitle(): raise TypeError("類的首字母必須大寫") if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): raise TypeError("類中必須有註釋且註釋不能為空")
super(Mymeta,self).__init__(class_name,class_bases,class_dict) """現在我自己定義一個__call__ 方法""" def __call__(self, *args, **kwargs): print("=====>>") class Chinese(object,metaclass=Mymeta): """ xx """ def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) # obj = Chinese("neo",22) # Chinese能這樣調用說明Mymeta中默認有 __call__ 方法 obj = Chinese("neo",22) # Chinese("neo",22) 在實例化的過程中會自動調用我重新定義的__call__ , 即 Chinese.__call__(Chinese,"neo",22) print(obj) # 運行結果: # =====>> # None """ 正常情況下,類實例化時會有3個過程發生: 1. 先造出一個空對象 2. 根據__init__把空對象初始化 3. 得到一個返回值 """ # 所以我上面在Mymeta中自定義的__call__ 是不規範的
下面對 Mymeta 中的 __call__ 方法進行修改,並說明類在實例化時自動調用其__init__方法的機制:
class Mymeta(type): def __init__(self,class_name,class_bases,class_dict): # 這個__init__ 控制的是 類(Mymeta)的實例化 if not class_name.istitle(): raise TypeError("類的首字母必須大寫") if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): raise TypeError("類中必須有註釋且註釋不能為空") super(Mymeta,self).__init__(class_name,class_bases,class_dict) def __call__(self, *args, **kwargs): # 它控制的是類(Chinese)的調用 print("=====>>") # 第一步: 先造一個空對象obj obj = object.__new__(self) # object.__new__(cls)的作用是新建一個 cls類 的對象 # 第二步: 把空對象obj初始化 self.__init__(obj,*args,**kwargs) # Chinese調用自己的 __init__ # 把新建的obj這個對象傳入到 Chinese類的__init__ 中 # 這行的 *args和**kwargs的含義是: __call__中的*args和**kwargs是怎麽接收的,就怎麽原封不動的傳給這行代碼中的*args和*kwargs # 第三步: 返回obj return obj """ 以上也是類(Chinese)在實例化時__init__被觸發自動動用的原因,因為 __call__ 被自動調用,而 __call__ 中又手動調用了 類(Chinese)中的 __init__ """ class Chinese(object,metaclass=Mymeta): """ Chinese中的 __init__ 在被 Mymeta 中的 __call__ 調用時,*args中的"neo"當做位置參數傳給 name,**kwargs中的 age=22 當作關鍵字參數傳給 age """ def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) obj = Chinese("neo",age=22) print(obj.__dict__) # 運行結果: # =====>> # {‘name‘: ‘neo‘, ‘age‘: 22}
自定義元類控制類的實例化行為的應用:
單例模式: 如果生成的多個對象裏面的屬性完全一樣,就沒必要實例化多次,讓這多個對象使用同一塊內存空間就行
單例模式實現方式一 :
"""單例模式:讓實例化的的對象共用同一個內存地址""" class Mysql: __instance = None # 設置一個隱藏屬性,用於儲存實例化的對象 def __init__(self): self.host = "127.0.0.1" self.port = 3306 @classmethod def singleton(cls): if not cls.__instance: # __instance 為 None時,說明還沒有實例化過 obj = cls() # 實例化得到一個對象 cls.__instance = obj # 把實例化的對象賦值給 __instance return cls.__instance def con(self): pass def execute(self): pass obj1 = Mysql.singleton() obj2 = Mysql.singleton() obj3 = Mysql.singleton() print(obj1) print(obj2) print(obj3) # 運行結果: # <__main__.Mysql object at 0x000000E52BF366D8> # <__main__.Mysql object at 0x000000E52BF366D8> # <__main__.Mysql object at 0x000000E52BF366D8>
單例模式實現方式二: 元類的方式
"""通過自定義元類控制類的實例化過程:單例模式的 __call__ 方法""" class Mymeta(type): __instance = None # 定義一個隱藏屬性用於儲存實例化的對象 def __init__(self,class_name,class_bases,class_dict): if not class_name.istitle(): raise TypeError("首字母必須大寫,且只能首字母大寫") super().__init__(class_name,class_bases,class_dict) def __call__(self, *args, **kwargs): if not self.__instance: # 如果沒有經過實例化 obj = object.__new__(self) # 創建一個 self 的新對象 self.__init__(obj) # 對新創建的 self的對象執行 Mysql中的 __init__ 方法(實例化) self.__instance = obj # 把實例化後的對象賦值給 __instance return self.__instance class Mysql(metaclass=Mymeta): def __init__(self): self.host = "127.0.0.1" self.port = 3306 def con(self): pass def execute(self): pass obj1 = Mysql() obj2 = Mysql() obj3 = Mysql() print(obj1) print(obj2) print(obj3) # 運行結果: # <__main__.Mysql object at 0x0000001965AC67B8> # <__main__.Mysql object at 0x0000001965AC67B8> # <__main__.Mysql object at 0x0000001965AC67B8>
面向對象:元類、