什么是ORM?
ORM的英文全稱是“Object Relational Mapping”,即對象-關系映射,從字面上直接理解,就是把“關系”給“對象”化。
對應到數據庫,我們知道關系數據庫(例如Mysql)的特征就是數據與數據之間存在各種各樣的“關系”,這種“關系”是由Table(表)來維護和表現的。
ORM就是把關系數據庫的一個"表"映射成一個"類",然后給"類"添加各種各樣的方法(比如增刪改查)。
也就是說,我們要完成這樣的設計:user可以根據實際需求(表的定義,比如包括哪些數據字段等)把通過ORM把"表"(table)設計成一個"類"(class),這就需要用到Metaclass的特性: 動態的創建類(class)
這樣,寫代碼更簡單,不用直接操作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. 返回修改之后的類
參考鏈接: