天天看点

用元类实现ORM

正常来说实现用代码往数据库存储映射数据是要针对每一个模型类去考虑的,这使得创建过程极其死板而不够动态。不过python是门动态语言,类的创建也能动态得不要不要的,而动态创建类有一个工具类叫元类,简单来说对象由类创建,类对象由元类属性__metaclass__创建,如果这个类和往上的父类都找不到这个属性那就由type创建这个类了,也就说对任何一个没有指定__metaclass__的类X的实力对象x来说,他的x.__class__.__class__.__name__的值都是type。所以我们可以自己定义这个属性在生成类的时候搞一些事情,从而达到动态创建类的效果。

要映射字段和数据得先来个字段基类

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
           

再来点儿普通的字段类,这两个类就是我们在各web框架中熟悉的'某字段=XField',定义字段的套路

class CharField(Field):
    def __init__(self, name)
        super(CharField, self).__init__(name, 'varchar')
class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'int')
           

有了这些,接下来就到了元类出场了

class ModelMetaclass(type):
    
    def __new__(cls, name, bases, attrs):
        
        map = dict()
        #一边保存映射关系,一边删除保存过了的
        for k, v in attrs.items():
            if isinstance(v, Field):
                map[k] = v
        for k in map.keys():
            attrs.pop(k)

        attrs['__map__'] = map 
        attrs['__table__'] = name 
        return type.__new__(cls, name, bases, attrs)
           

接下来就是Model了,不要忘了指定metaclass

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

    # 真正的操作数据库代码,可以自己写CRUD各方法
    def save(self):
        fields = []
        argsPosition = []
        args=[]
        for k, v in self.__map__.items():
            fields.append(v.name)
            argsPosition.append('%s')
            args.append(getattr(self,k, None))

        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(argsPosition))%tuple(args)
        print(sql)
           

接下来随便定义个类做测试就完了

class User(Model):
    name = CharField('name')
    age = IntegerField('age')
u=User(name='Lic',age=18)
u.save()