猿类:它们与人类最接近,被称为是人类的“表兄弟” - - - 来自百度百科。
好了,进入正题
第一部分 - 数据类型Field类的定义
定义Field类,包含两个属性,分别是字段的名称name与类型column_type
class Field(object):
# 类Fiedl的构造函数有两个属性: name, column_type
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)
类StringField继承自类Field,同样有两个属性name与类型column_type
class StringField(Field):
# 此处仅属性name是强制属性
def __init__(self, name):
# 通过super()函数调用parent类的构造函数
# 其中name就直接传递给Field, column_type传递固定值'varchar(100)'
# 所以StringField('username').__dict__ == Field('username', 'varchar(100)').__dict__
super(StringField, self).__init__(name, 'varchar(100)')
同理类IntegerField继承自类Field,同样有两个属性name与类型column_type
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')
第二部分 - 元类ModelMetaclass的定义
class ModelMetaclass(type):
# 此处说明下__new__和__init__的区别:
# __new__是用来创造一个类对象的构造函数,而__init__是用来初始化一个实例对象的构造函数
# 类似于__init__,__new__接收的第一个参数cls(类对象)其实就是相当于__init__的self(实例对象)
# 在初始化__init__之前,类是通过__new__创建的,所以在__init__前一定有__new__来构造类cls,之后__init__才能初始化对象self
def __new__(cls, name, bases, attrs):
# 对于名称为Model的类不做其他操作,直接通过type()函数生成类对象Model
if name == 'Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
# 对于名称不是Model的类,如User类,通过下面代码过滤类对象User的属性
# 因为类属性以dict格式存在attrs中,所以是对dict格式的操作:
# 第一步,过滤出满足条件的属性
# 先新建一个空dict,如果对象的属性值是Field类的实例对象(如StringField('username')),则将这些属性放入dict格式的mappings变量中
mappings = dict()
for k, v in attrs.items():
# 下面判断属性的值是否是Field类格式,满足Field类格式形态如下:
# isinstance(StringField('username'), Field)
# 或者isinstance(Field('name', StringField('username')), Field)
# 注意此处是属性的值,不是属性,如:属性name的值为StringField('username')
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
# 将过滤出的dict数据写入mappings变量
mappings[k] = v
# 其实上面的这几行可以简化为一行 mappings = {k:v for k, v in attrs.items() if isinstance(v, Field)} 吧?
# 第二步,把上一步过滤出满足条件的属性从类对象User的属性dict中移除
for k in mappings.keys():
attrs.pop(k)
# 第三步,把dict格式的mappings变量添加到类对象User的属性__mapping__中
# 即__mapping__成为了类对象User的一个属性,该属性值为dict格式,内容为满足Field类格式的原类对象的属性值
attrs['__mappings__'] = mappings
# 到目前为止其实就是把实例对象User的一些属性(满足Field格式)移了个位置
# 下面再新建一个属性__table__,并且赋值为该类对象的名字,如User
attrs['__table__'] = name # 假设表名和类名一致
# 将修改后的类对象User的属性值返回给type().__new__
return type.__new__(cls, name, bases, attrs) # 也就是动态创建对象
其实定义好的元类ModelMetaclass,仍然是调用type.new(cls, name, bases, attrs)函数去构造类对象,可以试下如下代码
不同的是type.new()构造出的类的属性就在__dict__下,但是ModelMetaclass将属性移到__mappings__下了
type.__new__(type, 'Model', (dict, ), {'id': IntegerField('id')}).__dict__
ModelMetaclass('Table1', (object, ), {'name': StringField('username')}).__mappings__
第三部分 - 生成Model的实例对象
核心在于:在运行__init__来生成实例对象前,调用元函数ModelMetaclass来生成类对象,用这个类对象再去生成实例对象。 根据ModelMetaclass代码可以知道,当类名称为Model时,直接返回原始的type.new(cls, name, bases, attrs), 如:type.new(type, ‘Model’, (dict, ), Model(id=12345, name=‘Michael’))
此处对于super()函数的理解还是不清楚
class Model(dict, metaclass=ModelMetaclass):
# 在运行__init__来生成实例对象前,调用元函数ModelMetaclass来生成类对象,用这个类对象再去生成实例对象
# 根据ModelMetaclass代码可以知道,当类名称为Model时,直接返回原始的type.__new__(cls, name, bases, attrs)
# 如:type.__new__(type, 'Model', (dict, ), Model(id=12345, name='Michael'))
# 接下来定义从类对象生成实例对象的__init__函数
def __init__(self, **kw):
# 通过super调用父类初始化函数,__init__()动态函数无需self
# 此处先调用dict
# ModelMetaclass('User', (type, ), {'id': IntegerField('id')})
super(Model, self).__init__(**kw)
# 重新定义getattr函数,可以匹配dict格式输入的{属性:属性值}
# 如:Model(id = IntegerField('id')).__getattr__
def __getattr__(self, key):
'''
重载__getattr__, __setattr__方法使子类可以像正常的类使用
'''
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
# 个人觉得可以不定义__setattr__,没什么影响
def __setattr__(self, key, value):
self[key] = value
# 定义save方法,用于动态生成SQL
def save(self):
# 定义3个数组
fields = []
params = []
args = []
# 此处引入了之前ModelMetaclass元类定义的dict格式变量__mappings__
# 假设__mappings__ = {'name': StringField('username')},则'name'就是k, StringField('username')是v, 'username'是v.name
for k, v in self.__mappings__.items():
# 把__mappings__的v.name(即'username')写入fields变量
fields.append(v.name)
params.append('?')
# fields变量('username')对应的数值通过重新定义的__getattr__函数获取
# __mappings__中的k为{'name': StringField('username')}中的'name'
args.append(getattr(self, k, None))
# __table__在之前ModelMetaclass元类定义为类对象的名称,即'User'表
# 通过'sep'.join(seq)函数(','.join(['id', 'username']))生成表字段和字段值
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
第四部分 - 生成User实例对象
class User(Model):
# 因为User继承了Model,而Model继承了dict类型 # 所以dict(id=12345, name='Michael')直接转化为{'id': 12345, 'name': 'Michael'}格式 id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
test = str('abc')
# 使用
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
总结User实例对象的创建步骤:
-
User类的创建1: User类继承了Model类,而Model类由元类ModelMetaclass定义类的生成,所以User类对象首先通过ModelMetaclass的type.new()构造生成
-
User类的创建2: 在ModelMetaclass的type.new()构造函数中,name和bases已经确认,而属性attr是根据输入的属性内容,由__new__构造出
-
User实例的创建1:User类继承了Model类,所以User通过Model的构造函数__init__生成属性的时候会把输入的(id=12345, name=‘Michael’)转化为dict格式{‘id’: 12345, ‘name’: ‘Michael’}
-
User实例的创建2: User实例的生成中要按照元类ModelMetaclass的定义生成,即生成__mappings__和__Table__等属性
作者链接https://www.liaoxuefeng.com/user/1353866849288226
----------------------------分割----------------------------
hellojakes:补充一下楼主的解释,Model类中__init__()方法中的super()函数是用来调用dict类的init函数的!这行代码至关重要:
class Model(dict, metaclass=ModelMetaclass):
括号中的dict说明Model继承自dict类,换句话说,Model类就是Python的字典类!
所以才有了下文的初始化方法:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
这完全等价于我们创建字典的初始化方法:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
继续简化,就变成了这样:
u = {'id': 12345,
'name': 'Michael',
'email': 'test@orm.org',
'password': 'my-pwd'}
-----------------分割线-----------------
下面都是我自己写的了啊
优点:元类就是类祖宗的意思,类的创建创建过程是找到类定义class - > 继承的类 - > 元类(更底层),所以如果修改了元类,就从根上修改了类的创建过程。
解析:
第一部分:自定义了一个数据结构。
第二部分:实现了一个元类,这个元类实现的是对创建类的属性进行筛选,筛选出自己定义的数据类型,只要三个划线的,这就是这里元类的作用。
第三部分:实现了一个Model类,这个model写复杂了,加大了对元类的理解难度,其实这个Model就是字典dict的改写,同时增加了一个save函数,这就使得model既有dict功能,也有save函数功能。
- dict功能:由于User继承了Model,Model继承dict,所以按照继承的传递性,User类是一个字典类的孙子,拥有字典的一切特性。
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
c = dict(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
print(type(u))
print(type(c))
print(c == u)
print(c is u)
输出
<class '__main__.User'>
<class 'dict'>
True
False
从结果可以看出啊,这两个都是类,一个是字典类,一个是main下的User类,其实也是字典类。"=="就能说明返回的结果一样,但是is说明这两个不是一个,明白吗?好比一个人和他爷爷,爷爷秃头,孙子也秃头,表现得都是秃头,但不是一个秃头,哈哈,越说越糊涂啦。
在上个图,看下u能够点出来东西,希望能明白。
- 另一部分就是绑定一个save()方法,这个save()主要是用留下的属性创建条SQL语句,在Model上绑定就相当于给User绑定,这里就不在解释了吧。
第四部分:一上来就变换了三个数据格式,用的第一部分定义的的数据结构。然后这些属性会被拿着去创建类,就到了元类里,筛选好之后就会用**type.new**动态创建这个类。
元类三部曲:
- 拦截类的创建-----因为所有的类都要通过元类创建
- 修改操作-----实现想要的目的
- 返回修改好的类-----返回修改的类
所以元类能够在类创建时偷偷的做一些操作,这些偷偷的操作偏底层,我们看不见调用关系,所以也难理解。
这种三不娶的还有一种情况:护士、幼师、银行女 - - - (同样来自百度)。
好吧,其实是这个,@property
class Student(object):
def __init__(self, birth):
self.birth = birth
along = Student("along")
print(along.birth)
# 输出
along
其实这里没有任何语法错误,但是显然逻辑不对,birth的值显然应该是一个一定范围的年份,这就需要在赋值的过程中校验参数,,元类是创建类的过程中操作类,所以改写代码如下
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
if not isinstance(value, int):
raise ValueError('birth must be an integer!')
if value < 0 or value > 2020:
raise ValueError('birth must between 0 ~ 2020!')
self._birth = value
along = Stuent()
along.birth = 2000
print(along.birth)
这就是一个三部曲,通过@property,后面的是元类的三部曲。
- 拦截-----找到 @birth.setter,运行到里面 -> 找到ModelMetaclass,运行到里面
- 对赋值进行操作------运行 if…else…操作 -> 也一样运行 if…else…,筛选啥的
- 设置赋值-----------self._birth = value赋值 -> type.new(cls, name, bases, attrs)动态创建类
这种能看到程序调用流程的操作更容易理解吧,其实元类也一样,只不过程序运行过程是看不到的。
以上,希望我能说明白。