python中的metaclass就是元類,當我們定義了類以後,就可以根據這個類創建出實例,所以:先定義類,然後創建實例。
但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然後創建類。
連接起來就是:先定義metaclass,就可以創建類,最後創建實例。
所以,metaclass允許你創建類或者修改類。換句話說,你可以把類看成是metaclass創建出來的“實例”。
我們先看一個簡單的例子,這個metaclass可以給我們自定義的MyList增加一個add方法:
定義ListMetaclass,按照默認習慣,metaclass的類名總是以Metaclass結尾,以便清楚地表示這是一個metaclass:
# metaclass是類的模板,所以必須從`type`類型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
#有了ListMetaclass,我們在定義類的時候還要指示使用ListMetaclass來定製類,傳入關鍵字參數metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
當我們傳入關鍵字參數metaclass時,魔術就生效了,它指示Python解釋器在創建MyList時,要通過ListMetaclass.new()來創建,在此,我們可以修改類的定義,比如,加上新的方法,然後,返回修改後的定義。
new()方法接收到的參數依次是:
- 當前準備創建的類的對象;
- 類的名字;
- 類繼承的父類集合
- 類的方法集合
測試一下MyList是否可以調用add()方法:
>>> L = MyList()
>>> L.add(1)
>> L
[1]
而普通的list沒有add()方法:
>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'
下面來理解一下一個簡單的ORM框架
#Field規定了數據庫中每個字段的名稱和類型
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
#StringField中定義了具體的String的類型
class StringField(Field):
def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')
#IntegerField中定義了具體的Integer的類型
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')
#爲Model的元類
class ModelMetaclass(type):
#arrts爲具體實現類的屬性,這裏的做法是取除實現類中類型爲Field的屬性,將其映射爲實現類中的__mappings__屬性,並取類名將其映射爲實現類中的__table__屬性
def __new__(cls, name, bases, attrs):
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 mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings # 保存屬性和列的映射關係
attrs['__table__'] = name # 假設表名和類名一致
return type.__new__(cls, name, bases, attrs)
#Model繼承元類ModelMetaclass,當一個類繼承這個類時,會自動去執行這個ModelMetaclass中的__new__方法,在這裏實現了遍歷由元類中添加的__mappings__屬性,生成SQL,並插入__table__中。
#其接受命名參數,並從__mappings__中拿到key去屬性中獲取值
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
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.__mappings__.items():
fields.append(v.name)
params.append('?')
#通過__mappings__去自身拿取key爲其中設置的鍵,也就是我們在User中定義的4個屬性的名稱
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))
#該類繼承Model
class User(Model):
# 定義類的屬性到列的映射,用於元類獲取__mappings__
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()
輸出的結果如下:
Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]
參考鏈接:廖雪峯的官方網站