天天看點

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

  簡單的講,元類建立了Python中所有的對象。

  我們說Python是一種動态語言,而動态語言和靜态語言最大的不同,就是函數和類不是編譯時定義的,而是運作時動态建立的。

  比方說我們要定義一個的class,就寫一個子產品:

  class HelloWorld(object):

  def helloworld(self):

  print('Hello World!')

  當Python解釋器載入子產品時,就會依次執行該子產品的所有語句,執行結果就是動态建立出一個的class對象,測試如下:

  >>> from helloworld import HelloWorld

  >>> h= HelloWorld()

  >>> h.helloworld()

  Hello, world!

  >>> print(type(HelloWorld))

  >>> print(type(h))

  函數用來檢視一個類型或變量的類型,是一個class,它的類型就是,而是一個執行個體,它的類型就是class 。

  我們說class的定義是運作時動态建立的,而建立class的方法就是使用函數。

  定義:type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))

  函數既可以傳回一個對象的類型,又可以建立出新的類型,比如,我們可以通過函數建立出類,而無需通過的定義:

  >>> 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))

  >>> print(type(h))

  那麼要建立一個class對象,函數需要依次傳入3個參數:

  class的名稱;

  繼承的父類集合,注意Python支援多重繼承,如果隻有一個父類,别忘了tuple的單元素寫法;

  class的方法名稱與函數綁定,這裡我們把函數helloworld_outside綁定到方法名上。

  通過函數建立的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的文法,然後調用函數建立出class。

  正常情況下,我們都用來定義類,但是,函數也允許我們動态建立出類來,也就是說,動态語言本身支援運作期動态建立類,這和靜态語言有非常大的不同,要在靜态語言運作期建立類,必須構造源代碼字元串再調用編譯器,或者借助一些工具生成位元組碼實作,本質上都是動态編譯,會非常複雜。

  除了使用動态建立類以外,要控制類的建立行為,還可以使用metaclass。

  metaclass,直譯為元類,簡單的解釋就是:

  當我們定義了類以後,就可以根據這個類建立出執行個體,是以:先定義類,然後建立執行個體。

  但是如果我們想建立出類呢?那就必須根據metaclass建立出類,是以:先定義metaclass,然後建立類。

  是以,metaclass允許你建立類或者修改類。換句話說,你可以把類看成是metaclass建立出來的“執行個體”。

  metaclass是Python面向對象裡最難了解,也是最難使用的魔術代碼。正常情況下,你不會碰到需要使用metaclass的情況,是以,以下内容看不懂也沒關系,因為基本上你不會用到。

  我們先看一個簡單的例子,這個metaclass可以給我們自定義的MyList增加一個方法:

  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

  下面是運作結果,測試一下是否可以調用方法:

  >>> 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)

  類名的定義:定義,按照預設習慣,metaclass的類名總是以Metaclass結尾,以便清楚地表示這是一個metaclass

  Metaclass的父類:M

  選擇__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聲明處傳入關鍵字參數,python會在目前class裡建立屬性__metaclass__,是以它訓示Python解釋器在建立時,要通過來建立,在ListMetaclass.__new__()中,我們可以修改類的定義,比如,加上新的方法,然後,傳回修改後的定義。

  Ok,下面測試一下是否可以調用方法:

  >>> L= MyList()

  >>> L.add(1)

  >> L

  [1]

  而普通的沒有方法:

  >>> L2= list()

  >>> L2.add(1)

  Traceback (most recent call last):

  File "

", line 1, in

  AttributeError: 'list' object has no attribute 'add'

  動态修改有什麼意義?直接在定義中寫上方法不是更簡單嗎?正常情況下,确實應該直接寫,通過metaclass修改純屬變态。