看了一下廖雪峰的那個web框架,其實就是封裝了web.py,請求使用異步並將aiomysql做
為MySQL數據庫提供了異步IO的驅動,前端部分則整合了jinja.其中最難的應該是orm部分了。
下面是orm一個簡單的例子。
class User(Model): __table__ = 'users' id = StringField(primary_key=True, default=next_id, ddl='varchar(50)') email = StringField(ddl='varchar(50)') passwd = StringField(ddl='varchar(50)') admin = BooleanField() name = StringField(ddl='varchar(50)') image = StringField(ddl='varchar(500)') created_at = FloatField(default=time.time)
整體來看這是一個為User的簡單table,集成了Model類。然后字段則是各種類型Field。
1.先來看看字段,也就是Field部分。Field部分比較簡單,繼承了object,然后初始化一些字段,其實沒什么好說的。在字段賦值時,參數以dict格式傳入,傳入到哪里呢,本來type的dict是為none的,但下面這段代碼用__new__改變了原來的type,而元類是創建類的類,因此,Field字段的參數,都會在ModelMetaclass類中做處理,下面看個調試的例子,看看元類的威力。
IPython 4.0.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: from orm import Model In [2]: class Blog(Model): ...: pass ...: In [3]: In [3]: a=Blog In [4]: type(a) Out[4]: orm.ModelMetaclass In [5]: dir(a) Out[5]: ['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__fields__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mappings__', '__module__', '__ne__', '__new__', '__primary_key__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__table__', '__weakref__', 'clear', 'copy', 'find', 'findAll', 'findNumber', 'fromkeys', 'get', 'getValue', 'getValueOrDefault', 'items', 'keys', 'pop', 'popitem', 'remove', 'save', 'setdefault', 'update', 'values'] In [6]:
從上面可以看到,a是繼承了元類修改后的類Blog的一個對象,因此繼承了新增的屬性跟方法。所以元類都能對所有的orm中的表做出約束和修改。
下面這段繼承自type的ModelMetaclass就是元類的代碼。
class ModelMetaclass(type): def __new__(cls, name, bases, attrs): if name=='Model': return type.__new__(cls, name, bases, attrs) tableName = attrs.get('__table__', None) or name logging.info('found model: %s (table: %s)' % (name, tableName)) mappings = dict() fields = [] primaryKey = None for k, v in attrs.items(): if isinstance(v, Field): logging.info(' found mapping: %s ==> %s' % (k, v)) mappings[k] = v if v.primary_key: # 找到主鍵: if primaryKey: raise StandardError('Duplicate primary key for field: %s' % k) primaryKey = k else: fields.append(k) if not primaryKey: raise StandardError('Primary key not found.') for k in mappings.keys(): attrs.pop(k) escaped_fields = list(map(lambda f: '`%s`' % f, fields)) attrs['__mappings__'] = mappings # 保存屬性和列的映射關系 attrs['__table__'] = tableName attrs['__primary_key__'] = primaryKey # 主鍵屬性名 attrs['__fields__'] = fields # 除主鍵外的屬性名 attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName) attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1)) attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey) attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey) return type.__new__(cls, name, bases, attrs)
而Model類相對簡單,主要是為了初始化了get/set以及幾個getvalue的函數,這一塊根據自己需要看看就好。
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 getValue(self, key): return getattr(self, key, None) def getValueOrDefault(self, key): value = getattr(self, key, None) if value is None: field = self.__mappings__[key] if field.default is not None: value = field.default() if callable(field.default) else field.default logging.debug('using default value for %s: %s' % (key, str(value))) setattr(self, key, value) return value @classmethod @asyncio.coroutine def findNumber(cls, selectField, where=None, args=None): ' find number by select and where. ' sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)] if where: sql.append('where') sql.append(where) rs = yield from select(' '.join(sql), args, 1) if len(rs) == 0: return None return rs[0]['_num_']
....
@classmethod是為了將方法變成類方法,@asyncio.coroutine則是為了做異步處理。
那每一個orm下面的表是如何進行值傳遞的呢?下面看看另一個調試例子
In [2]: from orm import Model,StringField In [3]: class Blog(Model): ...: __table__='user' ...: id=StringField(ddl='varchar(50)') ...: age=StringField(name="ss") ...: In [4]: a = Blog In [5]: dir(a) Out[5]: ['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__fields__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mappings__', '__module__', '__ne__', '__new__', '__primary_key__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__table__', '__weakref__', 'age', 'clear', 'copy', 'find', 'findAll', 'findNumber', 'fromkeys', 'get', 'getValue', 'getValueOrDefault', 'id', 'items', 'keys', 'pop', 'popitem', 'remove', 'save', 'setdefault', 'update', 'values'] In [6]: In [6]: a.__dict__ Out[6]: mappingproxy({'age': <orm.StringField object at 0x0000000004899A20>, '__ mappings__': {}, '__table__': 'user', '__primary_key__': None, '__module__': '__ main__', '__doc__': None, '__fields__': [], 'id': <orm.StringField object at 0x0 0000000048999E8>})
In [7]: a.__mappings__ Out[7]: {}
從這里可以看到,是以{'id':object,'age':object}向上傳遞到元類的attr,最后在__new__中處理后,重新傳遞給__init__初始化。