[譯]什麼是元類metaclass?

原文地址: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屬性

當咱們建立類的時候,能夠給它添加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%的狀況下,你根本不須要修改一個類。

相關文章
相關標籤/搜索