ORM之youku项目小练习(上)

 

 

什么是ORM

问题

如果我们具体操作数据库的时候,是不是很麻烦,每次都要和复杂的SQL语句打交道?

解决

Object-Relationl Mapping,作用就是关系型数据库和对象之间的一个映射,

这样子我们只需要像平时操作对象一样操作它就可以了.极大的增大了开发效率

缺点

sql封装固定,不利于SQL查询优化

具体的东西就不一一展示了,具体请看我转发的一篇

[文章]  https://www.cnblogs.com/jhpy/articles/11611468.html 

 

ORM对象关系映射

关系映射

'''MySQL:                python:
  表名     ---->       类名
  一条记录   --->       对象
  字段       --->       对象.属性'''

关系分析

上面关系分析:
通过python中创建类来实现对数据库一张表结构的关系产生一种一一对应关系
通过python中对创建的类实例化的对象操作对数据库一张表进行表记录的操作的一一对应关系
通过python中对象点属性的方式来操作记录表中某一字段的对应值,的一一对应操作关系

 

步骤

首先它围绕着这MySQL做的

创建一张User表,表内含有字段(id,name,pwd),

 

class User:
   def __init__(self,id,name,pwd):
       self.id=id
       self.name=name
       self.pwd=pwd

问题1 表内是有字段类型的

但是我们的表内是有字段类型的,所以应该先写字段类型类,仿优酷系统只需要两种类,一种是int,另一种是varchar

  1. 写字段类型类

  • 字段名

  • 字段类型(长度)

    • -varchar(64)

    • int

  • 是否为主键

  • 默认值

先写int类

#需要先给字段3个默认值column_type,primary_key,default
class IntegerField:
   def __init__(self,name,column_type='int',primary_key=False,default=None):
       self.name=name
       self.column_type=column_type
       self.primary_key=primary_key
       self.default=default

写完int.类再写string varchar()类 是不是觉得有点复杂了,

问题2,如果多一点的类型怎么办?

  • 解决:

    • 继承一个Models父类.

#父类
class Field:
   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

#两个字段类型类
#整型
class IntegerField:
   def __init__(self,name,column_type='int',primary_key=False,default=None):
       super().__init__(name,column_type,primary_key,default)
#字符串
class StringField:
   def __init__(self,name,column_type='varchar(64)',primary_key=False,default=None):
       super().__init__(name,column_type,primary_key,default)

仿优酷可以干些什么?

看电影,那么有个电影表类,还有公告表....

'''写表类
  User:
      user_name
      pwd

  Movie:
      movie_name
      movie_size

  Notice:
      title
      content'''

 

问题3.假设100张表就需要写100个__init__

  • 解决那么我们就写在一个父类里面 继承一个Models父类

但是又有问题了,即使我们想写父类也有限制,问题如下:

问题4:每张表的字段名与字段数量不同,导致没法直接继承Models父类

  • 解决:

    • dict是对象,继承dict,触发字典内部的__init__(因为它具备可接受任意数量以及任意类型属性的特性),

       

    验证:

原理解析:

dict是对象,(Python中一切皆为对象) 可以用来传任意关键字参数**kwargs ,形参中的**会将溢出的关键字实参全部接收,然后存储字典的形式,然后把字典赋值给**。key:value形式,可自行查看源码

解决问题的方法总比 问题多,,

问题5 字典的取/存值方式有限,希望改为 对象.属性取值, 对象.属性=值 存值的方式。

解决:

点拦截, __getattr__:取值方式

__setattr__:存值方式

 

为何要实现这个目的?因为我们通过pymysql模块实现操作数据库返回来的数据类型基本都是字典类型外面
  # 套列表的形式,那么如果想办法将查询的结果也变成一个字典对象,那么查询里面的key(字段名)和value(字段记录值)
  # 就特别方便了,同时在新增和插入数据时候会用到这个方法,达到更简单明了的目的。

代码实现


#父类
class Field:
   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

#两个字段类型类
#整型
class IntegerField:
   def __init__(self,name,column_type='int',primary_key=False,default=None):
       super().__init__(name,column_type,primary_key,default)
#字符串
class StringField:
   def __init__(self,name,column_type='varchar(64)',primary_key=False,default=None):
       super().__init__(name,column_type,primary_key,default)


