理解Python中的元類(metaclass)

理解Python中的元類(metaclass)

這篇文章基本上是What are metaclasses in Python?最高贊答案的翻譯,同時我簡化了一點東西,加了點自己的demo。可以直接去看原文。

類也是對象

在理解元類之前,你需要先掌握Python中的類。Python中類的概念借鑑於Smalltalk,這顯得有些奇特。在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在Python中這一點仍然成立:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是,Python中的類還遠不止如此。類同樣也是一種對象。是的,沒錯,就是對象。只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個對象.

下面這段代碼:

class ObjectCreator(object):
      pass

將在內存中創建一個對象,名字就是ObjectCreator。這個對象(類)自身擁有創建對象(類實例)的能力,而這就是爲什麼它是一個類的原因。但是,它的本質仍然是一個對象,於是你可以對它做如下的操作:

  1. 你可以將它賦值給一個變量
  2. 你可以拷貝它
  3. 你可以爲它增加屬性
  4. 你可以將它作爲函數參數進行傳遞

下面是示例:

>>> print ObjectCreator     # 你可以打印一個類,因爲它其實也是一個對象
<class '__main__.ObjectCreator'>
>>> def echo(o):
…       print o
…
>>> echo(ObjectCreator)                 # 你可以將類做爲參數傳給函數
<class '__main__.ObjectCreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #  你可以爲類增加屬性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可以將類賦值給一個變量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>

動態地創建類

因爲類也是對象,你可以在運行時動態的創建它們,就像其他任何對象一樣。首先,你可以在函數中創建類,使用class關鍵字即可

>>> def choose_class(name):
…       if name == 'foo':
…           class Foo(object):
…               pass
…           return Foo     # 返回的是類,不是類的實例
…       else:
…           class Bar(object):
…               pass
…           return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass              # 函數返回的是類,不是類的實例
<class '__main__'.Foo>
>>> print MyClass()            # 你可以通過這個類創建類實例,也就是對象
<__main__.Foo object at 0x89c6d4c>

但這還不夠動態,因爲你仍然需要自己編寫整個類的代碼。由於類也是對象,所以它們必須是通過什麼東西來生成的纔對。

type能動態的創建類,type可以接受一個類的描述作爲參數,然後返回一個類。

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

比如下面的代碼:

>>> MyShinyClass = type('MyShinyClass', (), {})  # 返回一個類對象
>>> print MyShinyClass
<class '__main__.MyShinyClass'>
>>> print MyShinyClass()  #  創建一個該類的實例
<__main__.MyShinyClass object at 0x8997cec>

type 接受一個字典來爲類定義屬性,因此:

>>> class Foo(object):
…       bar = True

等同於:

Foo = type('Foo', (), {'bar':True})

在Python中,類也是對象,你可以動態的創建類。這就是當你使用關鍵字class時Python在幕後做的事情,而這就是通過元類來實現的。

元類

元類就是創建類這種對象的東西。如果你喜歡的話,可以把元類稱爲“類工廠”。type就是Python的內建元類,當然了,你也可以創建自己的元類。

元類本身而言,它們其實是很簡單的:

  1. 攔截類的創建

  2. 修改類

  3. 返回修改之後的類

自定義元類

元類的主要目的就是爲了當創建類時能夠自動地改變類。

Python 3中創建元類的語法:

class NothingMetaclass(type):
    def __new__(mcs, name, bases, namespace):
         # 什麼都沒做,你可以在這裏做點什麼
        return type.__new__(mcs, name, bases, namespace)

class Foo(object, metaclass=NothingMetaclass):
    pass

demo1

  1. new是一個靜態方法,而init是一個實例方法.
  2. new方法會返回一個創建的實例,而init什麼都不返回.
  3. 只有在new返回一個cls的實例時後面的init才能被調用.
  4. 當創建一個新實例時調用new,初始化一個實例時用init.

看一個例子

