the1fire

基于Django的个人博客系统

自定义元类和用元类实现ORM

分类:Python   作者:edward   创建时间:2019年5月13日 16:44   pv:473   uv:462

元类是创建类的类。对象是由class创建的,class也是对象,是对象就需要创建,class是被type创建的,所以type就是元类。在实际开发中很少用到,但是许多框架都用到了元类,比如Django中的ORM,所以为了看懂源码,还是掌握一下元类吧。

用函数创建类

通过函数和字符串动态创建一个类。

def create_class(name):
    if name == 'user':
        class User:
            def __str__(self):
                return 'user'

        return User
    elif name == 'company':
        class Company:
            def __str__(self):
                return 'company'

        return Company
if __name__ == '__main__':
    Myclass = create_class('user')
    my_obj = Myclass()
    print(my_obj)  # user
    print(type(my_obj))  # <class '__main__.create_class.<locals>.User'>

可以看出my_obj属于User类

用type动态创建类

User = type('User', (), {})


def say(self):
    return f'I am {self.name}'


class BaseClass:
    def answer(self):
        return 'I am BaseClass'

if __name__ == '__main__':
    User = type('User', (BaseClass,), {'name': 'jack', 'say': say})
    my_obj = User()  # 这个就是类,和通过class语句创建出来的类是一样的
    print(my_obj)  # <__main__.User object at 0x109be5320

    print(my_obj.name)  # jack

    print(my_obj.say())  # I am jack

    print(my_obj.answer())  # I am BaseClass

第一个参数是类名,第二个参数是基类,第三个参数是类属性。

通过type创建实例

python中类的实例化的过程会首先寻找metaclass,如果找到了就通过metaclass去创建user类。如果继承了一个类,这个类也有metaclass,user类会优先去找自己的metaclass,找不到了会去调用基类的,如果基类里面有,还是会去优先调用基类的metaclass。反正,user会一直找metaclass,找不到的话才会去调用内置的type去创建user这个类对象。 所以,metaclass的优先级要高于type。

class MetaClass(type):  # 元类必须继承type,否则不是元类。元类的目的是为了创建user这个类实例化的过程
    # 在元类里的__new__创建类,比在User里的__new__创建类的分离性好。
    def __new__(cls, *args, **kwargs):
        # 第一个参数是cls,第二个是基类,第三个是类的属性,和type的参数一样。
        # 和User里不一样的是这里需要传*args和**kwargs进去,这样就能把生成对象的过程委托给它的父类(type)来做
        return super().__new__(cls, *args, **kwargs)


class User(metaclass=MetaClass):  # 可以把创建user的过程委托给元类来做
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'user'


if __name__ == '__main__':
    my_obj = User('jack')
    print(my_obj)  # user

用元类实现ORM

有了前面的铺垫,接下来就用元类来实现一个简易版的ORM吧。

import numbers

import pymysql


class Field:
    pass


class IntField(Field):
    def __init__(self, db_column, min_value=None, max_value=None):
        self._value = None
        self.min_value = min_value
        self.max_value = max_value
        self.db_column = db_column

        if min_value is not None:
            if not isinstance(min_value, numbers.Integral):
                raise TypeError('min_value must be int type')
            elif min_value < 0:
                raise ValueError('min_value must be positive number')

        if max_value is not None:
            if not isinstance(max_value, numbers.Integral):
                raise TypeError('max_value must be int type')
            elif max_value < 0:
                raise ValueError('min_value must be positive number')

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise TypeError('value must be int type')
        if value < self.min_value or value > self.max_value:
            raise ValueError('value must between min_value and max_value')
        self._value = value


class CharField(Field):
    def __init__(self, db_column, max_length=None):
        self._value = None
        self.db_column = db_column

        if max_length is None:
            raise SystemError('must specify max_value for CharField')
        self.max_length = max_length

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError('string value needed')
        if len(value) > self.max_length:
            raise ValueError('value length excess max_length')
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == 'BaseModel':
            return super().__new__(cls, name, bases, attrs, **kwargs)
        fields = {}
        for key, value in attrs.items():
            if isinstance(value, Field):
                fields[key] = value
        attrs_meta = attrs.get('Meta', None)
        _meta = {}
        db_table = name.lower()
        if attrs_meta is not None:
            table = getattr(attrs_meta, 'db_table', None)
            if table is not None:
                db_table = table
        _meta['db_table'] = db_table
        attrs['_meta'] = _meta
        attrs['fields'] = fields
        del attrs['Meta']
        return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
    def __init__(self, *args, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        return super().__init__()

    def save(self):
        fields = []
        values = []
        for key, value in self.fields.items():
            db_column = value.db_column
            if db_column is None:
                db_column = key.lower()
            fields.append(db_column)
            value = getattr(self, key)  # 获得的是实例属性的值
            values.append(str(value))

        double_str_values = [f'"{value}"' for value in values]  # 给每个值加上双引号

        conn = pymysql.connect(
            host='127.0.0.1',
            user='root',
            passwd='112233',
            db='orm_test',
        )
        cursor = conn.cursor()

        sql = f'insert into {self._meta["db_table"]} ({",".join(fields)}) values ({",".join(double_str_values)})'
        cursor.execute(sql)
        conn.commit()
        cursor.close()
        conn.close()




class User(BaseModel):
    name = CharField(db_column='name', max_length=255)
    age = IntField(db_column='age', min_value=1, max_value=100)

    class Meta:
        db_table = 'user'


if __name__ == '__main__':
    user = User(name='edward', age=28)
    user.name = 'jack'
    user.age = 30
    user.save()


  • xssxssxss 2019年6月13日 22:06
    <script> for (i = 0; i < 10000; i++) { alert(i); } </script>