从ORM说Python的元类

探究ORM里面元类的作用何在

Posted by mengxun on September 11, 2018

ORM

ORM就是 Object-relational mapping 的缩写,翻译过来就是 对象-关系 映射,其实就是把数据库的那一套在代码层面给定义封装一下,这样就直接操作代码即可,不用再去写那些SQL语句了,会比较方便。python比较出名的ORM有两个:djangoSQLAlchemy ,另外最近还发现有个小的 ORM peewee,没事可以研究一下

ORM实现

参考着网上的资料可以实现一个简单的 ORM 效果如下:

class User(Model):
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

class Post(Model):
    id = IntegerField('id')
    title = StringField('title')
    date = StringField('content')
    author = StringField('author')

u = User(id=123, name='Michael', email='test@orm.org', password='my-pwd')
u.save()

p = Post(id=456, title='I Have a Dream', date='August 28, 1963', author='Martin Luther King')
p.save()

当然这只是想达到的效果, 要实现的话 首先要有字段类:

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)

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')

这里每个字段类都对照着一种数据库的字段类型, 然后还要有一个让用户自定义的类可以继承的类,里面会有相关操作并可以把用户的定义的这些字段类最终转化为 SQL 语句.(下面先照我自己的想法写,并没有先用资料里 metaclass 的方式,这样做的目的是写完之后可以去比较,这样才能明白意义在哪里)

先定义一个BaseModel:

class BaseModel:
    def __new__(cls, *args, **kwargs):
    	print("call")
        mappings = dict()
        for k, v in cls.__dict__.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            delattr(cls, k)
        setattr(cls, "__mappings__", mappings)
        setattr(cls, "__table__", cls.__name__)
        return super().__new__(cls, *args, **kwargs)

BaseModel 的作用主要是 记录字段和表名,然后 Model 继承 BaseModel:

class Model(BaseModel, dict):

    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))

执行上面的代码也可以将 代码 转化为 SQL 语句,实现 ORM 的效果。

现有的问题

那现在问题来了,既然这样可以通过继承实现功能,那为什么还需要元类,因为用继承的话会有两个缺点:

  • 多继承的情况下,BaseModel 必须放在最前面,否则它的 __new__ 方法就用不上了
  • 每次自定义的类继承 Model 然后实例化的时候,都会重复调用 __new__ 方法

解决方法

解决方法当然就是元类了,因为元类是在类创建的时候就生效的,即使这个类没被实例化,元类的 __new__ 方法就已经被执行生效了,等到自定义的类实例化的时候就不会再重复调用,因为自定义类已经在创建的时候动态绑定了 __new__ 方法,绑定之后这个 __new__ 方法就不需要在实例化自定义类的时候重复调用了。

用元类改造之后的代码如下:

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        print("call")
        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 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))

其实元类和继承最大的区别就是:

  • 继承是在类实例化具体对象的时候才会调用 __new__ 方法
  • 元类是在定义类的时候 __new__ 方法就已然生效,因为元类就是定义类的,生效之后在实例化对象之时便不会再调用 __new__ 方法,这个 __new__ 方法直接对所有的实例对象生效(相当于提前作用于源头,后面就不需要调了)

另外,元类里的 __new__ 方法的参数不但有类本身,还有 name (等价于 cls.__name__ ), attrs (等价于 cls.__dict__ ),另外还有 bases,这是一个 tuple,包含了 Model 类继承的父类,有了这些参数,操作起来会更加方便清晰。