python3 metaclass--創建類的過程分析

之前學python的時候就看見過metaclass的文章,沒看懂,那篇博客後面說到,metaclass是python的黑魔法99% 不會用到。於是果斷放棄。

不過最近看flask-WTForm組建的源碼,一開始就是metaclass。沒辦法,硬着頭皮重新看metaclass。基本瞭解,現在總結如下:

一、metaclass幹嘛的?

metaclass是指定類由誰創建。能夠定製類的創建過程

指定類由誰創建的???開什麼玩笑,類不是由'我'創建的嗎????

python中一切皆對象,類也是對象,類是由type類創建。

我們寫下如下代碼時:

class Foo(object):
    pass

實際上,解釋器將其解釋爲:

Foo = type('Foo', (object,), {})

type()的三個參數:'Foo':類名; (object, ): 類的繼承關係,用元組表示; {}: 類的字段,方法。

以上是類的默認創建方法。由type創建。python也給我們提供了自定義類的創建的方法,即metaclass。type也是類,它可以創建類,因此我們叫它元類,不要過分糾結這是什麼鬼,知道type類可以創建類就行。

自定義類的創建過程,那就得寫一個像type一樣可以創建類的類,那簡單,繼承就可以辦到。

方式一:

class MyType(type):
    
    def __new__(cls, *args, **kwargs):
        print('MyType __new__')
        return super().__new__(cls, *args, **kwargs)
    
    def __init__(cls, *args, **kwargs):
        print('MyTpye __init__')
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('MyTpye __call__')
        super().__call__(cls, *args, **kwargs)


class Foo(metaclass=MyType):
    pass

這樣,解釋器解釋到class Foo(...)的時候,就會轉換爲:

Foo = MyType('Foo', (object,), {})

方式二:

class MyType(type):
    def __new__(cls, *args, **kwargs):
        print('MyType __new__')
        return super().__new__(cls, *args, **kwargs)
    
    def __init__(cls, *args, **kwargs):
        print('MyTpye __init__')
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('MyTpye __call__')
        super().__call__(cls, *args, **kwargs)


def with_meta(meta, Base):
    return meta('Foo', (Base, ), {})


class Foo(with_meta(MyType, object)):
    pass

這樣解釋的時候,與方式一的一樣。

二、創建類與類實例化時執行過程是怎樣的?

解釋器解釋到class的定義語句時,會先在class中尋找是否指定自定義的'MyType', 沒有再往父類找是否指定,沒有再在本模塊中找,是否本模塊指定了統一的'MyType', 若均沒有,則用默認的type創建。

解釋到class Foo(...)時,會調用'MyType'的__new__, __init__方法。生成類。

解釋到f = Foo() ,類的實例化時,會調用'MyType'的__call__方法,而'type'的__call__方法又會去調用Foo的__new__, __init__實例化類對象。

下面用一個實際的例子來說明元類的使用方法

三、ORM的元類實例:

#ORM:object relational mapping 對象-關係映射
#把關係數據庫的一行映射爲一個對象,也就是一個類對應一個表
#ORM框架所有的類只能動態定義


# 定義Field(定義域:元類遇到Field的方法或屬性時即進行修改)
class Field(object):

    def __init__(self, name, column_type):  # column==>列類型
        self.name = name
        self.column_type = column_type

    # 當用print打印輸出的時候,python會調用他的str方法
    # 在這裏是輸出<類的名字,實例的name參數(定義實例時輸入)>
    # 在ModelMetaclass中會用到
    def __str__(self):
        return "<%s:%s>" % (self.__class__.__name__, self. name)  # __class__獲取對象的類,__name__取得類名



# 進一步定義各種類型的Field
class StringField(Field):

    def __init__(self, name):
        # super(type[, object-or-type])  返回type的父類對象
        # super().__init()的作用是調用父類的init函數
        # varchar(100)和bigint都是sql中的一些數據類型
        super(StringField, self).__init__(name, "varchar(100)")  

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, "bigint")


# 編寫ModelMetaclass
class ModelMetaclass(type):

    # __new__方法接受的參數依次是:
    # 1.當前準備創建的類的對象(cls)
    # 2.類的名字(name)
    # 3.類繼承的父類集合(bases)
    # 4.類的方法集合(attrs)
    def __new__(cls, name, bases, attrs):
        # 如果說新創建的類的名字是Model,那直接返回不做修改
        if name == "Model":
            return type.__new__(cls, name, bases, attrs)
        print("Found model:%s" % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print("Found mappings:%s ==> %s" % (k, v))  # 找到映射, 這裏用到上面的__str__
                mappings[k] = v
            # 結合之前,即把之前在方法集合中的零散的映射刪除,
            # 把它們從方法集合中挑出,組成一個大方法__mappings__
            # 把__mappings__添加到方法集合attrs中
        for k in mappings.keys():
                attrs.pop(k)
        attrs["__mappings__"] = mappings
        attrs["__table__"] = name # 添加表名,假設表名與類名一致
        return type.__new__(cls, name, bases, attrs)


# 編寫Model基類繼承自dict中,這樣可以使用一些的方法
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self,  **kw):
        # 調用父類,即dict的初始化方法
        super(Model, self).__init__(**kw)

    # 讓獲取key的值不僅僅可以d[k],也可以d.k
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    # 允許動態設置key的值,不僅僅可以d[k],也可以d.k
    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        # 在所有映射中迭代
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append("?")
            args.append(getattr(self, k, None))
        sql = "insert into %s (%s) values (%s)" % (self.__table__, ",".join(fields), ",".join(params))
        print("SQL: %s" % sql)
        print("ARGS: %s" % str(args))

# 這樣一個簡單的ORM就寫完了# 下面實際操作一下,先定義個User類來對應數據庫的表Userclass User(Model): # 定義類的屬性到列的映射 id = IntegerField("id") name = StringField("username") email = StringField("email") password = StringField("password")# 創建一個實例u = User(id=12345, name="ReedSun", email="[email protected]", password="nicaicai")u.save()

上面的代碼按功能可以分爲三部分:

1. 定義屬性

class Field(object):
    pass

# 進一步定義各種類型的Field
class StringField(Field):
    pass
    
class IntegerField(Field):
    pass

2.  操作屬性:

# 編寫ModelMetaclass
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
      pass

# 編寫Model基類繼承自dict中,這樣可以使用一些的方法
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self,  **kw):
        # 調用父類,即dict的初始化方法
        super(Model, self).__init__(**kw)

    # 讓獲取key的值不僅僅可以d[k],也可以d.k
    def __getattr__(self, key):
       pass
    # 允許動態設置key的值,不僅僅可以d[k],也可以d.k
    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
      pass

# 下面實際操作一下,先定義個User類來對應數據庫的表User
class User(Model):
    # 定義類的屬性到列的映射
    id = IntegerField("id")
    name = StringField("username")
    email = StringField("email")
    password = StringField("password")

3. 統管屬性:

u = User(id=12345, name="ReedSun", email="[email protected]", password="nicaicai")
u.save()

代碼執行流程:

解釋器執行到 class Model 時,知道其指定了由 ModelMetaclass創建。因此,解釋(不是執行)完類內定義的方法後,跳進 ModelMetaclass 的 __new__(cls, name, base, attrs), ModelMetaclass 沒有__init__,執行type的__init__。(由於是生成Model, __new__(cls, name, base, attrs)中的clc爲Model)  至此Model類正式創建完畢。

解釋器執行到class User 時,與上面一樣, 解釋完類內定義的 id, name, email , password 字段後(這些字段均爲...Field對象),跳進ModelMetaclass 的 __new__(cls, name, base, attrs),此時 cls 爲 User, name 爲 'User',  base 爲 Model, attrs 爲類似{'id': IntegerField("id"), 'name':StringField("username"),.........}的字典。

建議最好自己設置斷點,調式執行看看,就會明白執行流程是怎麼樣的。

四、定製類生成的好處:

當然是非常多的,最容易理解的,單例模式便可以用 metaclass來實現。其他的好處,如ORM,我還說不上來,等弄清楚再補充。

參考與推薦博文:

https://blog.csdn.net/weixin_35955795/article/details/52985170

http://blog.jobbole.com/21351/

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000

https://www.cnblogs.com/xybaby/p/7927407.html

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