🍖元類

引入

Python中一切皆對象, 那麼本質上也是一個對象python

87965

一.什麼是元類

類既然也是對象, 那麼就應該有另外一個類來實例化獲得它, 實例化獲得類的類就是元類函數

默認情況下, 元類是 type 這個類, 而且全部的類都是由元類實例化獲得的, 包括他本身測試

1.先定義一個類來進行分析

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

p = Immortal("太白",4555)
print(type(p))  # <class '__main__.Immortal'>

全部的對象都是經過 [類名] + ( ) 獲得的, 也叫作實例化, p 對象就是由 Immortal 類實例化獲得的spa

一切皆對象, 那麼 Immortal 應該也是一個類實例化的結果, 因而咱們能夠推導出 元類+( ) ---> Immortal設計

print(type(Immortal))  # <class 'type'>
print(type(type))      # <class 'type'>

經過 type 函數咱們發現 Immortal 的類就是 type 類, 並驚奇的發現 type 類的類是自身3d

461501

由此咱們能夠推斷出 : 元類實例化獲得類, 類實例化獲得對象(實例), 而且驗證了全部類都是由type實例化獲得的, 不信你試試code

1a5d63547a21b3dc2be4584c1a6ee70

二.分析class關鍵字建立類的過程

上面咱們都是使用 class 這個關鍵字來產生類的, class 關鍵字在幫咱們建立類的時候必然調用了 Immortal = type( ) 這種方法, 那麼 type 裏的參數應該是什麼呢?對象

1.內置函數 exec 的用法

在分析 class 工做流程以前咱們先來了解一下 exec 函數做爲儲備知識 : exec 有三個參數繼承

  • 第一個參數是包含的要執行的一系列 Python 代碼(字符串格式)
  • 第二個參數是全局名稱空間 (字典形式), 默認爲 globals( )
  • 第三個參數是局部名稱空間 (字典形式), 默認爲 local( )

做用 : 能夠將字符串裏內容當作Python語句來執行, 並將期間產生的名字存放於局部名稱空間中文檔

msg = '''
name = "shawn"
age = 22
dic = {"sex":"man"}
def test():
    print("I am shawn")
'''
globals_dic = {}  # 全局名稱空間
locals_dict = {}  # 局部名稱空間
exec(msg,globals_dic,locals_dict)

print(locals_dict) 
# {'name': 'shawn', 'age': 22, 'dic': {'sex': 'man'}, 'test': <function test at 0x000002596D9BC4C8>}
print(locals_dict["name"])  # shawn
locals_dict["test"]()       # I am shawn

2.調用 type + ( )來實現建立類

原來咱們使用 class 來建立類, 其中必然調用了元類 type, type中須要傳入三個參數, 這三個參數是類的三大組成部分 :

  • 類名 : Immortal = type( )
  • 父類們(基類們) : class_bases = (object , )
  • 類的名稱空間 : class_namespace (類的名稱空間是執行類體代碼而獲得的)

接下來咱們開始動手建立一個類了

💠設置類名
class_name = "Immortal"

💠設置基類們
class_bases = (object,)

💠設置類體(類的名稱空間)
class_body = '''
name = "shawn"
age = 22
def print_name(self):
    print(f"I am {self.name}")
'''
class_namespace = {}

💠運行"exec"函數
exec(class_body,{},class_namespace)

💠建立類
Immortal = type(class_name,class_bases,class_namespace)

💠實例化
p = Immortal()
print(p.name,p.age)  # shawn 22
p.print_name()       # I am shawn

以上操做就是class關鍵字的工做流程, 至關於下面的效果

class Immortal(object):
    name = "shawn"
    age = 22
    def print_name(self):
        print(f"i am {self.name}")
p = Immortal()

🔰由此可知 "class" 關鍵字的本質就是 Immortal = type('Immortal',(object,),dic)

三.自定義元類來控制類的建立過程

既然咱們已經瞭解了 class 類的工做流程, 那咱們就可使用這種原理來自定義本身的元類

img

1.metaclass 關鍵字指定元類

首先咱們得了解, 一個類若是沒有聲明本身的元類, 那麼它默認的元類就是 type, 除了使用默認的元類, 咱們還能夠經過繼承 type 類來自定義元類, 指定元類的關鍵字是 : metaclass

class Monster(metaclass=type):  # 一個類默認的元類是type, 像左邊這樣
    ...

