Python 元类实现ORM
Posted 不自在
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。
以上是关于Python 元类实现ORM的主要内容,如果未能解决你的问题,请参考以下文章
35.Python面向对象元类:type()__metaclass__属性实现简易ORM框架
35.Python面向对象元类:type()__metaclass__属性实现简易ORM框架