python中的元類Metaclass

xiaoming3526發表於2016-11-28

python中的元類Metaclass

理解元類之前需要學習的知識

如果說讓我們建立一個類,最先想到的肯定是用class建立,當我們使用class建立類的時候,Python直譯器自動建立這個物件,但是python同樣也提供了手動處理的方法來建立類,這就是用python的自建函式type()

我們所熟知的type()函式的作用是返回一個引數的型別,但是實際上,它也有一種完全不同的能力,即接受一個類的一些描述作為引數,然後返回一個類。

type()函式的語法是這樣的:

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

舉個例子:

class ReedSun(ShuaiGe):
    shuai = True
    def test(x):
        return x+2
# 就等價於
type("ReedSun", (ShuaiGe,), {"shuai":True, "test":lambda x: x+2})
# (屬性和方法本質上都是方法)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在python中,類也是物件,當我們使用class關鍵詞建立一個類的時候,Python直譯器僅僅是掃描一下class定義的語法,然後呼叫type()函式建立出class。

元類是什麼

元類是什麼?元類實際上就是用來建立類的東西。為了幫助我們理解,我們可以這樣想,我們建立類就是為了建立類的例項,同樣的,我們建立元類就是為了建立類。元類就是類(例項)的類,就像下面這樣

Metaclass() = class
class() = object  # object==>例項
  • 1
  • 2
  • 1
  • 2

理解了什麼是元類,我們再來看一看type()函式。

其實type就是一個元類,type就是我們用來建立所有的類的元類。(如果我們要建立自己定義的元類的話,也要從type中繼承)

元類的工作原理

我們來看一下下面這個例子

class ReedSunMetaclass(type):
    pass

class Foo(object, metaclass = ReedSunMetaclass): 
    pass

class Bar(Foo):
    pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 首先,我們建立了一個元類ReedSunMetaclass(注意!按照預設習慣,元類的類名總是以Metaclass結尾,以便清楚地表示這是一個元類)

  2. 然後,我們又用元類ReedSunMetaclass建立了一個Foo類,(同時,Foo類的屬性__metaclass__就變成了ReedSunMetaclass)

  3. 最後,我們建立了一個子類Bar繼承自Foo

我們來試著理解一下在python內部是怎麼執行這幾個步驟的:

  • 對於父類Foo,Python會在類的定義中尋找__metaclass__屬性,如果找到了,Python就會用它來建立類Foo,如果沒有找到,就會用內建的type來建立這個類。很顯然,它找到了。

  • 對於子類Bar, python會先在子類中尋找__metaclass__屬性,如果找到了,Python就會用它來建立類Bar,如果沒有找到,就再從父類中尋找,直到type。顯然,它在父類中找到了。

我們可以看到使用元類的一個好處了,即他可以讓子類隱式的繼承一些東西。

自定義元類

元類的主要目的就是為了當建立類時能夠自動地改變類。建立類我們需要定義__new__()函式,__new__ 是在__init__之前被呼叫的特殊方法,是用來建立物件並返回之的方法。我們舉個例子來說明定義自定義元類的方法。

__new__()方法接收到的引數依次是: 
1. 當前準備建立的類的物件; 
2. 類的名字; 
3. 類繼承的父類集合; 
4. 類的方法集合。

class ReedSunMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 新增一個屬性
        attrs['哈哈哈'] = True
        return type.__new__(cls, name, bases, attrs)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我們用一個實際例子來說明元類的用法

ORM就是一個典型的使用元類的例子。ORM全稱“Object Relational Mapping”,即物件-關係對映,就是把關係資料庫的一行對映為一個物件,也就是一個類對應一個表,這樣,寫程式碼更簡單,不用直接操作SQL語句。下面我就用這個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中,這樣可以使用一些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類來對應資料庫的表User
class User(Model):
    # 定義類的屬性到列的對映
    id = IntegerField("id")
    name = StringField("username")
    email = StringField("email")
    password = StringField("password")


# 建立一個例項
u = User(id=12345, name="ReedSun", email="sunhongzhao@foxmail.com", password="nicaicai")
u.save()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

參考資料

  1. 使用元類-廖雪峰的官方網站
  2. 深刻理解python中的元類

相關文章