class Demon(metaclass=Mytype):  # 像這樣, 咱們可使用metaclass關鍵字來指定一個類的元類
    ...

2.自定義元類

值得注意的是 : 若是是元類, 那麼必須繼承 type, 不然就是一個普通的自定義類

class Mytype(type):  # 只有繼承了 type 的類才能稱之爲一個元類, 不然就是一個普通的自定義類
    ...

class Monster(metaclass=Mytype):  # 使用 metaclass 關鍵字來指定元類 Mytype
    def __init__(self,name,age):
        self.name = name
        self.age = age
#🔰"class Monster(metaclass=Mytype):" = "Monster = Mytype('Monster',(object,),{})"

上面是一個簡單版的自定義元類, 咱們能夠知道的是 class Monster(metaclass=Mytype): 這一句等於 Monster = Mytype('Monster',(object,),{}),看到這是否是清晰了不少呢?

下面咱們在 Mytype 中進行一些初始化類的設置

class Mytype(type):  # 只有繼承了 type 的類才能稱之爲一個元類
    def __init__(self,class_name,class_bases,class_namespace):
        print(self)             # <class '__main__.Monster'>
        print(class_name)       # Monster
        print(class_bases)      # (<class 'object'>,)
        print(class_namespace)  # {'__module__': '__main__', '__qualname__': 'Monster', '__init__': <function Monster.__init__ at 0x000002A528B4CDC8>}
        super().__init__(class_name,class_bases,class_namespace)  # 使用父類的__init__來完成初始化

class Monster(metaclass=Mytype):  # 指定元類
    def __init__(self,name,age):
        self.name = name
        self.age = age

print(type(Monster))  # <class '__main__.Mytype'> (能夠發現是自定義的元類建立出來的)

接下來咱們再對這個元類添加一些對建立類的限制功能

  • 需求1 : 類名必須首字母大寫, 不然拋出異常
  • 需求2 : 類中必須有文檔註釋, 不然拋出異常
class Mytype(type):
    def __init__(self,class_name,class_bases,class_namespace):
        super().__init__(class_name,class_bases,class_namespace)
		
        # 設置需求1
        if not class_name.istitle():
            raise Exception('類名首字母必須大寫')
		
        # 設置需求2
        doc = class_namespace.get("__doc__")
        if not doc or len(doc) == 0:
            raise Exception('註釋文檔不能爲空')

class Monster(metaclass=Mytype):
    '''
    我是Monster的註釋
    '''
    def __init__(self,name,age):
        self.name = name
        self.age = age

上面建立的類首字母大寫了, 也有註釋, 是正常運行的, 接下來咱們來測試一下兩種異常

💠首字母沒有大寫
class monster(metaclass=Mytype):
    '''
    我是Monster的註釋
    '''
    def __init__(self,name,age):
        self.name = name
        self.age = age
#🔰 拋出異常 : Exception: 類名首字母必須大寫

💠沒有註釋信息
class Monster(metaclass=Mytype):
    def __init__(self,name,age):
        self.name = name
        self.age = age
#🔰 拋出異常 : Exception: 註釋文檔不能爲空

四.自定義元類來控制類的調用 (類實例化)

1.__call__ 方法的觸發

在進行操做以前咱們先複習一下 __call__ 方法來進行儲備

  • 觸發條件 : [對象] + ( ) 就觸發 __call__ 的執行
class Person:

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

P1 = Person()

P1(1,2,3,4,name="shawn")  # 對象 + ( )
# <__main__.Person object at 0x0000019B82C6AC48>
# (1, 2, 3, 4)
# {'name': 'shawn'}

由上可知, 調用一個對象最早觸發的就是類中的 __call__ 方法, 一切皆對象, 那麼調用這個類的時候是否是應該觸發這個類的類的__call__方法呢😂

下面咱們來簡單驗證一下

class Mytype(type):
    def __call__(self, *args, **kwargs):
        print(self)    # <class '__main__.Monster'>
        print(args)    # (1, 2, 3, 'a')
        print(kwargs)  # {'name': 'shawn'}
        return 111111

class Monster(metaclass=Mytype):
    ...

p = Monster(1,2,3,"a",name = "shawn")
print(p)  # 111111

