Python 元类编程实现 DjangoORM
需求说明
使用 Python 元类编程实现简单的 Django Model 定义、校验以及构造生成 sql 语句。
我们实例化一个User对象的时候可以有三种方式
- 可以user=User()、user(name = None, age = 18)
- 可以user=User()、user['name] =None,user[‘age’] = 18
- 还可以 user=User()、user.name = None, user.age = 18
前面两种都可以通过继承字典dict 实现,第三种,用getattr和setattr
一、定义 Field 类
我们只定义了两个 Field 类:IntField、CharField,用于定义 Model 支持的数据类型以及对值的类型、长度、大小进行一些简单校验。
import numbers
class Field(object):
def __init__(self):
self._value = None
def __str__(self):
return self._value
def __get__(self, instance, owner):
return self._value
class IntField(Field):
def __init__(self, db_column=None, min_value=None, max_value=None, *args, **kwargs):
self.db_column = db_column
if min_value is not None and not isinstance(min_value, numbers.Integral):
raise ValueError('min_value must be int')
self.min_value = min_value
if max_value is not None and not isinstance(max_value, numbers.Integral):
raise ValueError('max_value must be int')
self.max_value = max_value
if min_value is not None and max_value is not None:
if min_value > max_value:
raise ValueError('min_value must smaller than max_value')
super().__init__()
def __set__(self, instance, value):
if not isinstance(value, numbers.Integral):
raise ValueError('IntField value must be int')
if self.min_value is not None and value < self.min_value:
raise ValueError(f'IntField value must bigger than {self.min_value}')
if self.max_value is not None and value > self.max_value:
raise ValueError(f'IntField value must smaller than {self.max_value}')
self._value = value
class CharField(Field):
def __init__(self, db_column=None, max_length=None, *args, **kwargs):
self.db_column = db_column
if max_length is not None and not isinstance(max_length, numbers.Integral):
raise ValueError('max_length must be int')
elif max_length < 0:
raise ValueError('max_length must bigger than 0')
self.max_length = max_length
super().__init__()
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError('CharField value must be str')
if self.max_length is not None and len(value) > self.max_length:
raise ValueError(f'CharField value length must smaller than {self.max_length}')
self._value = value
第一步没什么好说的,提醒一点:数据验证很重要。
二、定义元类
该元类主要用于将 Model 中定义的 column 以及 db_table 等初始化到 Model 的实例对象上。
class ModelMetaClass(type):
# name -> class.name
# bases -> 继承的基类名
# attrs 可变参数
def __new__(cls, name, bases, attrs, **kwargs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
fields[key] = value
_meta = {}
attr_meta_class = attrs.get('Meta', None)
db_table = name.lower()
if attr_meta_class is not None:
db_table = getattr(attr_meta_class, 'db_table', db_table)
_meta['db_table'] = db_table
attrs['fields'] = fields
attrs['_meta'] = _meta
# 调用父类的__new__方法。
return super().__new__(cls, name, bases, attrs, **kwargs)
第二步中值得注意的:attr_meta_class 中就是将来,django orm中class Meta:里的字段。
这里,如果attr_meta_class 字段为空,将来表的字段就是name.lower() → 类的名称
如果不为空就在attr_meta_class 取db_table 字段的值作为将来表的名称
三、定义 Model 基类
Model 基类主要用于支持 Model 创建对象的时候及初始化 column 值以及定义通用的 orm 处理逻辑函数。
class BaseModel(metaclass=ModelMetaClass):
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
# 属性访问
setattr(self, key, value)
super().__init__()
def save(self):
db_table = self._meta.get('db_table')
fields = []
values = []
# 这个self.fields 是继承元类的fields字段,里面保存的就是未来的 字段名和值
# 也可以换成 in self.attrs['fields'].items() -> 不可以换成这个。
# 原因是因为 BaseModel初始化中没有继承父类的attrs属性
for key, field in self.fields.items():
# 这里也可以用继承字典的方式,设置 属性分配。
db_column = field.db_column
if db_column is None:
db_column = key.lower()
fields.append(db_column)
value = getattr(self, key)
values.append(str(value))
sql = f"insert {db_table}({','.join(fields)}) value({','.join(values)})"
print(sql)
四、定义 Model 实体类
class User(BaseModel):
name = CharField(max_length=10)
age = IntField(min_value=0, max_value=100)
class Meta:
db_table = 'user'
def __str__(self):
return self.name
五、测试
user = User(name='hmb', age=19)
# hmb = User()
# hmb['name'] = 'hmb' 这种分配方式不可行,在第三步中没有实现。
# hmb['age'] = 18
hmb2 = User()
hmb2.name = 'hmb'
hmb2.age = 18
user.save()
# hmb.save()
hmb2.save()
创建 model 类,以及调用 save 会输出经过 orm 转换后的 sql 语句:insert user(name,age) value(python,18)
>
六、输出结果
insert user(name,age) value(hmb,18)
insert user(name,age) value(hmb,18)