談談Python中元類Metaclass(一):什麼是元類

簡單的講,元類建立了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

  1. class的名稱;
  2. 繼承的父類集合,注意Python支持多重繼承,若是隻有一個父類,別忘了tuple的單元素寫法;
  3. class的方法名稱與函數綁定,這裏咱們把函數helloworld_outside綁定到方法名helloworld上。

經過type()函數建立的類和直接寫class是徹底同樣的,由於Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,而後調用type()函數建立出class。對象

正常狀況下,咱們都用class Xxx...來定義類,可是,type()函數也容許咱們動態建立出類來,也就是說,動態語言自己支持運行期動態建立類,這和靜態語言有很是大的不一樣,要在靜態語言運行期建立類,必須構造源代碼字符串再調用編譯器,或者藉助一些工具生成字節碼實現,本質上都是動態編譯,會很是複雜。

metaclass

除了使用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結尾,以便清楚地表示這是一個metaclass
  • Metaclass的父類:Metaclass是類的模板,因此必須從`type`類型派生:
  • 選擇__new__函數做爲實現"修改類"的函數
    • 函數__new__(cls, name,bases,attrs)中,"cls"相似於類中其餘函數的self參數,例如__init__(self),只不過self表明建立的對象,而cls表明類自己(__init__做爲實例初始化的函數,須要把實例自己做爲參數傳進去,這樣咱們才能保證被修改的是實例;同理,__new__函數須要把類自己做爲參數傳進去,才能保證被初始化的是當前類); name表明類的名稱;bases表明當前類的父類集合;attrs表明當前類的屬性,是狹義上屬性和方法的集合,能夠用字典dict的方式傳入
    • 對__new__的定義def __new__(cls, name,bases,attrs),實際上,「new」方法在Python中是真正的構造方法(建立並返回實例),經過這個方法能夠產生一個」cls」對應的實例對象因此說」new」方法必定要有返回,要把建立的實例對象返回回去。在此,咱們把對類的修改放到__new__方法中,而後返回修改事後的實例對象。另外,很簡單的道理,選擇type.__new__函數做爲return的值,是由於咱們的ListMetaclass繼承自type,所以應該返回class 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是怎麼工做的。

相關文章
相關標籤/搜索