在前两篇文章中juejin.cn/post/729974...,juejin.cn/post/730885... 介绍了python中元类和ORM的概念,以及为什么搜元类的应用,搜出来都是和ORM相关。因为使用元类,可以很方便的实现ORM。
还有最后一个疑问:
ORM一定要用元类实现吗?
答案是不用的。
下面将不使用元类实现简单的ORM功能。
首先写出最后我们希望调用的效果:
python
class User(Model):
name = CharField(max_len=10)
age = IntegerField()
user = User(name='tom', age=24)
user.create()
这与常见的ORM没有区别。
现在来实现Model类。
从之前的文章可以看到,核心的问题是,如何把用户定义的User类中的类属性和数据表做映射,然后操作实例化中的user的值。
Field类
python
class Field:
def __init__(self, **kwargs):
self.name = kwargs.get('name')
self.column_type = kwargs.get('column_type')
# 这边name可以让它不填,不填就默认变量名。 在这边无法获取变量名,可以在model中获取
class IntegerField(Field):
def __init__(self, name=None):
kwargs = {
'name': name,
'column_type': 'INT'
}
super().__init__(**kwargs)
class CharField(Field):
def __init__(self, name=None, max_len=30):
kwargs = {
'name': name,
'column_type': f'VARCHAR({max_len})'
}
super().__init__(**kwargs)
Field和之前文章的没有什么区别
Model类
python
class Model:
def __init__(self, **kwargs):
# 获取类属性
_meta = self.get_class_meta()
# 获取实例属性
for k, v in kwargs.items():
if k in _meta:
self.__dict__[k] = v
@classmethod
def get_class_meta(cls) -> Dict:
# 类属性保存在_meta中
if hasattr(cls, '_meta'):
return cls.__dict__['_meta']
_meta = {}
# 遍历类属性,如果是Field类型,就存入_meta
for k, v in cls.__dict__.items():
if isinstance(v, Field):
# 判断如果没有设置字段名,就默认变量名作为字段名
if v.name is None:
v.name = k
name = v.name
# 注意k是类属性的变量名,name是字段名
_meta[k] = (name, v)
# 没有__table__字段就用类名作为表名
table = cls.__dict__.get('__table__')
table = cls.__name__ if table is None else table
_meta['__table__'] = table
setattr(cls, '_meta', _meta)
return _meta
def insert(self):
_meta = self.get_class_meta()
column_li = []
val_li = []
for k, v in self.__dict__.items():
# 从_meta获取已经保存的字段
filed_tuple = _meta.get(k)
if filed_tuple:
column: str
filed: Field
column, filed = filed_tuple
column_li.append(column)
val = str(v) if filed.column_type == 'INT' else f'"{str(v)}"'
val_li.append(val)
print('columns:', column_li)
print('values:', val_li)
sql = f'INSERT INTO {_meta["__table__"]} ({",".join(column_li)}) VALUES ({",".join(val_li)});'
print(sql)
调用结果:
python
class User(Model):
name = CharField(max_len=10)
age = IntegerField()
user = User(name='Tom', age=24)
user.insert()
输出:
columns: ['name', 'age']
values: ['"Tom"', '24']
INSERT INTO User (name,age) VALUES ("Tom",24);
这样就将ORM最核心的字段映射解决了。 这种方式可能有一些问题,比如它并不能和元类一样把类属性从cls.__dict__
中去除。因为在元类中,类属性被存放在attrs
中,这是一个字典。 而现在这种方式,cls.__dict__
是mappingproxy
,不能调用pop。使用del cls.__dict__
也不能删除。
python
.....
for k, v in cls.__dict__.items():
if isinstance(v, Field):
# 判断如果没有设置字段名,就默认变量名作为字段名
name = v.name if v.name else k
# 注意k是类属性的变量名,name是字段名
_meta[k] = (name, v)
for k in _meta:
cls.__dict__.pop(k)
....
报错:
AttributeError: 'mappingproxy' object has no attribute 'pop'
使用del:
....
for k in _meta:
del cls.__dict__[k]
....
报错:
TypeError: 'mappingproxy' object does not support item deletion
虽然在实例化的时候,实例属性会将同名的类属性覆盖,但是不得不在很多地方需要进行类型判断。使用元类可以更便利的修改类的创建。
Query
顺便完成一下查询。
先写一下预期的效果:
python
class User(Model):
id = IntegerField(name='id')
name = CharField(max_len=20)
age = IntegerField()
User.query().where(User.name == 'Tom' and User.age >= 20).order_by('age').get()
可以看到,这样的查询,要支持字段条件运算,要能筛选字段,还要支持链式调用。
先来完成字符串重载,让Field类可以支持字符串运算
python
class Field:
def __init__(self, **kwargs):
self.name = kwargs.get('name')
self.column_type = kwargs.get('column_type')
def __eq__(self, other):
return Compare(self, '=', other)
def __ne__(self, other):
return Compare(self, '!=', other)
def __gt__(self, other):
return Compare(self, '>', other)
def __ge__(self, other):
return Compare(self, '>=', other)
def __lt__(self, other):
return Compare(self, '<', other)
def __le__(self, other):
return Compare(self, '<=', other)
# 增加一个Compare类,用来储存条件。
class Compare:
def __init__(self, left: Field, operation: str, right: Any):
self.conditon = f'`{left.name}` {operation} "{right}"'
def __or__(self, other: "Compare"):
self.conditon = f'({self.conditon}) OR ({other.conditon})'
return self
def __and__(self, other: "Compare"):
self.conditon = f'({self.conditon}) AND ({other.conditon})'
return self
python
class Query:
def __init__(self, cls: Model):
# 传入Model类
self._model = cls
# 排序字段
self._order_columns = None
self._desc = ''
# 字段映射_meta
self._meta = self._model.get_class_meta()
# compare类
self._compare = None
# sql语句
self.sql = ''
def _get(self) -> str:
sql = ''
# 添加查询条件
if self._compare:
sql += f' WHERE {self._compare.conditon}'
# 添加排序字段
if self._order_columns:
sql += f' ORDER BY {self._order_columns}'
sql += f' {self._desc}'
return sql
def get(self, *args: Field) -> List[Model]:
sql = self._get()
table = self._meta['__table__']
column_li = []
# 判断最后查询的字段
if len(args) > 0:
for field in args:
column_li.append(f'`{field.name}`')
# 查询全部字段
else:
for v in self._meta.values():
if type(v) == tuple and isinstance(v[1], Field):
column_li.append(f'`{v[0]}`')
columns = ",".join(column_li)
sql = f'SELECT {columns} FROM {table} {sql}'
self.sql = sql
print(self.sql)
def order_by(self, colums: Union[List, str], desc: bool = False) -> "Query":
# 单个字段排序
if isinstance(colums, str):
self._order_columns = f'`{colums}`'
# 多个字段排序
if isinstance(colums, list):
self._order_columns = ','.join([f'`{x}`' for x in colums])
# 倒序
self._desc = 'DESC' if desc else ''
# 返回Query类,实现链式调用
return self
def where(self, compare: "Compare") -> "Query":
# 储存Compare
self._compare = compare
return self
调用结果:
python
class User(Model):
name = CharField(max_len=10)
age = IntegerField()
User.query().where(User.name == 'Tom' and User.age >= 20).order_by('age').get()
输出:
SELECT `name`,`age` FROM User WHERE `age` >= "20" ORDER BY `age`
这样就简单实现了Query功能,其他的limit之类的,或者调用方式变成了User.query().where(...).where()...
也可以自行修改。
总结
以上就是不使用元类实现简单的ORM。最初的疑问是ORM一定要通过元类实现吗? 元类是提供了一个更好的实现方案,概念比较难以理解,但是某些情况下,比其他手段来限制类的行为,来得更加优雅。