博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
12、元类(metaclass)实现精简ORM框架
阅读量:5781 次
发布时间:2019-06-18

本文共 8672 字,大约阅读时间需要 28 分钟。

metaclass,直译为元类,简单的解释就是:

先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。

先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

1 # -*- coding: utf-8 -*- 2  3 # metaclass是创建类,所以必须从`type`类型派生: 4 class ListMetaclass(type): 5     def __new__(cls, name, bases, attrs): 6         attrs['add'] = lambda self, value: self.append(value) 7         return type.__new__(cls, name, bases, attrs) 8  9 # 指示使用ListMetaclass来定制类10 class MyList(list, metaclass=ListMetaclass):11     pass12 13 L = MyList()14 L.add(1)15 L.add(2)16 L.add(3)17 L.add('END')18 print(L)

1、先定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这个一个metaclass。metaclass是创建类,所以必须从type类型派生;

2、有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass。因为MyList是定制化的List,所以需要继承父类list;

3、__new__()方法接收到的参数依次是:

(1)当前准备创建的类的对象;

(2)类的名字;

(3)类继承的父类集合;

(4)类的方法合集。

4、Python和c语言不同,所有的类都是运行时动态创建的。因此创建类MyList时,首先执行该类的定义(第10行至第11行),然后在定义中寻找是否有metaclass,有的话,将参数(name:MyList,bases:list,attrs:系统定义的一些参数)发送至ListMetaclass执行,生成MyList类,同时新定制的类具有add方法。

 

ORM

先贴出廖雪峰Python教程元类的简单ORM框架的代码,然后进行分析:

1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3  4 ' Simple ORM using metaclass ' 5  6 class Field(object): 7  8     def __init__(self, name, column_type): 9         self.name = name10         self.column_type = column_type11 12     def __str__(self):13         return '<%s:%s>' % (self.__class__.__name__, self.name)14 15 class StringField(Field):16 17     def __init__(self, name):18         super(StringField, self).__init__(name, 'varchar(100)')19 20 class IntegerField(Field):21 22     def __init__(self, name):23         super(IntegerField, self).__init__(name, 'bigint')24 25 class ModelMetaclass(type):26 27     def __new__(cls, name, bases, attrs):28         if name=='Model':29             return type.__new__(cls, name, bases, attrs)30         print('Found model: %s' % name)31         mappings = dict()32         for k, v in attrs.items():33             if isinstance(v, Field):34                 print('Found mapping: %s ==> %s' % (k, v))35                 mappings[k] = v36         for k in mappings.keys():37             attrs.pop(k)38         attrs['__mappings__'] = mappings # 保存属性和列的映射关系39         attrs['__table__'] = name # 假设表名和类名一致40         return type.__new__(cls, name, bases, attrs)41 42 class Model(dict, metaclass=ModelMetaclass):43 44     def __init__(self, **kw):45         super(Model, self).__init__(**kw)46 47     def __getattr__(self, key):48         try:49             return self[key]50         except KeyError:51             raise AttributeError(r"'Model' object has no attribute '%s'" % key)52 53     def __setattr__(self, key, value):54         self[key] = value55 56     def save(self):57         fields = []58         params = []59         args = []60         for k, v in self.__mappings__.items():61             fields.append(v.name)62             params.append('?')63             args.append(getattr(self, k, None))64         sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))65         print('SQL: %s' % sql)66         print('ARGS: %s' % str(args))67 68 # testing code:69 70 class User(Model):71     id = IntegerField('id')72     name = StringField('username')73     email = StringField('email')74     password = StringField('password')75 76 u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')77 u.save()

 

对上面代码进行分析:

编写底层模块的第一步,就是先把调用接口写出来,然后,根据此接口编写代码实现功能。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码(第70行至第77行):

class User(Model):    # 定义类的属性到列的映射:    id = IntegerField('id')    name = StringField('username')    email = StringField('email')    password = StringField('password')# 创建一个实例:u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')# 保存到数据库:u.save()

其中,父类Model和属性类型IntegerField、StringField是由ORM框架提供的,剩下的魔术方法save()全部由metaclass自动完成。

注意到User是一个(对应数据库中的表User),其属性是对象(对应数据库表中的字段)。

虽然metaclass的编写会比较复杂,但ORM的使用者使用起来却异常简单。

 

现在,我们就按照上面的接口来实现该ORM。

 

首先来定义Field类,它负责保存User类中属性 对应的 数据库表的字段信息(字段名和字段类型)

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)

注意: __str__()是Python中有特殊用途的函数,用来定制类。

    如果我们定义了Field类后,打印它的一个实例: print(Field(id, 'bigint')),则会打印出一堆:<__main__.Field object at 0x109afb190>,不好看。

       那么怎样才能打印的好看一点呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了。

       像Field类中的__str__(),打印的信息不仅好看,还能看到实例内部重要的数据。

 

在Field的基础上,进一步定义各种类型的Field,比如IntegerField、StringField等等:

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

注意:StringField和IntegerField生成对象,会调用父类Field中的__init__()进行初始化。

 

下一步,就是编写复杂的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)

