zoukankan      html  css  js  c++  java
  • Python 元类实现ORM

    ORM概念

    ORM(Object Ralational Mapping,对象关系映射)用来把对象模型表示的对象映射到基于 SQL  的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的 SQL 语句打交道,只需简单的操作实体对象的属性和方法。

    一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句。

    示例:

    class User(父类省略):
        uid = ('uid', "int unsigned")
        name = ('username', "varchar(30)")
        email = ('email', "varchar(30)")
        password = ('password', "varchar(30)")
        ...省略...
    
    
    u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
    u.save()
    # 对应如下sql语句
    # insert into User (username,email,password,uid)
    # values ('Michael','test@orm.org','my-pwd',12345)

    __new__、__init__、__call__的介绍

    在讲使用元类创建ORM之前,必须了解__new__这个内置方法的作用。

    __new__方法负责创建一个实例对象,在对象被创建的时候调用该方法它是一个类方法。__new__方法在返回一个实例之后,会自动的调用__init__方法,对实例进行初始化。如果__new__方法不返回值,或者返回的不是实例,那么它就不会自动的去调用__init__方法。

    __init__ 方法负责将该实例对象进行初始化,在对象被创建之后调用该方法,在__new__方法创建出一个实例后对实例属性进行初始化。__init__方法可以没有返回值。

    __call__方法其实和类的创建过程和实例化没有多大关系了,定义了__call__方法才能被以函数的方式执行。

    class A(object):
        def __call__(self):
            print "__call__ be called"
    
    a = A()
    a()    # 输出:__call__ be called 

    通过元类简单实现ORM中的insert功能

    class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
            mappings = dict()
            # 判断是否需要保存
            for k, v in attrs.items():
                # 判断是否是指定的StringField或者IntegerField的实例对象
                if isinstance(v, tuple):
                    print('Found mapping: %s ==> %s' % (k, v))
                    mappings[k] = v
    
            # 删除这些已经在字典中存储的属性
            for k in mappings.keys():
                attrs.pop(k)
    
            # 将之前的uid/name/email/password以及对应的对象引用、类名字
            attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
            attrs['__table__'] = name  # 假设表名和类名一致
            return type.__new__(cls, name, bases, attrs)
    
    
    class User(metaclass=ModelMetaclass):
        uid = ('uid', "int unsigned")
        name = ('username', "varchar(30)")
        email = ('email', "varchar(30)")
        password = ('password', "varchar(30)")
        # 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
        # 以上User类中有 
        # __mappings__ = {
        #     "uid": ('uid', "int unsigned")
        #     "name": ('username', "varchar(30)")
        #     "email": ('email', "varchar(30)")
        #     "password": ('password', "varchar(30)")
        # }
        # __table__ = "User"
        def __init__(self, **kwargs):
            for name, value in kwargs.items():
                setattr(self, name, value)    # 设置属性值
    
        def save(self):
            fields = []
            args = []
            for k, v in self.__mappings__.items():
                fields.append(v[0])
                args.append(getattr(self, k, None))
    
            args_temp = list()
            for temp in args:
                # 判断入如果是数字类型
                if isinstance(temp, int):
                    args_temp.append(str(temp))
                elif isinstance(temp, str):
                    args_temp.append("""'%s'""" % temp)    # 处理字符串类型的数据
            sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
            print('SQL: %s' % sql)
    
    
    u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
    # print(u.__dict__)
    u.save()

    输出结果

    Found mapping: uid ==> ('uid', 'int unsigned')
    Found mapping: name ==> ('username', 'varchar(30)')
    Found mapping: email ==> ('email', 'varchar(30)')
    Found mapping: password ==> ('password', 'varchar(30)')
    SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd')

    抽取到基类中

    class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
            mappings = dict()
            # 判断是否需要保存
            for k, v in attrs.items():
                # 判断是否是指定的StringField或者IntegerField的实例对象
                if isinstance(v, tuple):
                    print('Found mapping: %s ==> %s' % (k, v))
                    mappings[k] = v     # 将提取的内容存放在一个字典中
    
            # 删除这些已经在字典中存储的属性
            for k in mappings.keys():
                attrs.pop(k)
    
            # 将之前的uid/name/email/password以及对应的对象引用、类名字
            attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
            attrs['__table__'] = name  # 假设表名和类名一致
            return type.__new__(cls, name, bases, attrs)
    
    
    class Model(object, metaclass=ModelMetaclass):
        def __init__(self, **kwargs):
            for name, value in kwargs.items():
                setattr(self, name, value)  # 将name,value设置为实例属性
    
        def save(self):
            fields = []
            args = []
            for k, v in self.__mappings__.items():
                # fields.append(v[0])     # v为类属性的元组
                fields.append(k)    # 如果使用v[0],那就是取元组中的uid、username等为数据库表中字段名,不利于理解,直接取类属性的变量名
                args.append(getattr(self, k))   # k为类属性的变量名
    
            args_temp = list()
            for temp in args:
                # 判断入如果是数字类型
                if isinstance(temp, int):
                    args_temp.append(str(temp))
                elif isinstance(temp, str):
                    args_temp.append("""'%s'""" % temp)
            sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
            print('SQL: %s' % sql)
    
    
    class User(Model):      # User对应数据库中的表名
        # uid = ('uid', "int unsigned")
        # username = ('name', "varchar(30)")
        # email = ('email', "varchar(30)")
        # password = ('password', "varchar(30)")
        uid = ("int unsigned",)     # 数据库字操作的字段以这里的字段名为主,实例化传的变量名,主要用来匹配各个字段对应的值,可以多但不能少,否则报错
        username = ("varchar(30)",)
        email = ("varchar(30)",)
        password = ("varchar(30)",)
    
    
    u = User(uid=12345, username='Michael', email='test@orm.org', password='my-pwd', phone=12345678900)
    # print(u.__dict__)
    u.save()

    输出结果

    Found mapping: uid ==> ('int unsigned',)
    Found mapping: username ==> ('varchar(30)',)
    Found mapping: email ==> ('varchar(30)',)
    Found mapping: password ==> ('varchar(30)',)
    SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd')

    通过上面的示例,我们可以看出用元类创建API是非常好的选择,使用元类的编写虽然很复杂,但使用者可以非常简洁的调用API。

  • 相关阅读:
    一个在线的C++帮助文档网站
    linux 学习笔记 (四)
    类的static成员函数和const成员函数
    Linux的inode、软链接、硬链接
    常用linux命令(三)
    多语言调用之 C++ 调用 Java JNI
    多语言调用之 Java调用C/C++
    NHibernate 操作原生SQL以及查询DataTable,DataSet
    DataGridView控件用法合集
    Java AOP实战 寻找SQL的引用路径
  • 原文地址:https://www.cnblogs.com/testlearn/p/12696106.html
Copyright © 2011-2022 走看看