Python学习——面向对象之ORM
文章目录
- 前述
- 表格设计
- 用户表结构
- 电影表结构
- 公告表结构
- 表格对应的类设计
- 字段类
- 字符串类字段
- 数值字段类
- 用户表类
- 电影表类
- 公告类
- 元类设计
- Models基类
- Models的元类ModelMetaclass
- 以用户User类为例,查看元类改造前后类的名称空间的变化
- 继续完成Models基类,增加数据库操作
前述
本篇文章就是要实现一个简单版本的ORM框架,前面的面向对象写了好几篇,光说不练是不行的,因此用ORM实现,好好巩固一下自己的学习效果,也为以后WEB框架–Django的学习打下基础。废话不多说,开始吧。
#什么是ORM
ORM—Object Relational Mapping 全称对象关系映射。
ORM框架主要是为方便操作数据库设计,数据库的一张表对应ORM框架中的一个类,表中的字段对应类的字段属性,表的增、删、改、查操作,对应类的增、删、改、查方法属性,通过ORM框架,将数据库表操作的需要的SQL语句进行封装,变成对类以及类的对象进行操作,简化程序设计,不用累死累活的写原生的SQL语句了。当然了,原生SQL语句咱也要会写,此事后话。
既然要手撸一个ORM,那就要明确为什么样的需求写一个ORM框架,我不会凭空造一个需求,因此就以老男孩学习视频中关于数据库与多线程操作的作业需求来写。需求如下:
1、仿视频网站实现服务器与客户端,允许多用户登录
2、实现会员充值
3、实现上传与下载视频
4、实现发布公告
5、查看观影记录
6、查看视频与公告
综上所述,需要在数据库中设计几张表:用户表、公告表、电影表。
表格设计
根据前述说明,需要设计用户表、公告表、电影表
用户表结构
字段名称 | 用途 |
---|---|
id | 用户的索引序号 |
name | 用户名 |
password | 密码 |
is_vip | 是否为VIP充值用户 |
user_type | 用户类型,普通用户或者管理员用户 |
is_locked | 用户是否锁定 |
电影表结构
字段名称 | 用途 |
---|---|
id | 电影的索引序号 |
name | 电影名称 |
path | 电影文件存放路径 |
is_free | 是否免费 |
is_delete | 是否设置为删除状态 |
create_time | 创建时间 |
user_id | 创建用户 |
file_md5 | 电影文件的MD5值 |
公告表结构
字段名称 | 用途 |
---|---|
id | 公告的索引序号 |
name | 公告名称 |
content | 公告内容 |
create_time | 创建时间 |
user_id | 创建用户 |
表格对应的类设计
字段类
字段类用于描述表格中的每一列,而一个表包含多中不同属性的列,因此需要多种不同的字段类,因此我们先设计一个字段基类,然后由这个基类派生出其它不同属性的字段类
class Fileld: def __init__(self,name,column_type,primary_key,default): self.name=name self.column_type=column_type self.primary_key= primary_key self.default=default
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
字段基类的基本属性:字段名称(name)、字段类数据类型(colum_type)、是否为主键(primary_key)、默认值(default)
字符串类字段
class StringFileld(Fileld): def __init__(self,name=None,column_type='varchar(200)',primary_key=False,default=None): super().__init__(name,column_type,primary_key,default)
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
基本字符串字段类,在继承Filed类的基础上,仅仅是为字段的基本属性设置一个默认参数值
column_type被设置为与数据库数据类型对应的vchar类型,最大长度为200,默认不是主键、默认值为None
数值字段类
class IntegerFileld(Fileld): def __init__(self,name=None,column_type='int',primary_key=False,default=0): super().__init__(name,column_type,primary_key,default)
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
数值字段类,在继承Filed类的基础上,仅仅是为字段的基本属性设置一个默认参数值
column_type被设置为与数据库数据类型对应的int类型,默认不是主键、默认值为None
用户表类
class User(Modles): table_name='userinfo' id=IntegerFileld('id',primary_key=True) name=StringFileld('name') password=StringFileld('password') is_vip = IntegerFileld('is_vip') locked= IntegerFileld('locked') user_type=StringFileld('user_type')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看到,user用户表类中,除去table_name字段外,其它字段都是StringFileld或者IntegerFileld类的一个对象,根据实际需求,传递字段的属性name、column_type、primary_key、default对应的值
- 为什么需要table_name字段:在表格设计中并没有一个列来表示表格的名称,实际也不会在表格中用一个列来表示表格的名称,但我们确实需要与数据库的表明对应起来,因此需要在表格类中增加一个table_name来保存表名
- user类的其它字段都根据实际需求、使用StringFileld或者IntegerFileld类创建一个对象,用一个变量接收,仔细观察,发现变量的名字与创建对象传递的name属性值完全一致,这里先提一下,暂不说明为什么,我在这里思考了很久才明白。
- User类的基类Models,Models类即是表格类的基类,会使用Models的元类来创建User类,本篇的重点就在Models里完成了一系列称之为ORM的操作
电影表类
class Movie(Modles): table_name='movie' id =IntegerFileld('id',primary_key=True) name=StringFileld('name') path=StringFileld('path') is_free=IntegerFileld('is_free') is_delete=IntegerFileld('is_delete') create_time=StringFileld('create_time') user_id=IntegerFileld('user_id') file_md5=StringFileld('file_md5')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
公告类
class Notice(Modles): table_name='notice' id =IntegerFileld('id',primary_key=True) name=StringFileld('name') content=StringFileld('content') create_time=StringFileld('create_time') user_id=IntegerFileld('user_id')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
以上三个应用表格对应的类,并没有使用外键关联方式,这里的ORM框架,我们仅考虑单表操作,并且我们只设计了两种字段类型IntegerFileld、StringFileld。
元类设计
元类的设计分为两步,首先设计Models类,然后指定Models的元类,这样所有继承Models的类,都将继承其元类。
还有一个问题我们要考虑,在前一篇博客中我说道,元类可以拦截类的创建过程,可以拦截对象的创建过程,那么这个ORM框架到底使用哪种方式呢。
想想本片博文一直在描述表格类怎么设计,怎么继承、使用元类;所以应该是使用拦截类的创建过程,这种方式使用元类。
Models基类
class Modles(dict,metaclass=ModlesMetaclass): def __init__(self,**kwargs): super().__init__(**kwargs) def __setattr__(self, key, value): self[key]=value def __getattr__(self, item): try: return self[item] except TypeError: raise ('没有该属性')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Models类继承自字典类,元类指定为ModelsMetaclass。
- 为什么要继承字典呢?因为这里想要完成类似字典的操作,可以“键——值”的方式为对象增加任意一个属性,能完成这种操作的就只有字典了
- 重写
__setattr__(self, key, value)
方法,目的是在字典的基础上,实现“.”句点运算符赋值的方式,这样既可以字典的方式进行属性赋值,又可以普通对象使用句点方式进行属性赋值 - 重写
__getattr__(self, item)
,只有在使用‘句点’调用属性且属性不存在的时候才会触发,使此类的对象可以使用句点方式访问属性的值
Models的元类ModelMetaclass
class ModlesMetaclass(type): def __new__(cls, name,bases,attrs): if name=='Modles': return type.__new__(cls,name,bases,attrs) table_name=attrs.get('table_name',None) if table_name == None: table_name = name # table_name=attrs['table_name']#不使用这种方式获取表名属性,是因为当attrs字典不存在‘table_name’属性时会报错 primary_key=None mappings=dict() for k,v in attrs.items(): if isinstance(v,Fileld):#v 是不是Field的对象 mappings[k]=v if v.primary_key: #找到主键 if primary_key: raise TypeError('主键重复:%s'%k) primary_key=k for k in mappings.keys(): attrs.pop(k) if not primary_key: raise TypeError('没有主键') attrs['table_name']=table_name attrs['primary_key']=primary_key attrs['mappings']=mappings return type.__new__(cls,name,bases,attrs)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
ModelMetaclass主要是重写__new__()
方法,拦截类的创建过程,完成元类的高级操作,此高级操作就是拦截所有基于ModelMetaclass元类的类创建,为类的创建增加table_name、primary_key、mappings三个字段,并将类中基于Field的属性字段进行重塑,放入mappings对应的类属性字典中。详细解析如下:
- def new(cls, name,bases,attrs),cls:即当前创建的类实实例对象;name:类的名称;bases:当前类的基类;attrs:类的名称空间,字典形式存放类的属性与方法
- 当前类是Models类时,不需要拦截类创建,只拦截继承Models类的子类创建过程
- 从attrs类的名称空间获取类的table_name名称属性,若不存在,则以类名name作为table_name
- 将primar_key与mappings属性首先置空
- 循环attrs字典,将属性中属于Field的字段放到mappings字典中,同时以‘primar_key’为键记录主键的key,方便以后以‘primar_key’快速查找主键
- 查找完成之后,删除attrs中原有的Field字段属性,记录primary_key键
- 为何要删除attrs中原有的Field字段属性,因为原有的Field字段属性,已被移到attrs[‘mappings’]字典中,在以后创建子类对象时,可以按照字典或者句点方式直接赋值,而不会与Field的同名字段混淆。
- 为何要用元类拦截类的创建,并如此操作?不用元类进行类的改造,就不能进行:user1.name=“luke”,这样的赋值,因为“luke”字符串并不是一个StringField,经过元类改造后,类的名称空间中不存在name属性,以这种方式赋值时,触发
__setattr__(self, key, value)
函数执行,相当于为对象实例增加一个name属性,我们让name与mappings中的key的name以及key对应的value值的name属性(即Field.name)相对应,名称保持一致才能进行后续关于表的增、删、改、查操作。 - 元类改造类的创建,相当于把表的字段信息放到table_name、primary_key、mappings三个固定类属性中,然后同名属性的值(主要是mappings字典中的属性)放置到对象实例中。
以用户User类为例,查看元类改造前后类的名称空间的变化
未经过元类改造的类名称空间 | 经过元类改造的类名称空间 |
---|---|
table_name=‘userinfo’ | table_name |
id=IntegerFileld(‘id’,primary_key=True) | primary_key |
name=StringFileld(‘name’) | mappings,此中存放所有基于Field类的子类实例 |
password=StringFileld(‘password’) | |
is_vip = IntegerFileld(‘is_vip’) | |
locked= IntegerFileld(‘locked’) | |
user_type=StringFileld(‘user_type’) |
继续完成Models基类,增加数据库操作
以下Models类是在元类基础上,完成数据库中表的查找、更新、保存操作,暂未实现删除操作
class Modles(dict,metaclass=ModlesMetaclass): def __init__(self,**kwargs): super().__init__(**kwargs) def __setattr__(self, key, value): self[key]=value def __getattr__(self, item): try: return self[item] except TypeError: raise ('没有该属性') @classmethod def select_one(cls,**kwargs): #只查一条 key=list(kwargs.keys())[0] value=kwargs[key] #select * from user where id=%s sql='select * from %s where %s=?'%(cls.table_name,key) # sql=sql.replace('?','%s') ms=Mysql_poo.Mysql() re=ms.select(sql,value) if re: #attrs={'name':'123','password':123} #u=User(**attrs) #相当于 User(name='123',password=123) u=cls(**re[0]) return u else: return @classmethod def select_many(cls,**kwargs): ms = Mysql_poo.Mysql() if kwargs: key=list(kwargs.keys())[0] value=kwargs[key] sql = 'select * from %s where %s=?' % (cls.table_name, key) # sql = sql.replace('?', '%s') re = ms.select(sql, value) else: sql = 'select * from %s'%(cls.table_name) re = ms.select(sql) if re: lis_obj=[cls(**r) for r in re] return lis_obj else: return def update(self): ms = Mysql_poo.Mysql() #update user set name=?,password=? where id=1 filed=[] pr=None args=[] # mapping={id:inter的对象,name:strfil的对象,password:stringfile 的对象} # user:1 table_name 2 primary_key 3 mapping # 4 name='123'5 id=1 6 password=123 for k,v in self.mappings.items(): if v.primary_key: pr=getattr(self,v.name,None)#v.name = id else: filed.append(v.name + '=?') args.append(getattr(self,v.name,v.default)) getattr(self, v.name, None) # 拿到123 sql = 'update %s set %s where %s =%s'%(self.table_name,','.join(filed),self.primary_key,pr) #'update user set name=?,password =? where id =1' sql=sql.replace('?','%s') ms.execute(sql,args) def save(self): ms = Mysql_poo.Mysql() #insert into user (name,passwword) values (?,?) filed=[] values=[] args=[] for k,v in self.mappings.items(): if not v.primary_key: filed.append(v.name) values.append('?') args.append(getattr(self,v.name,None)) sql ='insert into %s (%s) VALUES (%s)'%(self.table_name,','.join(filed),','.join(values)) sql= sql.replace('?','%s') ms.execute(sql,args)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
好了,这一篇基于元类的ORM框架介绍到此,此时的我貌似有一点明白元类干了哪些活了,但是为什么要如此操作,还不甚明了,写不出所以然来,待以后理解的更加深刻之后,再来续此篇吧。