元少帶你看元類


一、什么是元類?

基於python的宗旨:一切皆對象。而對象都是由類實例化得到的

class OldboyTeacher(object):
    school = 'oldboy'
    def __init__(self,name):
        self.name = name
    def run(self):
        print('%s is running'%self.name)
t1 = OldboyTeacher('jason')
# 對象t1是由類OldboyTeacher實例化得到

那么類也是對象,它又是誰實例化得到的呢?

# 分別查看對象t1和OldboyTeacher的類型
print(type(t1))
print(type(OldboyTeacher))

# 結果為:
<class '__main__.OldboyTeacher'>
<class 'type'>

結論1:元類就是產生類的類,默認情況下type就是所有類的元類

二、不依賴於class關鍵字創建類

根據第一個結論我們能理出兩條對應關系

  1.調用元類得到自定義的類

  2.調用自定義的類得到自定義的類的對象

現在我們來看第一對關系,調用元類來得到自定義的類,都需要哪些參數(OldboyTeacher=type(...),括號內傳什么?)

我們自定義一個類的時候都有哪些關鍵的組成部分:

  1.類名

  2.類的父類

  3.類的名稱空間

就以第一階段的OldboyTeacher類為例,calss關鍵字創建自定義類的步驟

"""
1.獲取類名(OldboyTeacher)

2.獲取類的父類(object,)

3.執行類體代碼獲取產生的名稱空間(如何獲取???)

4.調用元類得到自定義類OldboyTeacher = type(class_name,class_bases,{...})
"""

知識點補充:

如何執行一段字符串內部的代碼並將產生的名稱空間交給對應的參數?  >>>   exec()

class_body = """
school = 'oldboy'
def __init__(self,name):
      self.name = name
def run(self):
      print('%s is running'%self.name)
"""
class_dic = {}
class_global = {}

exec(class_body,class_global,class_dic)
# class_global一般情況下都為空,除非在字符串代碼內部用global關鍵字聲明,才會將產生的名字丟到class_global全局名稱空間中
print(class_dic)

{'school': 'oldboy', '__init__': <function __init__ at 0x000000B5D2771EA0>, 'run': <function run at 0x000000B5DB5B7400>}

有了這個exec方法后,我們就可以不依賴於calss關鍵字創建自定義類

# 類名
class_name = 'OldgirlTeacher'
# 類的父類
class_bases = (object,) # 注意必須是元祖,逗號不能忘 # 名稱空間 class_body = """ school = 'oldgirl' def __init__(self,name): self.name = name def run(self): print(self.name) """ class_dic = {} exec(class_body,{},class_dic) #調用元類創建自定義類 OldgirlTeacher = type(class_name,class_bases,class_dic) print(OldgirlTeacher) # 結果為:<class '__main__.OldgirlTeacher'> # 並且它可以訪問自身的屬性和方法,並實例化產生對象 print(OldgirlTeacher.school) print(OldgirlTeacher.run) # 結果為: """ oldgirl <function run at 0x000000229B157378> """ obj = OldgirlTeacher('jason') print(obj.school) obj.run()
""" oldgirl jason """

三、自定義元類控制類的創建過程

1.如何自定義元類

class Mymeta(type):  # 必須是繼承了type的類才是自定義元類
    pass

class oldboyTeacher(metaclass=Mymeta):  # 通過metaclass可以指定類的元類
    school = 'oldboy'

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

    def run(self):
        print('%s is running'%self.name)

2.__call__

思考:一個類的對象加括號調用會執行該對象父類中的__call__方法,那么類也是對象,它在加括號實例化對象的時候,是不是也應該走它父類的__call_方法?

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

class OldboyTeacher(object,metaclass=Mymeta):
    school = 'oldboy'
    def __init__(self,name):
        self.name = name
    def run(self):
        print('%s is running'%self.name)
obj = OldboyTeacher('jason')

"""
打印結果:
<class '__main__.OldboyTeacher'>
('jason',)
{}
"""

思考:類加括號實例化對象的時候,有哪幾個步驟?

  1.創建一個該類的空對象

  2.實例化該空對象

  3.將實例化完成的空對象返回給調用者

# 也就是說__call__里面需要做三件事
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        # 1.產生空對象
        # 2.初始化對象
        # 3.返回該對象
        # 那我先做最后一件事,返回一個123,發現
        return 123

obj = OldboyTeacher('jason')
print(obj)  
# 結果就是123    

那接下來就需要我手動去干這三件事了

 
         
class Mymeta(type):  
  def
__call__(self, *args, **kwargs): # 1.產生一個空對象 obj = self.__new__(self) # 2.實例化該對象 self.__init__(obj,*args,**kwargs) # 3.返回該對象 return obj # 關於這個__new__,我們是不是不知道是個啥,我這里直接告訴你,它就是用來創建空對象的

思考:這是類加括號產生對象的過程,那么我元類加括號產生類的過程是不是也應該是這個三步

  1.產生一個空對象(指類)

  2.實例化該空對象(實例化類)

  3.將實例化完成的類對象返回

那依據上面的推導,self.__new__就是關鍵了,我可以在我的自定義元類里面定義一個__new__方法,看看它到底是個啥

class Mymeta(type):
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)


class OldboyTeacher(object,metaclass=Mymeta):
    school = 'oldboy'

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

    def run(self):
        print('%s is running' % self.name)

"""
<class '__main__.Mymeta'>
('OldboyTeacher', (object,), {'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x000000323CEB9510>, 'run': <function OldboyTeacher.run at 0x000000323CEE7158>})
{}
"""

我們發現__new__里面的*args參數接收到了三個位置參數,並且很容易辨認它們對應的就是類名,類的父類,類體代碼執行后的名稱空間

那么我們可不可以將__new__()的形參換一種寫法

class Mymeta(type):
    def __new__(cls, class_name,class_bases,class_dic):
        print(class_name)
        print(class_bases)
        print(class_dic)
     # 這里需要記住的是,必須在最后調用元類type中的__new__方法來產生該空對象
return type.__new__(cls,class_name,class_bases,class_dic) class OldboyTeacher(metaclass=Mymeta): school = 'oldboy' def __init__(self,name): self.name = name def run(self): print('%s is running'%self.name)

驗證:

class Mymeta(type):
    def __new__(cls, class_name,class_bases,class_dic):
        print(class_name)
        print(class_bases)
        print(class_dic)
        class_dic['xxx'] = '123'
        if 'school' in class_dic:
            class_dic['school'] = 'DSB'
        return type.__new__(cls,class_name,class_bases,class_dic)
    
class OldboyTeacher(metaclass=Mymeta):
    school = 'oldboy'
    def __init__(self,name):
        self.name = name
    def run(self):
        print('%s is running'%self.name)

print(OldboyTeacher.xxx)  # 發現可以打印出來    123
print(OldboyTeacher.school) # DSB

結論:

由此我們就可以通過自定義元類,並重寫__new__方法來攔截類的創建過程,在類被創建出來之前進行一系列其他操作

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM