利用python实现ORM
轉載自:利用python實現ORM
做web開發的基本繞不過ORM,很多時候ORM可以大大減少開發工作量,那么ORM是怎樣實現的呢?其實很簡單,說穿了一文不值,本文先實現一個簡單的ORM然后分析下現在流行的python ORM框架peewee源碼。
ORM原理
ORM即對象關系映射(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),簡單來說就是把數據庫的一個表映射成程序中的一個對象,表中的每個字段對應程序對象的一個屬性,一個表記錄對應一個對象實例。
這樣的好處在于,數據庫的操作變得透明,底層數據庫被隔離,而且操作數據的過程中感覺不到數據庫的操作,始終處理的只有程序對象,程序結構性非常好。
如下圖,要建立一個ORM映射包括如下:
1.類名ClassName到表名TableName的映射,通常兩者相同
2.類屬性先建立到對應表字段的映射,通常屬性和字段名相同,表字段類型在類屬性初始化時指定
ORM簡單實現
按照上述,我們希望定義一個ORM類如下即可完成全部工作:
class UserModel(BaseModel):id = Field("bigint")name = Field("varchar(100)")age = Field("int")在這個類定義中,我們指定了類屬性到表字段的映射關系,這里認為類屬性名和表字段名相同。現在類只有映射關系,還沒有具體的屬性,而且映射也不是我們想要的map形式,因此程序如下:
在BaseModel中繼承內置字典dict來完成類屬性的設置,如下
class BaseModel(dict):__metaclass__ = ModelMetaClassdef __init__(self, **kv):super(BaseModel, self).__init__(**kv)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError("Model has not key %s" % key)def __setattr__(self, key, value):self[key] = value有了類屬性,需要將映射關系轉成我們想要的map形式,具體映射關系在子類中指定,參考上一節,借助元類在BaseModel中我們通過指定元類hook類創建過程,如下
class Field(object):def __init__(self, column_type):self.column_type = column_typeclass ModelMetaClass(type):def __new__(cls, name, bases, attr_dict):if name == 'BaseModel':return type.__new__(cls, name, bases, attr_dict)print("Creating Model:%s" % name)print(attr_dict)mapping = dict()for k,v in attr_dict.items():if isinstance(v, Field):print("Found Mapping:%s=>%s" % (k, v))setattr(v, 'name', k)mapping[k] = vattr_dict.pop(k)attr_dict['__mapping__'] = mappingattr_dict['__table__'] = namereturn type.__new__(cls, name, bases, attr_dict)在類創建時,凡是指定Field類型的類屬性都是我們定義的映射關系,在此取出來填充Field屬性表示字段信息,并以屬性名作為key來索引對應信息,對應的表名也映射為類的名稱。
到此整個映射關系建立完成,此時實現ORM就很簡單了,基本上就是查表生成sql語句即可,如下
save保存一個記錄,按照映射關系插入對應字段對應值即可,可在BaseModel中如下實現:
def save(self):fields = []params = []args = []for k, v in self.__mapping__.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))getone獲取指定id的一條記錄,查詢得結果,按照映射關系指定到對應屬性上即可,可在BaseModel中如下實現:
@classmethoddef getone(cls, id):# 簡化,根據數據庫查詢生成對應數據表data = {"id":"2", "name":"wenwei", "age":"19"}a = cls()for k, v in a.__mapping__.items():a.__setattr__(k, data[v.name])return a如下調用
if __name__ == '__main__':u1 = UserModel(id='1', name='wenzhou', age='20')u1.save()u2 = UserModel.getone('2')print(u2, type(u2))結果如下,即模擬了常見ORM的操作
Creating Model:UserModel {'age': <__main__.Field object at 0x00000000031FF710>, '__module__': '__main__', 'id': <__main__.Field object at 0x00000000031FF668>, 'name': <__main__.Field object at 0x00000000031FF6D8>} Found Mapping:age=><__main__.Field object at 0x00000000031FF710> Found Mapping:id=><__main__.Field object at 0x00000000031FF668> Found Mapping:name=><__main__.Field object at 0x00000000031FF6D8> SQL:insert into UserModel (age,id,name) values (?,?,?) ARGS:['20', '1', 'wenzhou'] ({'age': '19', 'id': '2', 'name': 'wenwei'}, <class '__main__.UserModel'>)peewee源碼分析
python下常見的ORM有django orm、SQLAlchemy和peewee,前兩者太重,peewee相對來說比較輕量靈活,代碼非常簡潔,我們以此為例來分析下它的實現。
先看下簡單的使用,如下:
from peewee import *settings = {'host': 'localhost', 'password': '', 'port': 3306, 'user': 'root'} db = MySQLDatabase('test', **settings)class Person(Model):id = BigIntegerField()name = CharField(50)age = IntegerField()class Meta:database = dbdb_table = 'user_test'if __name__ == '__main__':db.connect()usernames = ['Bob', 'huey', 'mickey']for person in Person.select().where(Person.id >= 5):print(person.id, person.name, person.age)db.close()這里使用和我們自定義類類似,定義一個包含表字段的Model子類,然后創建連接使用即可。它這里把連接信息database和db_table當做類的元數據Meta傳入。框架對數據庫層操作做了封裝,我們指定Person.select().where(Person.id >= 5)查詢的時候,類似我們自定義操作,根據定義的字段信息拼接出對應的SQL語句執行查詢,并填充對應的對象屬性值。
因此我們主要看下,它的字段映射關系如何指定的,一般編輯器中Ctrl選中Model,查看定義如下:
class Model(with_metaclass(ModelBase, Node)):def __init__(self, *args, **kwargs):...對應元類為ModelBase,查看定義如下:
class ModelBase(type):...def __new__(cls, name, bases, attrs):...Meta = meta_options.get('model_metadata_class', Metadata)...# Construct the new class.cls = super(ModelBase, cls).__new__(cls, name, bases, attrs)cls.__data__ = cls.__rel__ = Nonecls._meta = Meta(cls, **meta_options)cls._schema = Schema(cls, **sopts)fields = []for key, value in cls.__dict__.items():if isinstance(value, Field):if value.primary_key and pk:raise ValueError('over-determined primary key %s.' % name)elif value.primary_key:pk, pk_name = value, keyelse:fields.append((key, value))...if pk is not False:cls._meta.set_primary_key(pk_name, pk)for name, field in fields:cls._meta.add_field(name, field)...return cls這里判斷字段是Field實例的屬性為數據庫表對應字段,注意看這里add_field,展開如下:
def add_field(self, field_name, field, set_attribute=True):if field_name in self.fields:self.remove_field(field_name)elif field_name in self.manytomany:self.remove_manytomany(self.manytomany[field_name])...field.bind(self.model, field_name, set_attribute)是不是很相似,先從自身移除屬性,然后綁定屬性名到新值,這個新值是什么,繼續看bind展開:
class Field(ColumnBase):...accessor_class = FieldAccessor...def bind(self, model, name, set_attribute=True):self.model = modelself.name = nameself.column_name = self.column_name or nameif set_attribute:setattr(model, name, self.accessor_class(model, self, name))class FieldAccessor(object):def __init__(self, model, field, name):self.model = modelself.field = fieldself.name = namedef __get__(self, instance, instance_type=None):if instance is not None:return instance.__data__.get(self.name)return self.fielddef __set__(self, instance, value):instance.__data__[self.name] = valueinstance._dirty.add(self.name)可以看到,這里設置的新值就是FiledAccesor,這是個屬性描述符。
不妨print(Person.__dict__)如下:
{'name': < peewee.FieldAccessor object at 0x000000000280B6D8 > ,'age': < peewee.FieldAccessor object at 0x0000000002839748 > ,'id': < peewee.FieldAccessor object at 0x0000000002843198 > ,'_meta': < peewee.Metadata object at 0x0000000002791828 >... }可以看到,通過元類,每個屬性已經被換成屬性描述符,通過這種方式,統一每個字段的行為,保證Person.name返回的是對應字段的描述,通過person.name返回的是具體對象實例的值。打印一個print(Person.name.__dict__)如下:
{'column_name': 'name''primary_key': False,'name': 'name','max_length': 50,'unique': False,'index': False,'model': < Model: Person > ,... }即為對應的數據庫表信息。
可以看到peewee整個ORM實現方式和我們的如出一轍,關鍵是利用好屬性描述符和元類來完成,前者完成每個字段的類訪問和實例訪問的行為統一,后者實現攔截類創建過程實現替換屬性建立類-數據庫表映射,把握這兩點就可以分析整個ORM框架了。
?
演示代碼下載鏈接
原創,轉載請注明來自http://blog.csdn.net/wenzhou1219?
總結
以上是生活随笔為你收集整理的利用python实现ORM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中间件的解析漏洞详解及演示
- 下一篇: Python中List的复制(直接复制、