from mysql_singleton import MySql
# 先创建字段类
class Field(object):
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
# 我们在创建一个简短的字段类
# 比如int类型的字段类
class IntegerField(Field):
def __init__(self, name, column_type="int", primary_key=False, default=0):
# 一定要调用父类的__init__方法
super().__init__(name, column_type, primary_key, default)
# 在定义一个str字段的类
class StringField(Field):
def __init__(self, name, column_type="varchar(32)", primary_key=False, default=None):
# 一定要调用父类的__init__方法
super().__init__(name, column_type, primary_key, default)
# 这时候我们要写一个元类,来限制我们表类的创建过程
class MyMetaClass(type): # 其实我们只是修该了class_attrs这个参数
def __new__(cls, class_name, class_cases, class_attrs):
if class_name == "Models":
# 如果是Models这个类,直接不做任何修该,直接返回对象
return type.__new__(cls, class_name, class_cases, class_attrs)
# class_name 创建表类的类名
# class_cases 创建表类所继承的父类
# class_attrs 创建表类的名称空间,一个字典
# 将表的名字拿出来,如果没有这个名字,我们就用这个表类的类的名字
table_name = class_attrs.get("table_name", class_name)
# 定义一个变量,来接收,主键的名字
primary_key = None
# 定义一个字典的变量,来接收,这个表中的所有字段
mappings = {}
# 循环,这个表类中的名称空间的字典以键值对的形式赋值解压给两个变量
# k,v我们可以把它看成是字段的名字,k是一个变量,指向v,v是一个字段实例化的对象
# 注意,这里必须加items()
for k, v in class_attrs.items():
# print("%s----------%s"%(k,v))
# 判断 v这个对象,是否是Field这个字段类实例化出来的对象,k是指向v字段对象的变量,v.name才是字段的名字
if isinstance(v, Field):
# 如果是就将k,v天健到mappings这个字典中
mappings[k] = v
# 在判断这个字段中的primary_key是否为True,如果是,这个字段为主键
if v.primary_key:
if primary_key:
# 判断这个表中是否已经有啦主键,如果有了,抛出异常
raise TypeError("一个表中只能有一个主键")
# 将这个字段中的主键,中的名字,赋值给变量primary_key
# 注意,v是对象,v.name是字段的名字。是将名字赋值给primary_key
primary_key = v.name
# 循环出mappings中的指向字段对象的k,指向字段v对象的变量
for k in mappings.keys():
# 将表类的名称空间中的一切字段类型的对象,通过k删除
class_attrs.pop(k)
if not primary_key:
raise TypeError("一张表必须要有一个主键")
# 将表的名字添加到这个表类中的名称空间字典中
class_attrs["table_name"] = table_name
# 将这个表的主键的字段的名字添加到这个表类中的名称空间字典中
class_attrs["primary_key"] = primary_key
# 将这个表中的所有字段的对象得这个字典添加到这个表类中的名称空间字典中
class_attrs["mappings"] = mappings
# 最后我们掉一下原来的类方法,让它给我们实例化一个对象,一定要调return type.__new__(cls, class_name, class_cases, class_attrs)
return type.__new__(cls, class_name, class_cases, class_attrs)
# 定义一个中间类,让继承他的类可以支持字典那样传入多个关键字传参数,而且可以实例化一个字典对象
# 然后重写里面的点方法,让它支持可以用对象点生么取值,应为字典是不支持对象点什么属性的
class Models(dict, metaclass=MyMetaClass):
# **kwargs,可以传任意个数的关键参数
def __init__(self, **kwargs):
super().__init__(**kwargs)
# 如果对象点一个属相,没有,会触发,这个方法
# 主要是,当我们创建一个继承这个类的类就可以使用点方法,取值,这时候生成的对象是有字典的特性的
# 我们就可以点出字典中的值,也可以通过点来修改字典中的值
def __getattr__(self, item):
return self.get(item, "没有这个键")
# 如果对象点一个属相,来修改这个值,会触发,这个方法
def __setattr__(self, key, value):
self[key] = value
# 应为是查询,我们是需要表的信息,通过类是可以将表的所有信息都能拿到
# 其实用对象绑定方法也可以拿到表的名字,但是我们查出来的表里面的数据是一个列表,包字典
# 我们应该将字典里面的值来出来以关键字的形式传入cls叫括号,就会实例化出一个字典的对象,
# 我们就可以拿到这个对象,通过我们继承了models这个类,就可以使用里面的点方法
# 我们就可以直接对象点属性的方式就能拿到对象字典里面的值,也可以对象点属相等于生么值就
# 能修改对象字典的值
@classmethod
def select(cls, **kwargs):
# 调用mysql的这个类
ms = MySql.singleton()
# 判断是否有参数传入
if not kwargs:
# 如果没有参数,我们就直接拿到表名字,拼出sql语句
sql = "select * from %s" % cls.table_name
# 然后调用mysql中的select方法,返回所查内容
res = ms.select(sql)
else:
# 如果有参数传入,我们就限定所有参数我们只拿一个,根据keys拿到所有的关键字参数的变量名
# 然后通过list将生成器直接转化成列表,我们取第一个值
k = list(kwargs.keys())[0]
# 然后根据关键字的变量名,到kwargs字典中取到所对应的值
v = kwargs.get(k)
# 最后拼接sql语句,注意,当我们要拼挂件字的参数,我们不能直接拼,我们可以先拿?占位
sql = "select * from %s where %s = ?" % (cls.table_name, k)
# 然后通过字符串的替换,将?号替换为%s
sql = sql.replace("?", "%s")
# 然后调用mysql中的select方法,返回所查内容
res = ms.select(sql, v)
# 判断,是否有查询消息返回
if res:
# 如果有,我们将得到的列表包字典,搞成列表生成式。
# 将每一个循环出来的字典通过**打散为关键字参数,传入类加括号,实例化成一个字典对象
# 应为我们的这个类是继承了Models这个类,而这个类右继承了dict这个字典类
# 当实例化的时候就将这些关键字参数,转换为字典对象
# 注意,这个时候的res是一个列表装的类实例化出来的对象所以我们在取出这个对象的时候要加[0]
# 这是orm的精华所在
res = [cls(**r) for r in res]
return res
def up_date(self):
ms = MySql.singleton()
# "update user set name=12 where id = 1"
# 定义一个变量老保存主键值
fields = []
# 定义一个变量来存放主键对应的值
pr = None
# 在定义一个变量来存放字段的值
values = []
# 循环打印mappings中的字段
# print(self.mappings)
for k, v in self.mappings.items():
# 判断字段是否是主键字段
# print(k,v)
# print(v.primary_key)
if v.primary_key:
# 注意此时的self是一个字典对象是,查询出来的数据经过res = [cls(**r) for r in res]得到的
# 它是经过这个字典对象来调用的,字典中的内容,能对应上mappings中的字段,一一对应
# 所以可以通过反射来拿到字段对应的值
pr = getattr(self, v.name, v.default)
else:
# 拼出name=?的格式,一会拼sql语句的时候,就可以直接写
fields.append(v.name + "=?")
# 将所有字段的值,添加到列表values中,如果没有值,就取默然值
values.append(getattr(self, v.name, v.default))
# 拼出sql语句
sql = "update %s set %s where %s = %s" % (self.table_name, ",".join(fields), self.primary_key, pr)
# 将sql中的?替换为%s
sql = sql.replace("?", "%s")
# 调用mysql中的execute方法,来执行sql语句,将values中的字段对应的值传入
ms.execute(sql, values)
def save(self):
ms = MySql.singleton()
# 定义一个变量,来接收除主键外所有字段,应为组主键是自增的,无需我们取指定
field = []
# 定义一个变量与字段数与对应的个数关系,一个字段,就对应一个?
args = []
# 定义一个变量来接收每个字段的值。
values = []
# 注意,这里循环的一定是字典的k,v键值对,必须在mappings后面写items(),否则会报错
for k, v in self.mappings.items(): {"id": "id", "name": "name", "password": "password"}
# 循环出不是主键的字段
if not v.primary_key:
# 将不是主键的字段,放入列表,注意,添加的一定是字段的名字,而不是v这个字段的对象
field.append(v.name)
# 将?号添加个数与字段个数一一对应
args.append("?")
# 将字段对应的值,放入列表
values.append(getattr(self, v.name, v.default))
sql = "insert into %s(%s) values(%s)" % (self.table_name, ",".join(field), ",".join(args))
sql = sql.replace("?", "%s")
ms.execute(sql, values)
if __name__ == '__main__':
class User(Models):
id = IntegerField(name="id", primary_key=True)
name = StringField(name="name")
password = StringField(name="password")
# 查询数据库中的数据
# 用类来调用select方法
# res = User.select()
# 返回一个列表包对象,我们的加下标取出对象
# 该对象是一个字典对象
# dict_obj = res[0]
# 访问对象中的数据
# print(dict_obj.name)
# 修改数据库中的数据
# 先要查询我们要修改的数据
# res = User.select(name="tank")
# 拿到字典对象
# dict_obj = res[0]
# 通过点方法来修改字典中name字段的值
# dict_obj.name = "jack"
# 调用up_date修改数据
# dict_obj.up_date()
# 向数据库中增加数据
# 先我们以关键字传参,来实例化 一个字典对象
# dict_obj = User(name="tank",password="123")
dict_obj = User(name="tank")
# 注意;
# 如果,我们只传了一个参数,在保存的时候,会将这个表类的字段对象全部循环出来
# 然后在将对象中的字段名字拿出来
# 这是通过getattr是没有值得,应为我们在实例化对象时,是没有给password指定值得
# 所以对象点属性会触发点方法,__getattr__方法,得到,如果没值,就会取默认值
# 所以我们传值的时候一定要各个字段都要传到
# 然后用字典对象来掉save方法增加数据
dict_obj.save()
"""
1 创建一个字段的父类,但是其中的字段类型不一样,我们就要每一次都要写init,所以我们要根据不同的字段类型
创建不同的字段
2 创建不同的类型的字段
3 字段创建出来了,我们就需要传值,但是传值还是需要init,这样态麻烦,我们就想出来了继承dict的models类这样就能
可以无限的传入关键字参数,返回的是一个字典的对象
4 但是我们创建出来的字典对象,不能支持对象点属性来取值,所以,我们重写了点方法,
5 我们还得控制,我们创建的表的对象的创建过程,所以通过元类,能方便的访问它类中的名称空间中的表的名字
表的主键,还有表中的所有的字段
我们先是,创建一个类,来继承元类,
判断我们继承的类是不是我么创建的中间类(models),也叫辅助类
如果是,我们就返回他的对象,不做修改
如果不是,我们就得将这个类的名称空间做一定的修改
我们先的定义3个变量,分别来存,表的名字,表的主键,表所有的字段
先将名称空间里,找出table_name的键值对,将值赋给我们定义的变量,来标识表的名字
后面范围表的名字都可以拿出这个值
在循环出名称空间里的所有键值对,判断该键值对的values值是否是我们字段类创建出来的对象
如果是,将所有的字段都保存到我们定义的变量中
然后在判断谁是主键,也赋值给我们创建的变量
此时,我们已经将所有的字段都放入了我们的变量中
现在我们要讲变量中的k拿出来
去根据k将表类中的这些字段全部删除,应为i,我们的字段都已经保存到了我们的变量中
最后,我们要讲我们的定义的变量保存到表的名称空间的字典中
最后在,调用一下元类的new方法,返回我们修改过后的对象
6 现在我们呀定义一个mysql的类
里面支持查询sql语句的方法,返回给我我们一个sql中存储的数据
还有一个是执行sql语句的方法
最后,我们要讲这个类,变成一个单列的类应为,这个类,是么有必要创建多个对象的
这样还可以节约资源
7 应为我们的所有的表的类都是继承models这个中间类的
所以我们应该在models这个类中来写这个查询sql语句并返回字典对象的这个方法
先我们的调用我们才写的mysql这个类
返回这个对象
然后判断用户是否传如数据,如果没有传入关键字参数,我们就可以拿到这个表类的表的名字
拼接sql语句
调用mysql对象中的方法,将sql语句传入,返回的是我么需要的一个字典的对象
应为后续的修改,需要我们拿到这个字典对象去修改你面的值
如果用户传入了值,我们就取第一个键值对
分别取出k,v然后拼接sql语句,注意,这里的sql语句的关键字参数,必须通过execute来拼接
我们可以先用?占位
8 修改数据库里的数据,还是得先实例化mysql对象
应为我们的sql语句的句式为"update 表名 set 字段名字=数据 where 主键名字=数据"
然后,得找3个变量来存主键的值,字段名字,字段名字的值
我们先循环我们创建出来的表类,它是经过元类改变了创建方式的类
里面有我们存入的表的名字,表的所有字段的列表,还有表的主键
所以,我们可以先将装有所有字段,筛选出主键
通过反射,拿到该主键的值
重点理解:
这里的getattr为什么能取值:
这里为什么我们可以通过反射能拿到该主键的值,应为我们修改数据库里的值得时候
将这些值以关键字参数传入了这个类叫括号,实例化出来的对象是一个字典对象
然后我们通过点方法修该了这个字典对象的值,然后在通过这个对象我们调用了修该的这个方法
所有此时的self就是这个修该过后的字典对象这个字典对象的结构就是,字段名字:数据
所以我们可以根据我们循环出来的字段的对象,拿到,这个字段对象的字段名字
然后就可以通过getattr取出字段名的值,意思就是说,我这个字典对象中有这个字段名
我要将这个字段名的值取出来
现在我们还需要将所有的字段名字拿到,将他们拼接成 字段名=?直接存入我么定义的变量
可以不拼接,。只是后面要麻烦一点
还需要通过放射,拿到所有的字段名字的值
最后我们拼接sql语句
调用mysql里的方法执行sql语句,修改数据
9 我们需要调用mysql,创建一个实例对象
然后还是需要定义3个变量来保存字段名字,与字段匹配的?的个数,还有就是字段的值
还是先将mappings的字典循环,取出不是primary_key主键的字段,注意,这里为啥不要主键,是应为,主键是递增的
不需要我们人为的取指定
吧这些拿到的字段的名字添加到一个列表中
还要将将匹配的问号,也添加到另一个列表中
最后将字段名字以反射的方式将字段名字的值,也添加到列表中
此时,我们就应该拼接sql语句了
这里的反射是拿到的我们创建
这个表类实例化的对象,我们是给它传入了关键字参数的
然后实例化的对象,也是一个字典对象,理由就和上面修改方法中的反射是一样的。
不一样的是,修改数据拿到的对象是我们查到数据时就给它实例化的
而增加数据,是我们自己传参,自己实列化的对象
但对象都是字典对象
,所以就可以通过getattr拿到这个字段所对应的的值,说白了,现在的字段就是我们字典对象的一个属相
我们现在通过这个属性,就可以拿到这个值了
"""
import pymysql
# 创建一个数据库类
class MySql(object):
# 定义一个变量
_instance = None
def __init__(self):
self.conn = pymysql.connect(
host="127.0.0.1",
port=3306,
user="root",
password="00",
database="db",
charset="utf8",
autocommit=True
)
self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
def db_close(self):
self.cursor.close()
self.conn.close()
# 执行sql语句,并返回查找的内容
def select(self, sql, args=None):
self.cursor.execute(sql, args)
res = self.cursor.fetchall()
return res
# 执行sql语句
def execute(self, sql, args):
try:
self.cursor.execute(sql, args)
except BaseException as e:
print(e)
# 单列模式
@classmethod
def singleton(cls):
# 如果,变量中没有值
if not cls._instance:
# 类加括号,创建一个实例对象,并赋值给变量_instance
cls._instance = cls()
# 如果有,直接返回对象
return cls._instance