# 請記住,'type'實際上是一個類,就像'str'和'int'一樣
# 所以,你可以從type繼承
class MetaA(type):
    # __new__ 是在__init__之前被調用的特殊方法
    # __new__是用來創建對象並返回之的方法
    # 而__init__只是用來將傳入的參數初始化給對象
    # 你很少用到__new__,除非你希望能夠控制對象的創建
    # 這裏,創建的對象是類,我們希望能夠自定義它,所以我們這裏改寫__new__
    # 如果你希望的話,你也可以在__init__中做些事情
    # 還有一些高級的用法會涉及到改寫__call__特殊方法,但是我們這裏不用
    def __new__(cls, name, bases, dct):
        print('MetaA.__new__')
        # 這種方式不會調用__init__方法
        # return type(name, bases, dct)
        # 這種方式會調用__init__
        return type.__new__(cls, name, bases, dct)

    def __init__(cls, name, bases, dct):
        print('MetaA.__init__')


class A(object, metaclass=MetaA):
    pass


print(A())

demo2

class ListMetaclass(type):

    # 元類會自動將你通常傳給‘type’的參數作爲自己的參數傳入
    # mcs表示元類
    # name表示創建類的類名(在這裏創建類就是繼承Model類的子類User)
    # bases表示創建類繼承的所有父類
    # namespace表示創建類的所有屬性和方法(以鍵值對的字典的形式)
    def __new__(mcs, name, bases, namespace):
        namespace['add'] = lambda self, value: self.append(value)
        return type.__new__(mcs, name, bases, namespace)


# 通過metaclass,給該類動態添加了add方法
class MyList(list, metaclass=ListMetaclass):
    pass


l = MyList()
l.add(1)
print(l)

究竟爲什麼要使用元類?

現在回到我們的大主題上來,究竟是爲什麼你會去使用這樣一種容易出錯且晦澀的特性?好吧,一般來說,你根本就用不上它:

“元類就是深度的魔法,99%的用戶應該根本不必爲此操心。如果你想搞清楚究竟是否需要用到元類,那麼你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什麼,而且根本不需要解釋爲什麼要用元類。” —— Python界的領袖 Tim Peters

元類的主要用途是創建API。一個典型的例子是Django ORM。它允許你像這樣定義

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

然後可以通過簡單點API操作數據庫。其背後的魔法就是定義了元類,並且使用了一些魔法能夠將你剛剛定義的簡單的Person類轉變成對數據庫的一個複雜hook。Django框架將這些看起來很複雜的東西通過暴露出一個簡單的使用元類的API將其化簡,通過這個API重新創建代碼,在背後完成真正的工作。

一個簡單的orm demo

class Field:
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)


class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')


class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')


class ModelMetaclass(type):
    def __new__(mcs, name, bases, attrs):
        if name == 'Model':
            return type.__new__(mcs, name, bases, attrs)
        print("Found Model: %s" % name)
        mapping = dict()
        fields = list()
        # 將屬性保存到mapping中
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping : %s ==> %s' % (k, v))
                mapping[k] = v
                fields.append(k)
        # 將Model中的Field刪除
        for k in mapping.keys():
            attrs.pop(k)

        attrs['__fields__'] = list(map(lambda f: '`%s`' % f, fields))
        attrs['__mapping__'] = mapping
        attrs['__table__'] = name
        return type.__new__(mcs, name, bases, attrs)


class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        super(Model, self).__init__(kwargs)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []

        for k, v in self.__mapping__.items():
            print("%s------%s" % (k, v))
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))

        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(self.__fields__), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))


class User(Model):
    # 定義類的屬性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')


u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

結語

Python中的一切都是對象,它們要麼是類的實例,要麼是元類的實例,除了type。type實際上是它自己的元類,在純Python環境中這可不是你能夠做到的,這是通過在實現層面耍一些小手段做到的。其次,元類是很複雜的。對於非常簡單的類,你可能不希望通過使用元類來對類做修改。你可以通過其他兩種技術來修改類:

1)Monkey patching

  1. 類裝飾器

當你需要動態修改類時,99%的時間裏你最好使用上面這兩種技術。當然了,其實在99%的時間裏你根本就不需要動態修改類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章