前言

在上一篇文章中juejin.cn/post/729974…, 介绍了python元类的概念以及几个应用的比如,这次来介绍一下ORM以及解答之前的问题【为什么搜索元类应用,总是呈现ORM】

关于ORM

ORM是指目标联系映射(Object Relational Mapping,简称ORM),从全称上能够看出这是处理【目标】和【联系型数据库】之间的映射的技能。
咱们先来看看一个数据表

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,  
  username VARCHAR(50) NOT NULL,  
  email VARCHAR(100) NOT NULL,  
  birthdate DATE,  
  is_active BOOLEAN DEFAULT TRUE  
);

当咱们进行查询或许刺进的时分,需求这么写:

# 查询
SELECT id, username FROM users;
# 刺进
INSERT INTO users (id, username) VALUES (1,"Tom");

能够发现,假如要进行数据库操作,需求拼接许多sql语句,这对于代码的保护和更新来说,将会增加许多工作量。
假如能够有一个办法,把面向目标和联系型数据结合起来,那将会便利很多。 比如,上述的操作,能够变成相似这样:

class User:
    id = INT(auto_increment = True, primary_key = True)
    username = VARCHAR(50)
    email =  VARCHAR(100)
# 创立数据表
create(User)
# 或许:
User.create()
# 查询
result = User.select().where(id = 1)
# 或许:
result = select(User).where(id = 1)
# 刺进
User.insert(id = 1, name = "Tom")
# 或许
insert(User(id = 1, name = "Tom"))

以上是举个比如,实践的ORM类库纷歧定是这样的风格。 首要是表达出ORM的观点:将目标和联系型数据库进行映射,目标的特点便是数据库的字段,目标的方法便是数据库的操作。
经过这样的映射,能够省去拼接sql语句,也能够进行更多杂乱的操作,例如数据校验等等。

* ORM的两种风格

具体介绍元类和ORM之前,先聊一下ORM的两种形式:active record 和 data mapper。
1.Active Record形式:
把数据模型和数据耐久化结合,每个描述数据表的模型都包含访问数据库的方法,能够直接操作数据库。例如上面的:

User.create()
result = User.select().where(id = 1)

在python中,Django的ORM,Peewee等便是运用active record形式。
2. Data Mapper形式:
这个形式把数据模型和数据耐久化分隔,由一个独立的数据映射来处理数据耐久化,数据模型只是担任业务逻辑。
例如上面的:

create(User)
result = select(User).where(id = 1)

SQLAlchemy便是运用data mapper形式。

这里不去讨论两种的好坏,只是简单介绍有这两种形式。后面的代码将经过active record来完结ORM.

元类的ORM的联系

在介绍了元类和ORM之后,二者到底有什么联系?
咱们看这样一个比如:
在一个user表里面,写入id =1,username="Tom";id = 2, username = "Jack"
假如创立一个函数:

def insert_user(id:int,username:str):
    sql = f'INSERT INTO user (id,username) VALUES ({id},{username})'

这将和拼接sql是没有不同的,并不是orm。 实践上咱们是不知道创立表的时分,会有什么字段,这些字段都是运用者自己创立的。 因而,咱们要有这样的映射:
【类】——>【数据表】
【类特点】——>【数据表字段】
【目标】——>【数据记录】
【目标特点值】——>【数据表字段值】

咱们期望运用的方法相似这样:

Class User:
    # 类特点映射到数据表
    id = IntegerField('id')
    username = VarcharField('username')
# 实例化一条数据记录
user1 = User(id = 1, username = 'Tom')
# insert
user1.insert()
user2 = User(id = 2,username = 'Jack')
user2.insert()

能够看到,在创立User类的时分,需求进行字段映射,因而,这里便是运用元类的原因:【拦截和修正类的创立】,让运用者在界说类的时分,完结对数据表的界说;在实例化一个类的时分,完结一条数据记录的界说。

运用元类完结ORM

先完结字段类型的界说:

class Field:
    def __init__(self, name):  
        self.name = name  
class IntegerField(Field):  
    def __init__(self, name):  
        super().__init__(name)  
        self.type = 'int'  
class VarcharField(Field):  
    def __init__(self, name):  
        super().__init__(name)  
        self.type = 'varchar(100)'

元类的界说:

class MetaModel(type):
    def __new__(cls, name, bases, attrs):
        # 排除Model类
        if name == 'Model':  
            return type.__new__(cls, name, bases, attrs)  
        # 将类特点隔离,防止后续实例化被掩盖。 类界说了id=IntegerField(),实例化id = 1会因为同名被掩盖
        mapper = {}  
        for k, v in attrs.items():  
            if isinstance(v, Field):  
                mapper[k] = v  
        for k in mapper:  
            attrs.pop(k)  
        attrs['__mapper__'] = mapper  
        if '__table__' not in attrs:  
            attrs['__table__'] = name  
        return type.__new__(cls, name, bases, attrs)

Model类的界说:

class Model(metaclass=MetaModel):
    # 实例化设置具体值
    def __init__(self, **kwargs):  
        for k, v in kwargs.items():  
            self.__setattr__(k, v)  
    def insert(self):  
        column_li = []  
        val_li = []  
        # 读取元类设置好的__mapper__
        for k, v in self.__mapper__.items():  
            val = getattr(self, k, None)  
            if val is not None:  
                # 留意这个v.name,这是Field字段的name,实践数据表里面的字段名
                column_li.append(v.name)  
                val_li.append(f'"{val}"')  
        print(column_li)  
        print(val_li)  
        sql = f'INSERT INTO {self.__table__} ({",".join(column_li)}) VALUES ({",".join(val_li)})'  
        print(sql)
    @classmethod  
    def create(cls):  
        cloumn_li = []  
        for k, v in cls.__mapper__.items():  
            cloumn_li.append(f'{v.name} {v.type}')  
        sql = f'CREATE {cls.__table__} {",".join(cloumn_li)}'  
        print(sql)

调用状况:

class User(Model):
    __table__ = 'user'
    id = IntegerField('id')  
    username = VarcharField('username')  
User.create()
user = User(id=1, username='tom')  
user.insert()
输出:
CREATE user id int,username varchar(100)
['id']
['"1"']
INSERT INTO user (id) VALUES ("1")

元类首要做了几件事:
1.收集类特点,判别类特点是否是属于File(),也便是是否是数据表字段。将这些字段存入__mapper__,【防止实例化被具体数值掩盖】。
2.表名设置。经过对类特点的查看,设置表名。 因而,假如需求其他额定特点的设置,也能够在元类界说加入。
这便是核心的把类、目标和数据表进行映射。还能够加上其他操作方法、增加不同的数据库语法、字段查看等等。

总结

运用ORM能够便利的进行联系型数据库库操作,减少开发担负。要完结ORM的要点,便是如何把类和实例与联系型数据库进行映射。 python的元类,能够在拦截和修正类的创立,因而能够经过元类,在类的界说时,进行映射。
回到最初的问题:ORM必定要用元类来完结吗? 经过上述的内容能够得知,只要能够完结映射,就纷歧定需求用元类。 后续会展现不运用元类完结的ORM。