之前學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