class Models(dict):
   def __init__(self,**kwargs):
       super().__init__(**kwargs)

   #__getattr__:在对象.属性的时候 ,属性没有触发的时候直接来到这边
   def __getattr__(self, item):
       #给他返回值
       return self.get(item)

   def __setattr__(self, key, value):
       self[key]=value


#先创建一张User表,表内有字段(id,name,pwd)
class User:
   def __init__(self,id,name,pwd):
       self.id=id
       self.name=name
       self.pwd=pwd

 

 

问题5

 主键是唯一的,表里面只能有一个,开发者不知道用户到底有没有写这个约束,

用户可以自定义一个表名,  

通过某种方式限制我们的表类必须按照我们的约束去设置

解决通过自定义元类去控制累的创建,使其遵循以上的约束.

代码实现

元类的编写--->必须要有表名,唯一主键获取所有字段

开始拦截,约束子类的创建(表结构映射的创建)

我们应当知道一件事,User表中可能还有表的名字,或者其他的,只要不是父类Field里面东西我们都不要

还可以创建一个字典,将所有在父类的东西都放里面

开始拦截类的创建(表结构映射的创建)
class MyMeta(type):
   def __new__(cls, class_name, class_bases, class_attrs):
       
       # 可以将存在父类的子类的属性集中在一个字典中,这个字典我们起个名字:mappings
       # # __new__拦截了哪些类的创建:Models、Models的子类,很显然Models类我们无需拦截,因为我们创建表结构映射的类
       # 并不是Models,而应该是继承了Models的一个类,所以需要排除Models
       if class_name == 'Models':
           return type.__new__(cls, class_name, class_bases, class_attrs)
       # 开始部署自定义的类属性:
       # 表名:我们在创建类体代码的时候会设置个属性叫table_name=***,如果没有设置,默认为类名
       table_name = class_attrs.get('table_name', class_name)
       primary_key = None  # 后面要找出主键是哪个字段名,这里先设置个空
       mappings = {}  # 这个mappings就是我们需要找出的自定义字段名和对应相关参数
# class_attr={'table_name':'user','id':IntegerField(name='id', primary_key=True),'name':StringField(name='name')...)

       for k, v in class_attrs.items():
           if isinstance(v, Field):  # 用字段类型是否属于它的基类Field来过滤得到我们想要的自定义的属性
               mappings[k] = v
               if v.primary_key:  # 如果该字段类型中primary_key=True
# 在for循环中,因为最初primary_key是None,当第一次找到primary_key时候,将primary_key赋值给该字段名,当下次在for循环
# 中找到primary_key同时primary_key不为空时候就代表又找到了第二个primary_key,此时必须的抛异常,因为一张表不能有2个primary_key
                   if primary_key:
                       raise TypeError('一张表只能有一个主键')
                   primary_key = v.name
           for k in mappings.keys():  # 代码健壮性行为 ,主要是因为前面的类名、父类、类的名称空间重复的太多了
               class_attrs.pop(k)   # 前面将表中自定义字段集中在mappings里了,此时外面的class_attrs中的内置默认属性中的自定义字段
               # 为了避免重复需要删除。
               if not primary_key: # 如果遍历完了class_attrs还没有找到primary_key,也需要抛异常,一张表必须要有一个主键
                   raise TypeError('一张表必须有一个主键')
               # 最后将我们自定义的属性(表名、字段名和字段类型的类对象、主键字段名)加入class_attrs(创建这个类的初始属性中)
               class_attrs['table_name'] = table_name
               class_attrs['primary_key'] = primary_key
               class_attrs['mappings'] = mappings
               # 加进去之后,我们仅仅是拦截__new__来达到这个目的,关于创建类的其他全部过程还是该怎么走怎么在,交个元类去做
               return type.__new__(cls, class_name, class_bases, class_attrs)
           


class Models(dict, metaclass=MyMeta):
   def __init__(self, **kwargs):
       super().__init__(**kwargs)

   # 要搞清楚这里的self是啥?我们肯定知道是个对象,这个对象有点特别,他是个字典对象,因为Models继承了dict的方法
   def __getattr__(self, item):
       return self.get(item)

   def __setattr__(self, key, value):
       self[key] = value

 

  

 

posted @ 2019-10-01 00:20  Huise.J  阅读(153)  评论(0编辑  收藏  举报