顯而易見的結論 :

  • 調用 Monster 就是在調用 Mytype 中的__call__方法
  • 而後將 Monster 傳給 Mytype 中的 self, 溢出的位置參數傳給 *,溢出的關鍵字參數傳給 **
  • 調用 Monster 的返回值就是調用 __call__ 的返回值

2.重寫元類中的 __call__ 方法來控制 Monster 的調用過程

默認的, 一個類的調用 (p = Monster("name",29)像這種) 會發生三件事 :

  • 調用__new__ 方法建立一個空對象 obj
  • 調用__init__方法初始化對象 obj
  • return 初始化後的對象 obj
class Mytype(type):
    def __call__(self, *args, **kwargs):
        print("i am Mytype_call")           # 添加測試
        obj = self.__new__(self)            # 建立空對象 obj 
        self.__init__(obj,*args,**kwargs)   # 經過父類來完成obj的初始化 (self=Monster,obj=p)
        return obj                          # 返回初始化以後的 obj

class Monstar(metaclass=Mytype):
    def __init__(self,name,age):
        self.name = name
        self.age = age

p = Monstar("shawn",22)  # i am Mytype_call
print(p.name)            # shawn
print(p.age)             # 22
print(p)                 # <__main__.Monstar object at 0x000001881394AD08>

當整個流程已經明瞭以後咱們就能夠爲所欲爲的對調用過程作一些"手腳"

  • 需求1 : 在元類中控制自定義的類無需使用__init__方法
class Mytype(type):
    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)
        for k,v in kwargs.items():  # 咱們直接遍歷 kwargs 取出 key 和 value
            obj.__dict__[k] = v     # 再將其放入到對象的屬性字典中去

        return obj

class Person(metaclass=Mytype):
    def print_dict(self):
        print(self.__dict__)

p = Person(name="shawn",age=22)    # 這種設計方式就須要你調用傳參的時候按照鍵值對的形式來傳
print(p.name,p.age)  # shawn 22
p.print_dict()       # {'name': 'shawn', 'age': 22}
  • 需求2 : 在元類中控制自定義的類產生的對象相關的屬性所有爲隱藏屬性
class Mytype(type):
    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)           # 獲得空對象obj (self = Person)
        self.__init__(obj,*args,**kwargs)  # 初始化對象obj (obj = p)
        
        # 循環取出對象 p 屬性字典裏面的 key 和 value 進行隱藏屬性操做(這裏使用的是字典推導式)
        obj.__dict__ = {f"_{self.__name__}__{k}":v for k,v in obj.__dict__.items()}
        return obj  # 返回處理後的對象
    
class Person(metaclass=Mytype):
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
	
    def print_name(self):  # 設置一個查看 name 的方法
        print(self.__name)

p = Person("shawn",22,"man")
# print(p.name)   # 拋出異常 : "AttributeError" 沒有該屬性
# print(p.age)    # 拋出異常 : "AttributeError" 沒有該屬性
p.print_name()    # shawn
print(p.__dict__) # {'_Person__name': 'shawn', '_Person__age': 22, '_Person__sex': 'man'}

至此, 自定義元類來控制類的建立過程以及自定義元類來控制類的調用 (類實例化)已經介紹完畢了, 小夥伴有沒有學廢呢?

11115

五.加入元類以後的屬性查找

1.類屬性查找順序 :

  • 先對象層 : Person -----> Foo -----> Bar -----> object
  • 再元類層 : Mytype -----> type
  • 對象的屬性查找: 只會找到object. 不會找元類.

16d90878ff4eec5a5e93a2fd1a20351

class Mytype(type):
    n = 555
    def __call__(self, *args, **kwargs):
        obj=self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj

class Bar(object):
    # n=333
    ...

class Foo(Bar):
    # n=222
    ...

class Person(Foo,metaclass=Mytype):
    # n=111
    def __init__(self,name,age):
        self.name=name
        self.age=age

print(Person.n)

2.使用 __new__ 建立空對象的兩種方式

  • obj = self.__new__(self) : 就是上面咱們使用的方法, 推薦使用這種, 若是 self 中沒有__new__方法, 它是按照繼承關係去查找 __new__ 的, 一層層往上找, 最終能在 object 中找到, 沒必要到 元類中去找
  • obj = object.__new__(self) : 這種方法是直接跳過前面三個類去找 object__new__ 方法, 以下圖 :應該是這樣畫

image-20210106221950211

33132

就這樣吧!

相關文章
相關標籤/搜索