1. 元類是什么
眾所周知,對象由類實例化而來,類是對象的模板,而python一切皆對象,類也是對象,它由元類(type)創建,所以元類是類的類,是類的模板
2. 創建類的另一種方法
一般情況下,我們使用class關鍵字申明一個類,就像
class Demo:def __init__(self,name,age):self.name = nameself.age = agedef output(self):print("name is " + str(self.name) + " age is " + str(self.age))if __name__ == '__main__':demo = Demo("Bob",18)demo.output()
python 中所有的類都是通過type創建的,所以當我們使用type()函數查看類的類型時會顯式<class type>
>>> class Demo:pass>>> type(Demo)
<class 'type'>
>>> demo = Demo()
>>> type(demo)
<class '__main__.Demo'>
通過類實例化出來的對象的類型是
<class 類名>
,這樣也更加驗證了所有類都是由元類type實例化而來
通過type創建類
可以看一下type的文檔,type可以傳入三個參數,object_or_name, bases, dict,當只有一個參數是object時,返回該對象的類型,就是最常使用的這種情況,當傳入name, bases, dict參數時,會返回一個類,name是類名,bases是基類元組,dict是類中屬性和方法的字典
class type(object):"""type(object_or_name, bases, dict)type(object) -> the object's typetype(name, bases, dict) -> a new type"""
我們使用type重寫一下上面的Demo類
# 模擬__init__()
def __init__(self,name,age):self.name = nameself.age = agedef output(self):print("name is " + str(self.name) + " age is " + str(self.age))class_name = 'Demo'
class_bases = (object,)
class_dict = {'__init__':__init__,'output': output,
}# type(name, bases, dict) -> a new type
Demo = type(class_name,class_bases,class_dict)
demo = Demo('Bob',18)
demo.output() # name is Bob age is 18
實際上,每次用class定義類時,執行的都是type()方法
3. MetaClass
既然所有類都是由type創建的,那我們就可以控制類的創建行為,這就需要使用元類metaclass
- 元類用來創建類,實質上也是一個類,繼承自type
__new__
是真正的構造函數,用來分配內存空間__new__(cls: type, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> type
def add(self, value):print('add one value')self.append(value)class ListMetaclass(type):def __new__(mcs, name, bases, namespace):namespace['add'] = addreturn type.__new__(mcs, name, bases, namespace)class MyList(list, metaclass=ListMetaclass):passli = MyList()
li.add(1)
print(li)
在用class定義類時,括號中可以指定metaclass,指定后會創建__metaclass__
,python在創建類的時候,會先檢查有沒有__metaclass__
,如果有,就會以此方法創建對象,沒有就會逐級向上查找父類中有沒有該,如果找到當前package中還沒有找到,就會使用默認的type創建(調用metaclass.__new__()
)
值得注意的是,如果我們在做類的定義時,在class聲明處傳入關鍵字metaclass=ListMetaclass,那么如果傳入的這個metaclass有__call__函數,這個__call__函數將會覆蓋掉MyList class的__new__函數。這是為什么呢?請大家回想一下,當我們實例化MyList的時候,用的語句是L1=MyList(),而我們知道,__call__函數的作用是能讓類實例化后的對象能夠像函數一樣被調用。也就是說MyList是ListMetaclass實例化后的對象,而MyList()調用的就是ListMetaclass的__call__函數。另外,值得一提的是,如果class聲明處,我們是讓MyList繼承ListMetaclass,那么ListMetaclass的__call__函數將不會覆蓋掉MyList的__new__函數。
元類在一般情景下很少用到,但在像ORM中還是會有應用的,ORM(對象關系映射),ORM看這位大佬的文章談談Python中元類Metaclass(二):ORM實踐
4. 總結
- 通過class定義的類其實是通過type()創建的
- type(object_or_name, bases, dict)
- 如果想要控制類的創建行為,需要在創建類時指定metaclass,一旦指定了metaclass,就會在class上添加
__metaclass__
,創建類時會找__metaclass__
指向的類,并用這個類創建類,如果找不到,就會調用默認的type()
參考文章
Python中的元類(metaclass)
談談Python中元類Metaclass(一):什么是元類
Python之元類