原文地址:what is metaclass in Python?
個人簡書地址::nummypython
在理解元類以前,須要先掌握Python中的類,Python中類的概念與SmallTalk中類的概念類似。
在大多數語言中,類是用來描述如何建立對象的代碼段,這在Python中也是成立的:數據庫
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
Python中,類其實也是對象。當咱們使用關鍵字class的時候,Python會執行這段代碼,而後生成一個對象。下面的代碼在內存中建立一個對象ObjectCreator
:編程
>>> class ObjectCreator(object): ... pass ...
當一個對象具備建立對象的能力時,就稱該對象爲類。函數
因此類本質上仍是一個對象,所以它具備如下屬性:this
能夠將它賦值給其它變量code
能夠對它進行復制對象
能夠給它添加屬性繼承
能夠將它傳遞給函數做爲參數接口
例如:ip
>>> print(ObjectCreator) # you can print a class because it's an object <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
既然類就是對象,那咱們就能夠像建立其餘對象同樣動態建立類。
首先,在函數中使用class建立一個類:
>>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # return the class, not an instance ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print(MyClass) # the function returns a class, not an instance <class '__main__.Foo'> >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
可是上面的例子也稱不上是徹底動態的建立類,由於咱們還須要在其中編寫整個類的代碼。
既然類就是對象,那麼它們確定是經過某個東西來建立的。當使用class關鍵字的時候,Python會自動建立類,Python也提供了方法讓咱們手動來建立類。
還記得type()函數嗎?這個函數能夠獲取對象的類型。
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>
type還有另一個功能,那就是建立類。type使用類的相關描述做爲參數,而後返回一個類。
type建立類的語法以下:
type(類名,基類元組(能夠爲空,用於繼承), 包含屬性或函數的字典)
例如:
>>> class MyShinyClass(object): ... pass
上面的類可使用下面的方法手動建立:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass) <class '__main__.MyShinyClass'> >>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
type也接收一個字典參數來定義類中的屬性:
>>> class Foo(object): ... bar = True
等價於
>>> Foo = type('Foo', (), {'bar':True})
經過type
建立的類使用方式跟普通類同樣:
>>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
固然也能夠繼承:
>>> class FooChild(Foo): ... pass
等價於:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar is inherited from Foo True
最後,咱們可能還想給類添加方法,能夠先定義一個函數,而後將它以屬性的方式賦予給類。
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
並且,咱們還能夠在動態建立類以後,給類添加更多的方法和屬性:
>>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True
一般,咱們定義類來建立對象,可是如今咱們知道類也是對象。那麼是經過什麼來建立類呢?答案就是元類。你能夠想象關係以下:
MyClass = MetaClass() MyObject = MyClass()
你已經知道使用type
能夠建立類:
MyClass = type('MyClass', (), {})
那是由於type函數實際上就是一個元類,Python使用type做爲元類來建立全部的類。
經過檢查class屬性,咱們能夠知道,其實Python中任何數據類型都是對象,包括整型、字符串、函數以及類,它們都是對象。它們都是從類中建立的。
>>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
那麼__class__
的__class__
是什麼呢?
>>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
因此類其實就是經過元類來建立的,你能夠將元類稱之爲類工廠。
type是內置的元類,Python默認使用它來建立類。固然,咱們也能夠定義屬於咱們本身的元類。
當咱們建立類的時候,能夠給它添加metaclass屬性:
class Foo(object): __metaclass__ = something... [...]
若是咱們定義了metaclass屬性,Python就會使用這個元類來建立類Foo。
注意,編譯器首先讀取class Foo(object)
,這時並不會在內存中建立Foo類。Python會繼續查找類定義中的__meatclass__
,若是找到了,就使用它來建立類Foo,若是沒有找到,就使用type來建立類。
因此對於如下代碼:
class Foo(Bar): pass
Python工做流程以下:
首先檢查Foo
中是否具備屬性__metaclass__
?
若是找到,就使用__metaclass__
定義的元類在內存中建立一個類對象。
若是在類定義中沒有找到這個屬性,就在模塊級別中進行查找。
若是仍是沒有找到,就會使用父類Bar中的元類來建立類。
注意:類中的__metaclass__
屬性不會被子類繼承,可是父類中的__class__
會被繼承。
元類的主要做用是在建立類的時候自動改變類。
例如,想要實現模塊中全部的類屬性都是大寫格式。能夠定義模塊級別的__metaclass__
來實現。
這樣模塊中全部的類都是經過這個元類來建立的。
def upper_attr(future_class_name, future_class_parents, future_class_attr): """ 返回一個類,該類的全部屬性名的都爲大寫 """ # 將不是__開頭的屬性名轉爲大寫字母 uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # 使用type建立類 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # 定義模塊級別的元類,這樣模塊中全部類都會使用該元類建立 class Foo(): # 注意,新式類不支持模塊級別的元類,可是能夠在類中定義__metaclass__ bar = 'bip' print(hasattr(Foo, 'bar')) # 輸出: False print(hasattr(Foo, 'BAR')) # 輸出: True f = Foo() print(f.BAR) # Out: 'bip'
也能夠將metaclass
定義爲一個真正的類:
# 記住type仍是一個類,因此能夠繼承它 class UpperAttrMetaclass(type): # __new__ 會在__init__以前調用,它會建立並返回一個實例 # 而__init__僅用於初始化,進行一些參數的配置 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
可是上面的作法並不符合OOP的思想,由於它直接調用了type方法,實際上能夠調用type的__new__
方法。
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # 調用type.__new__方法 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能注意到參數upperattr_metaclass
, 它表明要實例化的類。固然,我這裏取這麼個複雜的名字主要是爲了明確它的含義。可是,就像self
參數同樣,全部參數都有其習慣性命名。因此生產環境下的metaclass
定義以下:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type.__new__(cls, clsname, bases, uppercase_attr)
更好的方式是使用super
方法,以便減輕這種繼承關係。
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
元類實際上作了如下三方面的工做:
干涉建立類的過程
修改類
返回修改以後的類
理由以下:
目的更明確,當你閱讀UpperAttrMetaclass(type)
的時候,你知道它用來作什麼。
可使用面向對象編程,元類能夠繼承自其它元類,還能夠覆蓋父類方法。
能夠更好的組織代碼結構。元類一般用於處理比較複雜的狀況。
能夠爲__new__
、__init__
和__call__
編寫鉤子,爲後續開發者提供便利。
如今,終極問題來了,爲何要使用元類這種模糊且容易出錯的功能?
通常狀況下,咱們並不會使用元類,99%的開發者並不會用到元類,因此通常不用考慮這個問題。
元類主用用於建立API,一個典型的例子就是Django的ORM。
它讓咱們能夠這樣定義一個類:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
運行下面的代碼:
guy = Person(name='bob', age='35') print(guy.age)
返回的結果是int
類型而不是IntegerField
對象。這是由於models.Model
使用了元類,它會將Python中定義的字段轉換成數據庫中的字段。
經過使用元類,Django將複雜的接口轉換成簡單的接口。
首先,咱們知道了類其實就是能夠建立實例的對象。而類又是經過元類來建立的。
>>> class Foo(object): pass >>> id(Foo) 142630324
Python中全部數據類型都是對象,它們要麼是類的實例要麼是元類的實例。
除了type,它其實是自身的元類。這一點無法在Python中重現,由於它是在編譯階段實現的。
其次, 元類都是複雜的,對於通常的類是用不着的。可使用如下兩種技巧修改類:
monkey patch
類修飾器
當你須要修改類的時候,99%的狀況下可使用元類。可是99%的狀況下,你根本不須要修改一個類。