什么是 ORM?
ORM 的英文全称是 "Object Relational Mapping", 即对象 - 关系映射, 从字面上直接理解, 就是把 "关系" 给 "对象" 化.
对应到数据库, 我们知道关系数据库 (例如 Mysql) 的特征就是数据与数据之间存在各种各样的 "关系", 这种 "关系" 是由 Table(表)来维护和表现的.
ORM 就是把关系数据库的一个 "表" 映射成一个 "类", 然后给 "类" 添加各种各样的方法(比如增删改查).
这样, 写代码更简单, 不用直接操作 SQL 语句.
要编写一个 ORM 框架, 所有的类都只能动态定义, 因为我们知道, 在数据库使用过程中, Mysql 的 "表" 是由 User 来创建的; 对应过来, 那么 "表" 对应的类也是应该由 User 来创建的.
让我们来尝试编写一个 ORM 框架.
编写底层模块的第一步, 就是先把调用接口写出来.
先搞清楚 user 会如何使用这个 ORM 框架: 使用这个 ORM 框架, 创建一个 Customer 类来操作对应的数据库表 customer, 我们期待他写出这样的代码:
- class Customer(Model):
- # 定义类的属性到列的映射:
- id = IntegerField('id')
- name = StringField('username')
- email = StringField('email')
- password = StringField('password')
- # 创建一个实例:
- u = Customer(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
- # 保存到数据库:
- u.save()
其中, 父类 Model 和属性类型 StringField,IntegerField 是由 ORM 框架提供的. 显然, save()等方法应该由 ORM 提供, 这样既安全又使得 user 不用重复定义这些方法. 具体实现上, save()等全部由 metaclass 和基类 Model 自动完成. 虽然 metaclass 和基类 Model 的编写会比较复杂, 但 ORM 的使用者用起来却异常简单.
现在, 我们就按上面的接口来实现该 ORM.
首先搞清楚我们应该做的工作:
1. 既然要使得 user 能够通过继承 Model, 实现动态的创建类, 那么必然要在 ModelMetaclass 中实现动态类的创建(比如能实现类 Cutomer 中的定义的 attr 属性们)
2. 在基类 Model 中实现 save 等方法.
首先来定义 Field 类, 它负责保存数据库表的字段名和字段类型:
- class Field(object):
- def __init__(self, name, column_type):
- self.name = name
- self.column_type = column_type
- def __str__(self):
- return '<%s:%s>' % (self.__class__.__name__, self.name)
在 Field 的基础上, 进一步定义各种类型的 Field, 比如 StringField,IntegerField 等等:
- class StringField(Field):
- def __init__(self, name):
- super(StringField, self).__init__(name, 'varchar(100)')
- class IntegerField(Field):
- def __init__(self, name):
- super(IntegerField, self).__init__(name, 'bigint')
下一步, 就是编写最复杂的 ModelMetaclass 了:
- class ModelMetaclass(type):
- def __new__(cls, name, bases, attrs):
- if name=='Model':
- return type.__new__(cls, name, bases, attrs)
- print('Found model: %s' % name)
- mappings = dict()
- for k, v in attrs.items():
- if isinstance(v, Field):
- print('Found mapping: %s ==> %s' % (k, v))
- mappings[k] = v
- for k in mappings.keys():
- attrs.pop(k)
- attrs['__mappings__'] = mappings # 保存属性和列的映射关系
- attrs['__table__'] = name # 假设表名和类名一致
- return type.__new__(cls, name, bases, attrs)
当用户定义一个
class Customer(Model)
时, Python 解释器首先在当前类 Customer 的定义中查找 metaclass, 如果没有找到, 就继续在父类 Model 中查找 metaclass, 找到了, 就使用 Model 中定义的 metaclass 的 ModelMetaclass 来创建 Customer 类, 也就是说, metaclass 可以隐式地继承到子类, 但子类自己却感觉不到.
在 ModelMetaclass 中, 一共做了几件事情:
排除掉对 Model 类的修改(肯定要避免 user 通过调用 ModelMetaclass 来修改我们定义好的基类 Model);
在当前类 (比如 Customer) 中查找 user 定义的类 (也就是例子中的 Customer) 的所有属性, 如果找到一个 Field 属性, 就把它保存到一个__mappings__的 dict 中, 同时从类 (Customer) 的属性中删除该 Field 属性, 否则, 容易造成运行时错误(实例的属性会遮盖类的同名属性, 也就是说保证 Customer 这个类里没有 id,name 等属性, 从而不会影响 Customer 的实例的值; 这里之所以为了避免出现实例与类的同名属性, 既避免运行时错误, 是因为当出现类和实例的同名属性时, python 会优先调用实例属性, 如果没有的话则会调用类的属性, 但是有时当实例属性不存在时, 我们并不希望用户能调用到这个同名的类属性, 比如当实例属性被删除时, 再用相同的名字, 就访问到了类属性了. 因此才这么做);
把表名保存到__table__中, 这里简化为表名默认为类名.
以及基类 Model:
- class Model(dict, metaclass=ModelMetaclass):
- def __init__(self, **kw):
- super(Model, self).__init__(**kw)
- def __getattr__(self, key):
- try:
- return self[key]
- except KeyError:
- raise AttributeError(r"'Model' object has no attribute '%s'" % key)
- def __setattr__(self, key, value):
- self[key] = value
- def save(self):
- fields = []
- params = []
- args = []
- for k, v in self.__mappings__.items():
- fields.append(v.name)
- params.append('?')
- args.append(getattr(self, k, None))
- sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
- print('SQL: %s' % sql)
- print('ARGS: %s' % str(args))
在 Model 类中, 就可以定义各种操作数据库的方法, 比如 save(),delete(),find(),update 等等.
我们实现了 save()方法, 把一个实例保存到数据库中. 因为有表名, 属性到字段的映射和属性值的集合, 就可以构造出 INSERT 语句.
编写代码试试:
- u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
- u.save()
输出如下:
- Found model: User
- Found mapping: email ==> <StringField:email>
- Found mapping: password ==> <StringField:password>
- Found mapping: id ==> <IntegerField:uid>
- Found mapping: name ==> <StringField:username>
- SQL: insert into User (password,email,username,id) values (?,?,?,?)
- ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345]
可以看到, save()方法已经打印出了可执行的 SQL 语句, 以及参数列表, 只需要真正连接到数据库, 执行该 SQL 语句, 就可以完成真正的功能.
最终, 我们实现了关系型数据库的 ORM 框架, user 可以通过继承 Model 类, 把各种各样的表写成对象, 进行操作.
总结一下, 通过 ORM 的这个例子, 我们可以看出 Metaclass 的作用总结起来就三点:
1. 拦截类的创建
2. 修改类
3. 返回修改之后的类
参考链接:
深刻理解 Python 中的元类(metaclass) http://blog.jobbole.com/21351/
来源: https://www.cnblogs.com/ArsenalfanInECNU/p/9100874.html