注意: 元类(metaclass)继承自type,对元类的具体讲述可参考上篇博文()。

1、首先进行判断,如果将要创建的类是Model,无需做个性化定制,直接通过type创建,排除对Model类的修改;

2、打印要创建的类的类名;

3、生成一个Dict对象mappings,保存User类属性和数据库字段的映射关系;

4、循环读取User类的属性,程序调试后看起来比较直观:

   可以看到User类共有6个属性,其中id、name、email和password属性均是对象(对象保存的是该属性对应的字段信息:字段名和字段类型)。User类的id属性对应数据库表User中的字段(字段名id, 类型'bigint'),name属性对应数据库表User中的字段(字段名username,类型'varchar(100)),email属性对应数据库表User中的字段(字段名email,类型'varchar(100)'),paswword属性对应数据库表User中的字段(字段名password,类型‘varchar(100)')。牢记一点,就如User中的定义一样,key是User类中的属性,value是对应的字段信息(Field类型)。

        在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中。

5、在类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性)。

6、给类添加属性__mappings__,保存的是类属性和数据库字段的对应关系;

7、给类添加属性__table__,保存的是表名;

8、动态创建类。

 

编写基类Model:

1 class Model(dict, metaclass=ModelMetaclass): 2  3     def __init__(self, **kw): 4         super(Model, self).__init__(**kw) 5  6     def __getattr__(self, key): 7         try: 8             return self[key] 9         except KeyError:10             raise AttributeError(r"'Model' object has no attribute '%s'" % key)11 12     def __setattr__(self, key, value):13         self[key] = value14 15     def save(self):16         fields = []17         params = []18         args = []19         for k, v in self.__mappings__.items():20             fields.append(v.name)21             params.append('?')22             args.append(getattr(self, k, None))23         sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))24         print('SQL: %s' % sql)25         print('ARGS: %s' % str(args))

 注意:动态创建Model时,在类定义中寻找到了metaclass,于是用元类来创建Model。由于在元类中,有判断语句,如果类名时Model,则直接创建,于是不做任何修改生成Modle类。

 

结合下面的测试代码来具体分析Model类:

1 class User(Model):2     id = IntegerField('id')3     name = StringField('username')4     email = StringField('email')5     password = StringField('password')6 7 u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')8 u.save()

 1、u=User(...)  创建User类的对象,但本身没有构造函数,于是使用父类Model的构造函数__init()进行初始化。

      又因为Model继承自dict,所以super(Model, self).__init__(**kw)使用dict的构造函数进行初始化。

      在Python官方文档中,dict对象的创建这样描述:

     

      dict的创建可以通过:(1)key:value键值对;(2)构造器

   

     所以u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')执行后,u是一个dict类型的对象:

     

      这不仅是User类的一个对象,映射到数据库表User中,即是表中的一行记录。

2、对象可调用__getattr__和__setattr__方法读取修改属性值,即时对表中记录的某个字段进行读取和修改;

3、接下来分析save函数:

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

Mysql中insert的用法,举个例子参照下:

cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])

 

fields保存类属性对应的表字段名;

params保存 ‘?’,在实际中会在特定方法中根据成数据库的类型,被替换成不同的占位符,Mysql的SQL占位符是 %s;

args保存类属性对应的表一行记录的值。

 

所以,我们从始至终一直在强调一个概念:

创建User类时,定义了类的属性(id,name,email,password),同时映射到对应数据库表中字段的信息,映射关系保存在__mappins__中,这是类的属性!

根据User类创建对象时,对象的属性值映射到对应数据库表中的一行记录,这是对象的属性!

所以总结下:User的属性(id,name,email,password):

                   (1)与数据库列名(字段)的映射关系保存在__mappins__中;

                   (2)与数据库一行记录的值,在创建User类的对象时赋值。

posted on
2018-03-23 14:59 阅读(
...) 评论(
...)

转载于:https://www.cnblogs.com/zwb8848happy/p/8630347.html

你可能感兴趣的文章
在SpringMVC利用MockMvc进行单元测试
查看>>
Nagios监控生产环境redis群集服务战
查看>>
Angular - -ngKeydown/ngKeypress/ngKeyup 键盘事件和鼠标事件
查看>>
Android BlueDroid(一):BlueDroid概述
查看>>
Java利用httpasyncclient进行异步HTTP请求
查看>>
宿舍局域网的应用
查看>>
html代码究竟什么用途
查看>>
Hadoop HDFS编程 API入门系列之路径过滤上传多个文件到HDFS(二)
查看>>
Python version 2.7 required, which was not foun...
查看>>
context:annotation-config vs component-scan
查看>>
exgcd、二元一次不定方程学习笔记
查看>>
经典sql
查看>>
CSS3边框会动的信封
查看>>
JavaWeb实例设计思路(订单管理系统)
查看>>
source insight中的快捷键总结
查看>>
PC-IIS因为端口问题报错的解决方法
查看>>
java四种线程池简介,使用
查看>>
ios View之间的切换 屏幕旋转
查看>>
typedef BOOL(WINAPI *MYFUNC) (HWND,COLORREF,BYTE,DWORD);语句的理解
查看>>
jsp 特殊标签
查看>>