python的元类以及应用(三),不用元类实现ORM

在前两篇文章中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一定要通过元类实现吗? 元类是提供了一个更好的实现方案,概念比较难以理解,但是某些情况下,比其他手段来限制类的行为,来得更加优雅。

相关推荐
米码收割机8 分钟前
【python】tkinter实现音乐播放器(源码+音频文件)【独一无二】
开发语言·python·pygame
星如雨グッ!(๑•̀ㅂ•́)و✧19 分钟前
Java NIO全面详解
java·python·nio
笛柳戏初雪24 分钟前
Python中的函数(下)
开发语言·python
码界筑梦坊40 分钟前
基于Django的个人博客系统的设计与实现
后端·python·django·毕业设计
weixin_307779131 小时前
AWS EMR上的Spark日志实时搜索关键指标网页呈现的设计和实现
大数据·python·spark·云计算·aws
凌肖战1 小时前
Python3 OS模块中的文件/目录方法说明十四
python
深蓝海拓2 小时前
基于深度学习的视觉检测小项目(十六) 用户管理界面的组态
人工智能·python·深度学习·qt·pyqt
Qhumaing2 小时前
Python学习——函数参数详解
开发语言·python·学习
Icomi_2 小时前
【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙
c语言·c++·人工智能·pytorch·python·机器学习·计算机视觉