python面向物件之元類
python面向物件之元類
什麼是元類(metaclass)?
type是內建的元類
用class關鍵字定義的所有的類都是由內建的元類type實現的
所有內建的類也都是由內建的元類type實現的
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
class Person:
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份碼是{self.id}' )
print(type(Person))
print(type(int))
執行結果
<class 'type'>
<class 'type'>
class關鍵字與元類造類步驟
我們通過上述分析可以發現,class關鍵字其實是在呼叫type元類例項化了一個類並返回了一個類物件。而要知道在這個過程中發生了什麼,首先我們需要明白類有哪些必須的屬性
- 1、類名 - class_name = 'Person' - 2、類的基類 - class_bases = (object,) - 3、類的名稱空間 - 內部包含了類的方法、屬性構成的程式碼塊 - class_dic = {}
下面我們根據類的三個要素,分別用class關鍵字和type元類例項化來分別創造一個Person類:
利用class關鍵字
# 用class關鍵字創造Person類
class Person:
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份碼是{self.id}')
print(Person)
執行結果
<class '__main__.Person'>
利用type元類例項化
"""用type元類例項化一個Person類"""
class_name = 'Person'
class_bases = (object,)
class_dic = {}
class_body = '''
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份碼是{self.id}')
'''
# 用exec方法模擬創造類的名稱空間
exec(class_body, {}, class_dic)
print(type(class_name, class_bases, class_dic))
執行結果
<class '__main__.Person'>
可以看到,class關鍵字在建立一個類的時候做了三件事
- 得到名稱空間
- 呼叫元類
- 例項化得到一個類
從上面實驗我們可以發現,元類例項化和class關鍵字所創造出來的類並沒有太大區別,區別在於我們簡單例項化元類創造出來的類並沒有包含class關鍵字創造出來的類中的內建方法與功能屬性。那元類存在的意義是什麼呢?
自定義元類控制類的產生
剛剛我們明白了元類是如何創造一個類的,基於該過程中,我們發現在元類例項化的過程中需要傳入三個引數,那麼我們是否可以通過自定義一個元類然後給他傳入三個引數來對類實現一個高度化的定製。
首先我們要清楚如何自定義一個元類,元類也是一個類,但是只有繼承了type類的類才是元類。
根據這一點我們可以自定義一個元類Mymeta
自定義一個元類
class My_meta(type):
def __init__(self, class_name, class_bases, class_dic):
print('My_meta is running...')
print(self)
print(class_name)
print(class_bases)
print(class_dic)
接下來我們通過例項化這個元類My_meta來創造一個Person類
例項化元類
class Person(metaclass=My_meta):
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份碼是{self.id}')
執行結果
My_meta is running...
<class '__main__.Person'>
Person
()
{'__module__': '__main__', '__qualname__': 'Person', '__init__': <function Person.__init__ at 0x031FCDB0>, 'info': <function Person.info at 0x031FCDF8>}
我們知道在呼叫Mymeta的過程中會發生下面三件事:
- 創造一個空物件Person
- 呼叫My_meta內的init方法,完成初始化物件
- 返回初始化的物件
上面過程中我們成功的自定義了一個簡單的元類,並對它進行了例項化產生了一個Person類,那麼如何通過元類對類進行一個控制,或者說是實現一個高度定製化呢?
元類實現控制類
元類要如何控制類呢? 從上面我們可以看到,元類例項化產生的類必然要遵循元類的邏輯,因此我們可以在元類的init方法中增加自定義的邏輯程式碼實現對例項化出來的類的控制
下面我們對類名進行一個控制,比如在python中,類名通常被要求首字母大寫,但是不大寫程式也不會報錯,基於這點我們可以在元類中要求首字母必須大寫,否則報錯
class My_meta(type):
def __init__(self, class_name:str, class_bases, class_dic):
if not class_name.istitle():
raise NameError('類名首字母必須大寫!')
class person(metaclass=My_meta):
pass
執行結果
NameError: 類名首字母必須大寫!
在上面的操作中我們通過控制呼叫Mymeta過程中第二個過程,通過init方法對類進行了一個控制,那麼我們是否可以通過控制第一個和第三個過程來實現對類的控制呢?
我們來分析第一個過程,我們知道建立一個空物件是利用了類的new方法進行實現的,所以我們只需要對元類中的new方法進行重寫。
class My_meta(type):
# 第二部執行
def __init__(self, class_name, class_bases, class_dic):
print('init is running...')
print(self, class_name, class_bases, class_dic)
# 第一步執行
def __new__(cls, *args, **kwargs):
print('new is running...')
print(cls, args, kwargs)
return super().__new__(cls, *args, **kwargs)
執行結果
new is running...
<class '__main__.My_meta'> ('Person', (), {'__module__': '__main__', '__qualname__': 'Person', '__init__': <function Person.__init__ at 0x030ACDF8>, 'info': <function Person.info at 0x030ACE40>}) {}
init is running...
<class '__main__.Person'> Person () {'__module__': '__main__', '__qualname__': 'Person', '__init__': <function Person.__init__ at 0x030ACDF8>, 'info': <function Person.info at 0x030ACE40>}
因此我們如果需要在初始化物件(類)對產生的類進行控制,那麼我們在new方法中重寫邏輯就可以達到我們的目的
下面我們再來分析第三個過程,返回一個物件。在我們呼叫Mymeta過程中,其實是在呼叫一個call方法,如果我們想讓一個物件可以加括號進行呼叫,那麼我們就需要在該物件的類中新增一個call方法,清楚了這個知識,我們就可以通過重寫call方法對返回物件過程進行控制
class My_meta(type):
# 第二部執行
def __init__(self, class_name, class_bases, class_dic):
print('init is running...')
print(self, class_name, class_bases, class_dic)
# 第一步執行
def __new__(cls, *args, **kwargs):
print('new is running...')
print(cls, args, kwargs)
return super().__new__(cls, *args, **kwargs)
# 第三步執行
def __call__(self, *args, **kwargs):
return 'call is running...'
class Person(metaclass=My_meta):
pass
print(Person())
執行結果
new is running...
<class '__main__.My_meta'> ('Person', (), {'__module__': '__main__', '__qualname__': 'Person', '__init__': <function Person.__init__ at 0x0126CE88>, 'info': <function Person.info at 0x0126CED0>}) {}
init is running...
<class '__main__.Person'> Person () {'__module__': '__main__', '__qualname__': 'Person', '__init__': <function Person.__init__ at 0x0126CE88>, 'info': <function Person.info at 0x0126CED0>}
call is running...
到這裡,我們通過定製了一個元類並通過控制呼叫原來的三個流程來實現了控制類的產生。