簡單的講,元類建立了Python中全部的對象。python
咱們說Python是一種動態語言,而動態語言和靜態語言最大的不一樣,就是函數和類不是編譯時定義的,而是運行時動態建立的。數據庫
比方說咱們要定義一個HelloWorld
的class,就寫一個helloworld.py
模塊:app
class HelloWorld(object): def helloworld(self): print('Hello World!')
當Python解釋器載入helloworld
模塊時,就會依次執行該模塊的全部語句,執行結果就是動態建立出一個HelloWorld
的class對象,測試以下:ide
>>> from helloworld import HelloWorld >>> h = HelloWorld() >>> h.helloworld() Hello, world! >>> print(type(HelloWorld)) <class 'type'> >>> print(type(h)) <class 'helloworld.HelloWorld'>
type()
函數用來查看一個類型或變量的類型,HelloWorld
是一個class,它的類型就是type
,而h
是一個實例,它的類型就是class Helloworld
。函數
咱們說class的定義是運行時動態建立的,而建立class的方法就是使用type()
函數。工具
定義:type(類名, 父類的元組(針對繼承的狀況,能夠爲空),包含屬性的字典(名稱和值))測試
type()
函數既能夠返回一個對象的類型,又能夠建立出新的類型,好比,咱們能夠經過type()
函數建立出HelloWorld
類,而無需經過class HelloWorld(object)...
的定義:spa
>>> def helloworld_outside(self): # 先定義函數 ... print('Hello World!') ... >>> HelloWorld = type('HelloWorld', (object,), dict(helloworld=helloworld_outside)) # 建立HelloWorld class >>> h = HelloWorld() >>> h.helloworld() Hello, world! >>> print(type(HelloWorld)) <class 'type'> >>> print(type(h)) <class '__main__.HelloWorld'>
那麼要建立一個class對象,type()
函數須要依次傳入3個參數:code
helloworld
上。經過type()
函數建立的類和直接寫class是徹底同樣的,由於Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,而後調用type()
函數建立出class。對象
正常狀況下,咱們都用class Xxx...
來定義類,可是,type()
函數也容許咱們動態建立出類來,也就是說,動態語言自己支持運行期動態建立類,這和靜態語言有很是大的不一樣,要在靜態語言運行期建立類,必須構造源代碼字符串再調用編譯器,或者藉助一些工具生成字節碼實現,本質上都是動態編譯,會很是複雜。
除了使用type()
動態建立類之外,要控制類的建立行爲,還可使用metaclass。
metaclass,直譯爲元類,簡單的解釋就是:
當咱們定義了類之後,就能夠根據這個類建立出實例,因此:先定義類,而後建立實例。
可是若是咱們想建立出類呢?那就必須根據metaclass建立出類,因此:先定義metaclass,而後建立類。
因此,metaclass容許你建立類或者修改類。換句話說,你能夠把類當作是metaclass建立出來的「實例」。
metaclass是Python面向對象裏最難理解,也是最難使用的魔術代碼。正常狀況下,你不會碰到須要使用metaclass的狀況,因此,如下內容看不懂也不要緊,由於基本上你不會用到。
咱們先看一個簡單的例子,這個metaclass能夠給咱們自定義的MyList增長一個add
方法:
class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs) class MyList(list, metaclass=ListMetaclass): pass
下面是運行結果,測試一下MyList
是否能夠調用add()
方法:
>>> L = MyList() >>> L.add(1) >> L [1]
經過這個例子咱們能夠看到,自定義咱們的MyList分兩步:
1. 建立Metaclass,用來建立/修改類
2. 建立實際的MyList Class
首先咱們來看第一步,建立Metaclass:
class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs)
ListMetaclass
,按照默認習慣,metaclass的類名老是以Metaclass結尾,以便清楚地表示這是一個metaclassetaclass是類的模板,因此必須從`type`類型派生:
選擇__new__函數做爲實現"修改類"的函數:
class MyList(list, metaclass=ListMetaclass): pass
有了ListMetaclass,下一個問題是如何使用ListMetaclass?
首先咱們須要先談一談Python建立class的機制:
當建立class的時候,python會先檢查當前類中有沒有__metaclass__,若是有,就用此方法建立對象;若是沒有,則會一級一級的檢查父類中有沒有__metaclass__,用來建立對象。建立的這個「對象」,就是當前的這個類。若是當前類和父類都沒有,則會在當前package中尋找__metaclass__方法,若是尚未,則會調用本身隱藏的的type函數來建立對象。
值得注意的是,若是咱們在作類的定義時,在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__函數。
所以,咱們在定義類的時候還要指示使用ListMetaclass來定製類(即在MyList class定義時,在class聲明處傳入關鍵字參數metaclass=ListMetaclass):咱們傳入關鍵字參數
metaclass後
,python會在當前class裏建立屬性__metaclass__,所以它指示Python解釋器在建立MyList
時,要經過ListMetaclass.__new__()
來建立,在ListMetaclass.__new__()中,咱們能夠修改類的定義,好比,加上新的方法,而後,返回修改後的定義。
Ok,下面測試一下MyList
是否能夠調用add()
方法:
>>> L = MyList() >>> L.add(1) >> L [1]
而普通的list
沒有add()
方法:
>>> L2 = list() >>> L2.add(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute 'add'
動態修改有什麼意義?直接在MyList
定義中寫上add()
方法不是更簡單嗎?正常狀況下,確實應該直接寫,經過metaclass修改純屬變態。
可是,總會遇到須要經過metaclass修改類定義的。ORM就是一個典型的例子。
ORM全稱「Object Relational Mapping」,即對象-關係映射,就是把關係數據庫的一行映射爲一個對象,也就是一個類對應一個表,這樣,寫代碼更簡單,不用直接操做SQL語句。
我將會在下一次文章中詳細講講ORM是怎麼工做的。