数据库开发

数据仓库和数据库的区别

  数据仓库和数据库本质上来说没有区别,都是存放数据的地方。但是数据库关注数据的持久化、数据的关系,为业务系统提供支持,事务支持;数据仓库存储数据的是为了分析或者发掘而设计的表结构,可以存储海量数据。

  数据库存储在线交易数据OLTP ;数据仓库存储历史数据用于分析OLAP.数据库支持在线业务,需要频繁增删改查;数据仓库一般囤积历史数据支持用于分析的SQL,一般不建议删改。

        (因为数据库呢放的多了,他就仓库嘛,但是数据库本身不是仓库吗?,我仓库小也是仓库呀,对吧,所以一般来讲的话他俩是没有什么本质区别的,那数据仓库这个词现在叫的比较多了,是因为我们现在信息化建设已经跟互联网结合在一起了,也就是说每一家企业如果你搭上互联网,你能攒的数据就不是以前我们认为单个数据库能承受的了的,这个时候呢,往往我们会需要一个叫数据仓库的地方,你可以认为这个时候要特别说明数据仓库的话,数据仓库指的是一个大的数据库,很大的数据库,比你认为的那个数据库还要大一点点,就这么理解就可以了,他可以大多少呢,你数据库里面我们可以放几十亿条数据,数据仓库呢,一般也放几十亿条数据是吧。但是他不止这些,一般对于一个数据仓库来讲,你放几千亿条数据都没什么大问题。几亿条数据能不能出现呢?能啊,互联网企业一天光产生的流水记录,你算多少条。一个区一天的交通路口,记录车辆行驶的话就有100多万条,你算我一年多少条?再加上我还一些杂七杂八信息要记录的话,几亿条轻松突破。对不对,互联网就这些东西,你比如说像淘宝就用事就更多了,除了这么多记录之外,商品信息、商品交易信息、商品交易信息的并发问题如何解决的问题,这都是他要面临的问题,以及他的图片如何解决,图片如何缓存的问题,图片如何更快的推送到用户跟前。如果没有图片,淘宝网就不用开了。所以他要解决问题更多,而我解决的问题要解决是大图片如何存储,录像如何存储的问题。他要解决的是小图片如何存储的问题。这两种解决方案是完全不同的啊。不管怎么样,这都是跟存储相关。但是千万别把图片存数据库里啊。我提醒你一句,不管图片有多大,千万别存到数据库里去。即使有BLOB这样的字段,你也别存,可以存别存,谁存谁挨骂。      好,数据仓库一般指的什么意思呢?数据仓库指的是你可以认为它是一个海量的数据库你可以这么认为。那一般来讲的话,线上所使用这种数据库呢,他一般来讲的话。跑不了这么多数据啊。当你一张表达到几亿条数据的时候,你还是这种原始的玩法的话。那就出问题了,当然这个时候有dba来协助你们啊,因为这个时候数据库就不能这么玩了,数据库得分了,怎么分?第一种方法,分区是一种方法。总之一张表你放在一起就不合适,我就得想办法切一切,用分区的方式来切,其实就是相当于又加一个索引。 分区相当于是个索引,也就是说分区他不但存储上能给你做一些结构性调整。他还可以在逻辑上给你相当于做一次索引。这是分区,分区呢,能不能给你解决问题呢?不能,因为你的数据在每一个区里的数据还是很大,还会很大,怎么办呢?这个时候就牵扯到我们对数据库的另一种玩法,叫纵向切,还是横向写,就水平切分还是纵向切分的问题了。这就叫纵向切分,就是分表或者分库的问题了。横向切分呢就是分表的问题了。这里是更高级的玩法了,这必须数据量在很大的时候再说对吧。有这么两种说法,怎么分库啊,怎么分表,到底是横着分还是竖着分呢?安字段分开呢,还是说横着分呢,对不对,也这都是有讲究 的,但是各种方法,你甭管它理论上多么牛,都要在生产环境中去试验一下。也许你这么分就是不合适的。他在某种并发或者某种数据压力下的话,它可能这种分还是合适的,但是换一种场景不一定就行了,所以什么东西都得在线上测算。线上跑了之后搞定那就行,搞不定就是不行,你别拿理论来压我,我们要的是结果。好,这是我们讲的数据仓库里面的一些问题啊。但是数据仓库在某一点上是跟数据库是有明显差异的。我们一般称为数据仓库叫OLAP,a指的是分析,它是一个用做在线分析的,而数据库我们一般关系型数据库一般我们更多的成为他叫OLTP,T指的是事务他是一个在线事务系统。在线事务系统和在线分析系统,它是有差别的。在线事务系统更多的关注是事务还要保证数据的完整性、一致性。而在线分析系统不是说让你不保证这些东西,他更多关注的是这些数据,我应该怎么去分析。 分析不是增删改。他会做什么事呢?数据仓库会把以陈年的数据库里的数据,比如说近10年的数据,近20年的数据全部拿走,放在数据仓库中干什么样,不是为了删的,不是为了改的。历史就成为历史的,你不允许改,什么意思。数据仓库里面导进来的,这些数据库里面那些数据不是用来让你改的。需要你对历史数据进行分析的,分析什么呀,分析趋势的分析变化啊,指导生产的呀。支持决策用的。干这事的,他不是让你过来做增删改的,说看哪句不合适来删了。那年不合适就别导进来了呀,不是让你在这儿来删的。数据仓库是用来做分析的。我们前面一张表出现了个hive,我们就可以称为它是一个数据仓库。他可以存储海量数据,然后使用hive的sql语句进行存储,进行查询。数据库更多的是要求支持事务,他往往会用在线上做频繁的增删改。当然还包括查。这查说实在的,你到后面怎么优化都有问题。这时候就要借助nosql这些数据库的支持了,甚至缓存系统,搜索引擎系统。要用这东西来支持了。数据仓库呢,一般是囤积陈年的数据的 ,用作分析的,一般不建议修改和删除,是可以做的,但是一般都不建议这么做。所以这个时候就靠谁呢?就靠数据分析人员了或者是一些数据科学家由他们来做的当然分析工具有很多。往往数据仓库里面都要结合很牛的这种分析工具一起来做了,那其中当然你如果觉得你的数据很大,而且你需要分布式,那就需要一些分布式工具。比如说hive,需要这些东西来帮助你来解决这样一个问题。这都是大数据领域的东西了。但是呢对于传统的关系型数据库的分析呢也是可以的。你比如说找了一个数据仓库,当然有成熟的商业产品放进去之后,他也会给你提供很多工具,只不过他不是分布式的。) 

游标

  操作查询的结果集的一种方法。

  可以将游标当做一个指针,指向结果集中的某一行。

        (操作查询结果集,就是我们查询是有结果吧。这个结果呢他怎么操作,我操作到哪了呢?他可以认为这是一个表,表上面有个指针,操作到这了。这个指针来回的动,对吧,游标就是来回动,他只带着你刚才已经操作到哪一行了,游标指的是这个意思,就是个指针。这个概念我们后面要用,他指向的是结果集中的某一行 )

存储过程,触发器

  存储过程( Stored Procedure ) ,数据库系统中,一段完成特定功能的SQL语句。编写成类似函数的方式,可以传参并调用。支持流程控制语句。

  触发器( Trigger) ,由事件触发的特殊的存储过程,例如insert数据时触发。
  触发器功能虽然强大,但是会有性能问题。

  这两种技术,虽然是数据库高级内容,但基本很少用了。

        (什么意思呢?他写了一个函数,这个函数也叫过程,他写个一个函数可以传参,可以调用。这个函数里面是什么东西呢?这函数里面有一些流程控制语句加sql语句,就写了这东西可以用变量。那对你现在一个写程序的人,写这东西其实不算什么。你会点sql语法加上封装不就成了一个过程了。做一个存储过程,它是放在数据库这边的啊,是放在数据库里面进行管理的,它叫存储过程,他支持流程控制,支持传参,支持封装成函数。支持变量定义对吧,然后呢还可以支持你编写的操作语句了,各种操作语句都没有问题。那触发器是什么呢?触发器也是类似于他,也是让你去定义的。也有特殊语法让你定义,但是呢它是由事件触发,什么是事件,当什么什么满足了,这就是一种事件。这是由数据库这边,由数据库系统来给你提供这种功能。所以说触发器他是当条件满足的时候,它会触发调用一段sql语句,其实就调用了,相当于调用存储过程一样,你写好了一段语句,对吧,条件触发是自动替你调用,在存储过程给你自己手动调用。那能不能在触发器里面去调用存储过程啊,这不就是你写的吗?当然可以啊。但是呢一般情况下,我们存储过程是什么呢?是我们通过传sql语句,从客户端传sql语句,告诉他调这个存储过程的。触发器你不用调,因为触发器是自动执行的,他是当条件满足时候自动执行的触发器功能非常强大,他做的事情非常多。比如说插入错误怎么办啊?当你插一条记录的时候,我应该怎么办呀?当你更新的时候怎么办?当你更新失败的时候,我应该怎么办?这都是什么时候什么时候,这就可以到达一个条件了,到一个条件就可以触发,触发器。 以上讲的存储过程和触发器,虽然是数据库的高级功能,但是基本上很少用了。早些年记得是上世纪吧。那上世纪的时候,大家还经常提,会了存储过程啊或者触发器怎么样。两千年过后提的是越来越少了。千万别以为会了存储过程和触发器就可以拽,不用,你拽什么拽啊。触发器有很大的性能问题的,不用啊。最后发现你是一个频繁的系统的时候,如果你的触发器写的不好的话,你会频繁读取或者说插入的这种语句。总之看你触发器写的是观察什么东西,你等待什么事件上,如果你的触发器写了之后,你会发现会引起严重的性能问题的。那当然跟你触发器写的好坏也有关系。总之这东西会带来很大的问题,基本上都废弃了,很少用了。存储过程要不不要用啊,存储过程按道理用是没有任何问题的,但是我们现在不用他的原因是因为你里面写的这些东西,我可以认为是一个处理逻辑。这个逻辑我们前移了。不放在server上了,前移到哪去了呢?在代码逻辑中了,而不是在数据库中的存储过程中去存储这个逻辑了。)

数据库开发

   我们肯定要用程序来操作数据库的,程序操作数据库。那在不同的语言中呢,他们实现的这种连接数据库接口都不太一样的,标准也不太一样。你比如微软实现叫OLE DB,后来有用ODBC、到了java这边他有jdbc,那python这边呢一般来讲的话是原生的数据库连接方式啊,但是后来也有其他一些库帮你封装。不管怎么样,我们首先第一步你要操作一个数据库你就必须通过这些访问的接口或者已经定好的协议来连接数据库,这是必须的。只不过人家有统一的标准。你比如说ODBC,jdbc这些都是统一标准。其他数据库厂商必须在自己提供的协议之上对这些接口进行重新的封装。一般来讲因为这两种用户太多了,所以说odbc和jdbc非常的流行,那python这边呢,他到底应该怎么实现呢?python这边呢,实际上它是对这个数据库这边的连接,连接协议做了一些简单的封装,因为他没有提出一个其他的连接标准或连接协议出来,所以基本上都是对他自己原生的协议进行一个封装就可以了。

驱动

  MysQL基于TCP协议之上开发,但是网络连接后,传输的数据必须遵循MysOL的协议。封装好MysQL协议的包,就是驱动程序。

        你比如说我们在微软平台,在java这种平台上我们去做一些开发,都经常讲数据库驱动,数据库驱动指的是什么意思啊,数据库驱动就是指的是你怎么样,通过什么样的连接接口与数据库之间怎么通信的问题。也就是说你们的数据应该怎么包装,你们应该怎么通讯。你比如说我们的驱动,我什么驱动啊,我与硬盘之间的驱动,也就是说你的数据应该交给操作系统以后,由操作系统怎么去调底层驱动,调什么驱动啊,其实不就调一个程序嘛。调一个程序,把你的数据怎么打包之后,然后发给你的硬盘,你的硬盘能认识啊,你怎么控制你的硬盘,你的硬盘认识,你们是不是有一种协议啊,你没有协议你的硬盘怎么听你使唤,你比如说硬件设备,你要怎么操作他的控制字。你连写什么字节进去,操作什么控制字,你都不知道。你让他工作在什么模式,你都决定不了,他自然不能工作了。还别说传数据了。所以说一般来讲的话与他与硬件设备之间呢,你得有控制协议,你还得有数据协议。控制就是说我怎么控制你,让你工作在什么状态。然后我再把数据传给你,咱们才能传数据。如果你连他的工作状态你都没法控制的话,那他根本就不能在一个合理的工作模式下去工作了,这是硬件设备。但是对于我们这mysql的话,一般来讲的话,当然中间肯定有控制的啊,我们更多的关心的是数据如何传输的问题。那mysql的驱动呢,就指的是他是这个协议,那么这都是基于tcp协议之上开发的驱动。网络连接后呢,传输的数据必须遵循mysql的协议,你不遵循的话,大家没法商量了。你发出的数据,我也不认识,我发出的数据你也不认识,没法通讯了。所以光连上3306是没有用的。还得把协议商量好。所以呢一般情况下你要抓包的话,我们看到的是你跟任何一个端口,如果能建立socket连接,这应该没什么大的问题。人家请求你是不是要连呀,连了就给你分一个socker,这都是可以连上了,但是我拒绝为你服务。因为我们要做一个协议的协商。就大家一连接以后,马上就开始说。我准备说英语,你说我准备说中文,好吧,对不起,咱俩不能友好的聊天了是吧。也就是说大家连接之后,下面第一件事就是协议协商的问题了。就发一些信息过去说,然后我准备用这个协议,然后ok不ok呀,他说ok 好,我们就开始用这种协议开始说话或者说他呀不但是说协议协商完,还有版本协商。我要用1.0版本,你说不行,用2.0版本,你有没有2.0,他说我有2.0,那大家用2.0版本来说话。就这么一个过程,所以一般都是这样,所以tcp的连接这没什么好说的。但是这之上的链接才最重要的。就我们讲的都是应用层的事情了。

MySQL的驱动

  mysql的驱动或者说mysql的开发库里面一般来讲的话,最著名的有这几个

  MySQLdb
        最有名的库。对MySQL的C Client封装实现,支持Python 2,不更新了,不支持Python3       (第一个就是mysqldb这是一个早期非常有名的库。可能在你们老的项目中是可以看到的。他那些暴露出来那些接口的,基本上都变成了其他的,想超越他的人的必须要支持的接口。也就是说我定了一种标准。大家都已经习惯使用我这种接口名称,或者使用我这种标准。你要实现哪怕你想取代我,你也必须实现这种名称。大家用习惯了呀。这个库很有名,我们知道mysql自己是不是有客户端 ,自己有客户端是不是之间有通讯协议,他相当于对他做了一个封装。但是呢目前这个库不更新了,他也只能支持到二点几的版本,三点几不再支持了,没人更新了嘛。所以呢这种在你的二点几的那个项目中,可能有人在用。那学完我们下面要学东西,你会发现他基本是兼容他的语法。所以你到时候看那个东西也不会觉得很生啊。)

  MySQL官方Connector       (第二种就是官方的,当然你比如说我自己有一种封装,你可以用我这种东西。那mysql官方的一个封装呢,它一般称为叫什么连接器。但是它有不同的,为odbc的,为jdbc的,python这边其实是通用连接器,大家可以拿来用。但是呢他毕竟不是为python专门做的,用起来就不是那么的方便。所以呢要想与数据库的联通的话,我们还得借助其他的人啊,当然因为我们这种开源语言有很多好心人就替我们开源出了很多这样的一些好用的库。比如下面我们要重点学习的pymysql)

  pymysql
        语法兼容MysQLdb,使用Python写的库,支持Python 3      (pymysql这样的库呢,他从语法上兼容mysqldb,使用python写的库,而且他一直在更新支持python3)

安装pymysql

pip3 install pymysql

创建数据库和表

CREATE DATABASE IF NOT EXISTS school;
SHOW DATABASES;
USE school;

CREATE TABLE student (
      id int(11) NOT NULL AUTO_INCREMENT,
      name varchar(255) NOT NULL,
      age int(11) DEFAULT NULL,
      PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

建立一个Connect连接

  首先,必须建立一个传输数据通道--连接。

  pymysal.connect()方法返回的是Connections模块下的Connection类实例.connect方法传参就是给Connection类的_init_提供参数

        你要用数据库,你首先得建立连接,但是这个连接不是socket连接,你就不用关心这么底层了,他自然会给你联通的,你不用管了。而且他肯定是个长连接。所以呢我们只需要看他怎么连接就行。我们从程序上看他怎么连接就行,底层他怎么打socket通道我们不用管。首先必须建立 一个传输通道。数据是双向传输的,我们必须把这个通道建立好。这个就叫连接叫Connection。就我们后面天天打交道的东西,pymysal呢提供了一个方法叫connect方法。connect方法实际上返回的是他Connections模块下的Connection类。他返回是这样一个类实例。那么connect参数呢,其实传参它实际上相当于传给了他这个Connections模块下的connection类,connect他事实上传的这个参,就是传给了这个Connection类的_init_了.也就是他的初始化方法所需要的参数,初始化方法一般来说需要以下几个参选。这几个是你最最关心的参数,没这几个参数基本上就没法玩一个连接了,有这几个参数,才能把链接建立起来。      哪几个呢,主机不用多说,这是按顺序的。一般我们讲主机端口对吧。但是他那个参数的顺序刚好是这样这样的。      首先是主机,你连谁呀,你得告诉我啊,然后呢你用什么用户名和密码,你才能连到这个里面去。而且你用什么用户名密码之后才能连到这个服务器上的某一个db上去。我们这个数据库管理里面放了好几个库。你这个权限能访问什么库。如果说你前面用户名和密码对你这个库没有访问权限,对不起,拒绝你。所以说我们一般来讲的话,你得告诉别人,你访问什么样的主机,什么端口,然后用什么样的用户名,用什么样的密码,然后你访问什么样的数据库。这是你要給定的东来,下面有个叫connection.ping()的方法,它能够测试数据库服务器是否活着。这个数据库服务器如果活着,那你就能ping到,如果他不活着,你根本就拿不到这个值。他有一个reconnect的这样一个参数,这个可以表示,如果现在这个连是接断开了,我要不要给你连上去,连上之后我再测服务活不活。因为我们测的是服务端那个服务他是否活着,我现在连接断开,我当然测不了,你要不要重连。他指的这个意思。
Connection初始化常用参数 说明
host 主机
user 用户名
password 密码
database 数据库
port 端口
  Connection.ping(方法,测试数据库服务器是否活着。有一个参数reconnect表示断开与服务器连接是否重连。
import pymysql      (首先导入pymysql)

# >我们拿到之后第一件事,就是建立连接,建立一个连接,我们给一个conn,然后pymysql有一个connect()方法,他会返回一个Connection类的实例,connect()方法没提示有什么参数不知道呀,这个时候我们一般点进去,点到这了connect = Connection = Connect这么等于的话,说实在的你你想让他提示他都提示不出来,点进去之后还好from . import connections这块他告诉你,是可以导入connections这东西的,在看from .connections import Connection他事先告诉你从connections这里面导入Connection这个连接,因为他自己也要用这个连接类,我们点开这个连接类,看他的_init_。依顺序给定初始化参数(host,user,password,database)下面有一些你基本不太常用的东西,可以看到autocommit默认情况下自动提交是False看到这点就行了,其他的参数你不懂就不要动,因为他有的参数它是牵一发动全身的。他不是说你就改一个参数嘛,他会影响到他的工作模式,不懂就不要动。      那通过这种方式我们就能找到参数了。
conn = pymysql.connect('192.168.0.173','root','root','mysql') # 返回一个Connection类的实例,服务器地址192.168.189.151,这是一个远程地址,确保有登陆权限,和别的权限,如果是默认可能看不到别的库,给user是root,然后给个密码就行了,然后呢我们需要访问的是什么库呢?mysql库或者自己创建的test库。当然这东西是不是可以最后写到配置文件里面去啊 。就没必要放在这儿啊,都是写配置文件了,没有人写死在这儿了。我们把第一步做好了,就是说你要访问谁,3306改不改呢,我这边不改了,因为他是默认的。一般情况下的话,在你的生产环境中,一般情况下大家不会乱改3306的。除非你计算机上跑了俩,端口冲突了。3306和3307可能这样改,一般情况下上面只部署一个就够了,下面呢,我们就想试一下,看看能不能建立上去。那不管怎么样,按道理建立完之后,不用了就应该关掉。我们现在是怕他出问题啊,所以说这个地方呢应该怎么样呢?应该是做异常这么做是最合适的。

# >建立连接一定要断开连接释放资源 
conn.close()

# -----------------------------------------------


# >怕连接出现问题,如果有异常需要把异常抛出,      我不管你建不建,但是他提醒我了,说这变量你这么写不合适了。那有几种解决方案,
try:
    conn = pymysql.connect('192.168.189.151','root','root','mysql') # 返回一个Connection类的实例
finally:
    conn.close() # 断开连接释放资源

# -----------------------------------------------

# >如果连接失败就不用在释放资源,      conn = None但是你这么一写有风险没有,有,里面的连接一旦出错conn值一定是None,如果是None 那么if conn值为假一定进不来,如果不是None那说明我要close()断开一下,那按照python的写法的话,你没conn = None这一句其实问题也不大。你要是没这一句的话,其实python他这个没有开启新作用域,这个变量还能用。但是如果conn = pymysql.connect()这句没成功的话,conn这个变量它是有可能不存在的呀。你就这么直接conn.close(),他一样会抛问题,所以你还不如这样conn = None把变量定义好,对吧。测试一下,成功。改一个东西比如改为mysql1,测试,直接抛异常了,这个异常也没捕获,但是异常抛归抛,连接关归关,如果连接创建成功conn这肯定有一个连接对象,所以它该conn.close()就close,如果conn为None那就不close呗,      就说对于这个连接对象的话,我们肯定最后要释放资源的,一定会释放资源的。
conn = None
try:
    conn = pymysql.connect('192.168.189.151','root','root','mysql') # 返回一个Connection类的实例
finally:
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源


# -----------------------------------------------

# > 如果连接成功了,我想测试一个命令,这个命令叫conn.ping它里面有一个参数叫reconnect,点一下ping看一下这东西干什么用的,阅读一下这个代码,它说有一个ping方法,对于一个连接对象来讲,那这个ping方法干什么用?如果 if self._sock is None就说明你现在就没有socket,那if reconnect这个时候如果你写的是reconnect也就是说它等于true,相当于你要求我重连嘛。如果是重连我就调连接方法self.connect()连 一把不就可以了,然后我就告诉你reconnect等于False,否者的话 raise err.Error("Already closed")就直接给你抛错误,都关了,你要我干什么事儿是吧,它这样给你抛出一个错误出来。那如果reconnect等于Faise,我们肯定是想给你Faise掉,不想让你重连,因为我想就是连接之后看看服务这边活着还是没活着,能不能有效提供服务。下面看,下面他会执行self._execute_command(COMMAND.COM_PING, "")它封装的东西,不用关心,然后他会告诉你self._read_ok_packet()就是ok的packet,但是它不会返回给你没有return,如果except Exception:出现异常,它就会告诉你if reconnect要不要重连,那我们reconnect这个值是False吧,为假,那就直接raise抛异常,也就是说如果能成功肯定没异常,如过失败了,肯定有异常。因为reconnect等于true重连之后他要连了的,他self.ping(False)重新ping了,然后它也用的是False,因为它刚重连过干嘛在为true呢,它就要排除现在网络出问题了,测试一下返回一个None因为ping代码中没有return回值,能返回None说明连接时建立的,如果想让它返回值,就return回return self._read_ok_packet()这个值,结果是<pymysql.protocol.OKPacketWrapper object at 0xffff979e6ac8>能返回这样一个东西说明连接时建立的,OKPacket的一个Wrapper包装起来了,      这样的话就可以了,但是这条语句用不用呢,我觉得你用也罢,不用也罢。因为呢你可以通过print(conn.ping(False))这个判断他到底是不是None,或者有没有异常,你就可以判断现在那个服务是否能活着呢,对吧,所以这条语句你用也罢,不用也罢,反正我们用这个try也可以,用try来整个看它有没有异常。如果执行过程中,他如果出现异常,也会出一定的问题。
conn = None
try:
    conn = pymysql.connect('192.168.189.151','root','root','mysql') # 返回一个Connection类的实例
    print(conn.ping(False))
finally:
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源

游标Cursor

  操作数据库,必须使用游标,需要先获取一个游标对象。
  Connection.cursor(cursor-None)方法返回一个新的游标对象。
  连接没有关闭前,游标对象可以反复使用。

  cursor参数,可以指定一个Cursor类。如果为None ,则使用默认Cursor类。
  
        前面我们说过游标了,什么是游标Cursor,Cursor是什么东西呢?它是操作数据库的这个数据集的时候,我们要使用的一个东西。那使用游标之后呢,我们就可以控制现在究竟读到哪了或者我要读下一行游标要在哪儿,我们就读取下一行什么的。我们要做这样的事情啊,用游标,游标在什么上面获取呢?游标在connection对象上获取。,但游标这个在有些库里面,其实是有游标库和没游标库,没有游标是相当于把游标给你隐藏了。有游标就说那拿游标你自己来操作吧。就这么个事儿啊,那你要看他这个库到底支持不支持,但是不管支持不支持,其实他都不见的,不代表他没有。只不过说像这库,他把游标直接给你拿来,由你自己来控制了。从连接上去拿一个Cursor,你准备操作数据库了,从连接上去拿一个,连接没有关闭前,游标对象可以反复使用。那也就是说连接要是关了,游标也没法用了。当然呀你连接都关了,你操作谁呀? 所以呢我们保证在使用游标之前,数据库必须是连接状态,cursor参数,可以指定一个Cursor类。如果为None ,则使用默认Cursor类,这什么意思呢?写代码就知道了
# >获取游标,操作数据库,必须使用游标,需要先获取一个游标对象,现在数据库连接已经有了,你操作数据库之前必须获取一个curser游标,就是这边呢他要求你必须使用这样东西,然后呢我们来写个cursor,cursor呢写在这之后呢,从哪个上面来啊,从连接对象上来去用cursor方法获得一个游标。他说这后面可以写个cursor等于None,我们cursor()点进去来看看究竟什么意思,可以看到cursor可以给一个参数,参数if cursor如果是None,就不多说了,直接执行return self.cursorclass(self)它,cursorclass是什么东西?点点看。self.cursorclass它等于这个cursorclass,这个cursorclass等于哪?在点点看,可以看到等于传进来默认参数cursorclass=Cursor,而默认参数等于Cursor类,所以说如果你给个None,那他就会用默认的参数。而默认的参数,是在默认,在缺省值这块是不是给了,缺省值这块用的是缺省的一个Cursor类。这个类在哪呢?在点点看class Cursor(object): 在Cursor这个模块里,class Cursor(object)这就是Cursor类,Cursor类操作什么呢?就操作数据库呗。他是个类,那说明他是有实例的,到时候你用的时候肯定要拿一个实例回来操作。往下看def close(self)看到了吧,close给你写的多靠前,告诉你不用了,请你关了呗。他会把Cursor里面相关的数据都给你清掉。在往下看def __enter__(self)和def __exit__(self, *exc_info)也就是说你想懒,那就懒呗,用With就行了呗。
cursor = conn.cursor() # 返回一个新的游标对象,连接关闭前,游标对象可以反复使用

# >游标也需要释放资源,用完之后是不是这边应该cursor.close()手动关,或者用With语法是不是也可以啊。它说反复可以使用,什么时候可以反复可以使用,你关了还能反复使用吗?关了就不能反复使用了。你自己把cursor关了,当然不能反复使用了。如果你cursor没关,连接还没断,你可以把这个cursor对象拿过来继续用,是这个意思。也就是说cursor这边应该还可以有其他的cursor。不然的话,那后面能给你一个参数嘛。cursor等于None这个参数,也就是说除了这种游标,可能还有其他游标类,因为传进去是一个类吧,他拿的是return self.cursorclass(self)这个cursorclass类,拿这个类在干什么呢?这是self.cursorclass(self)这类的实例化呀。这个cursor(self)也是类的实例化,这不是调函数啊,这是实例化,什么意思啊,你现在return cursor(self)这不是有个cursor类啊,cursor类加个括号,然后cursor(self)送你一个self参数进去。可以他把什么参数送进去了?这self是什么呀?是连接实例,因为一个游标要连接嘛,没连接拿游标操作谁啊,self现在是在连接里面,我们在连接里面用连接的cursor方法,这是个连接实例。也就是说每一个cursor肯定要用这个self连接,他要把这个cursor(self)连接给传进去。 有cursor,我们下面就要用cursor来操作数据库中的表。不是用连接操作表啊,我们在连接里面去要一个游标,然后用游标来操作表。你想连接直管连接事就ok了。然后连接说我现在操作数据库你给我一个游标,剩下就不用你关心了,你给我个游标,我来操作数据去了。有的游标我们下面就可以操作数据库了。
cursor.close()

操作数据库

  数据库操作需要使用Cursor类的实例,提供execute()方法,执行SQL语句,成功返回影响的行数。

        那操作数据库需要使用Cursor类的实例,提供了一个方法叫execute()的方法,execute是执行的意思,执行什么?执行你给他的sql语句。管你是增删改查,只要有权限执行给我就行了。然后呢他会返回一个叫成功的影响的行数。如果我成功了,比如插入一条语句,影响一行返回个1,是这个意思啊,如果他执行里面sql语句是查询语句。他返回什么东西呢?他返回的依然是影响的行数。那数据在哪儿啊?数据用其他办法取。所以这个函数返回的是影响行数,它不返回结果,你查询的结果。但是有些数据库的操作这些类啊,它返回的是结果集,他返回的不是行数,他的行数用其他方法来取。所以不同的封装他们这个取的方法不一样。但是不管怎么样,我影响几行,你要告诉我,如果我是查询语句,你不但要告诉我影响几行,你还得告诉我,我查的结果在哪儿,你得给我。不然我查数据库干什么呀,是吧。所以说这些东西不同库有不同封装。了解一下,学习一下,就行,但是大差不差,因为我的需求是这样,你必须得实现。
新增记录
  使用insert into语句插入数据。

       新增记录怎么样新增呢?用insert into语句插入呗
# >下面就要构建一个sql语句了,命令自己取,等于""这么一个东西,然后下面用cursor.execute的方法,把这样的cursor.execute(sql)一个sql语句,传给他就行了,然后他一执行,执行之后,他会返回一个所谓的行数,对不对,你愿意拿就拿,你不愿意来就不要了呗,对吧。反正是个数字,你愿意的话line = cursor.execute(sql)就把它拿过来,然后你把print把line它打印一下,别忘了最后关闭。着就ok了,对不对,但是我现在什么语句都没写完。我们写一条语句,insert into t values(10, 'dean',30)然后执行,测试结果可以看到打印出来的是1,成功的影响了1行,它如果失败了,它应该抛出异常,因为我们没有捕获任何异常,但是在数据库中有验证结果,并没有插入任何数据。这就是我们没有commit。所以呢插入是不是算是一种修改啊,对吧。而且我们在连接的时候特意跟大家说了,他有一个叫 autocommit它默认等于False,你不commit肯定是不行的。所以呢我们的语句本身没问题,而且你确实成功影响1行。但是你没commit,你这个影响不能持久,但是你说我这连接都关了,关了怎么办,事务直接就回滚。他不可能提交成功,因为你根本就没提交。我们通过这样的一些方式,就可以把数据给它添加进去。
cursor = conn.cursor()

sql = "insert into t values(10, 'dean',30)" # 构建 一个sql语句
line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
print(line) # 这个行数愿意那就拿,不愿意拿就不要了

cursor.close()



# >在Connection类的__init__方法的中有一句话‘是否应该开启自动提交呢’
import pymysql

conn = None
try:
    conn = pymysql.connect('192.168.189.151','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    cursor = conn.cursor()
    
    sql = "insert into t values(10, 'dean',30)" # 构建 一个sql语句
    line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
    print(line) # 这个行数愿意那就拿,不愿意拿就不要了
    cursor.close()
finally:
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源

# autocommit: Autocommit mode. None means use server default. (default: False)
# 是否应该开启自动提交呢(默认是False),没有提交所以语句执行不成功,数据库没有插入数据
# 生产环境中不用开启,一般需要手动管理事务。

  发现数据库中没有数据提交成功,为什么?

  原因在于,缺省情况下的话,自动提交它是等于False ,在Connection类的_init_方法的注释中有这么一句话
  autocommit: Autocommit mode. None means use server default. (default: False)

  那是否应该开启自动提交呢?(不建议开启,生产中的代码不建议你开启自动提交。我们的建议是手动管理事务。)

  不用开启,一般我们需要手动管理事务。

事务管理
  Connection类有三个方法:
        begin开始事务
        commit将变更提交
        rollback回滚事务
  
              我们说事务相关的方法有3个。但是我们这个begin用不用呢?在这里,在他这个库里你其实可以不用的。就像我们前面是不是再给大家演示隔离的时候就没有用begin,对吧,我们用的是你做了修改commit不就完了,只要有变化,commit就行了,他就会把这种变化直接给你提交到库里去,那如果有问题的话怎么办,回滚就行了。那如果是这样的话,怎么改代码呢?有任何except异常,我conn都会rollback回滚,如果没有异常怎么办呢?conn点commit()就行了。当然你在上面cursor.close()这个关闭,跟下面的语句的conn.commit()他没有影响,因为cursor.close()这个又不是conn关闭的,你conn关闭的是不是你的cursor就不能用了。所以cursor的关闭的话,跟这个conn.commit()提交不提交没有关系啊,谁先执行也没有任何关系。我们通过这样一种方式就可以把数据提交进去了。通过在数据库上查看表,确实提交进数据库,写入的有数据了。      我们在这儿做的时候,在这个可视化编辑环境里面,创建的数据他是自动提交的。但是我们说在生产环境中,我们来开发代码的时候,请你注意了,这个时候我们自己手动的来管理事务。不用他来管,我们自己来管。该提交的时候提交。那我们以后在生产环境中写代码,要不要事务呢?可以说只要是增删改,都得用事务。你必须保证他增删改这个东西,这件事儿必须完整完成。当然为了增删改之前他还要做很多事。首先比如说我要去拿一些数据回来,相当于select什么东西,然后for update,因为我要读取的这些数据,要被我锁定,我下面马上就对他们进行删除,修改。增加呃,就是说我拿过来就是立马就要进行更改,对吧。那这个时候的话。我们要保证连查询,在修改这些事情是一个事物必须全部完成。但是什么才叫全部完成呢?当你能commit了。这才叫全部完成,你不commit,这不叫全部完成。得一旦出现任何异常,我们都应该回滚。然后最后别忘了把conn.close()它这个连接关了。这就是一个标准的数据库连接和操作的写法就这么写。当然这个关不关闭游标,你最后结束也行。你比如说放到finally这来能不能,当然我在这个conn.close()连接关闭之前,对吧,我这个地方写个if cursor如果cursor为真,然后关闭cursor.close()游标。也可以,那么cursor = conn.cursor()这一句就不要了,这个放到finally里面了,对吧,说不定我commit完了之后,我还要再做事情呢,对吧。那这样的话游标放finally这儿,那游标要放finally这以后,他又会出现同样问题,你到底想怎么样,你这个游标啊,对不对,所以呢这是不是跟conn = None他一样,你要不行,你就写一个cursor等于None。      所以呢我们通过这样一种操作就可以来进行数据库操作了,这是一个标准的写法。当然他肯定实现上下文,我们到时候一会儿再说。因为上下文里面还有一些其他事儿,所以我们先不用上下文。就是你不用上下文,你这么写也是没什么问题的,有错误该关闭关闭啊,对吧,有错误,该回滚回滚,有错误之后或者没错误,你该关闭资源,要关闭资源,对吧。这个肯定要做的事情。      好,这是我们一个简单操作。当然你说sql = "insert into t values(10, 'dean',30)"这里面我能不能写成一个查询语句,select语句,当然可以啊。这没有限定,他说执行任何一个sql语句,只要是合法都可以啊。那有的是合法的,但是他抛异常的原因是在于,比如说主键冲突,对吧。这个因为是语句是合法的,但是数据库那边插入不进去,这个异常产生了。但是你想如果这真的是有主键异常的话,except这地方产生了一个异常,这异常是被捕获了,捕获之后回滚嘛,对的呀。这件事做的没有错啊。那有人说,那你没插入进去,你别忘了你conn.rollback()这个地方是一个事务啊,你这里面可能有很多句语句啊,你前面可能插入成功了,下面这一句没插入成功。那我按照事务的要求,原子性不可分割的话,应该怎么做,前面插入的全部回滚掉一个不留,对吧。所以总之这个try块里面是我的一个事务应该做的事情,只要出现问题怎么办?全给他回滚。只要我的插入语句,只要我对数据库的修改,哪怕是select语句,只要在其中产生的任何异常全部回滚,我这是一个事务,原子性不可分割就这么做。      一个简单的基本的流程,对吧,他就是这么写代码的。然后呢就是里面要嵌入事务。因为对我们来说的话,关系型数据库,我们用它最主要的是要解决增删改查的问题。对数据增删改查的问题,也就是ciud的问题。那这样的问题要解决,你必须在其中使用事务。但是纯粹的查询例外,如果你从头到尾全是查询,其实你没有必要使用事务。如果有增删改你就应该在其中使用事务,如果查询里面带了for update,实际上你这个查询是为了后面做update做更改要用的。那这个当然要替你做事务了。我们只纯粹的查询,就是要查一个结果集,我也不为了改,我就是看看,那这种东西其实不需要事务。也就说读这事儿没事儿,写就有问题了。
# >Connection类有三个方法;rollback 回滚事务
conn.rollback()

# >Connection类有三个方法;begin开始事务
conn.begin()

# >Connection类有三个方法;conmit 将变更提交
conn.conmit()



# >添加事务,这样运行就可以操作表
import pymysql

conn = None
cursor =None
try:
    conn = pymysql.connect('192.168.189.151','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    cursor = conn.cursor()
    
    sql = "insert into t values(10, 'dean',30)" # 构建 一个sql语句
    line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
    print(line) # 这个行数愿意那就拿,不愿意拿就不要了

    cursor.close()
    conn.commit() # 如果没有异常,将变更提交

except:
    conn.rollback() # except有任何异常,回滚事务,保证事务的原子性,一旦出现异常全部回滚

finally: # 有没有异常都执行
    if cursor:
        cursor.close() # 关闭游标,释放资源,cursor值为none,条件为假不进入语句块
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源
批量添加数据
  批量增加应该怎么写呢?比如说呢我写两条sql语句然后提交是不是也可以呀,那既然是这样的话,假设我这个问题的话,可以用循环来解决,是不是就可以写一个for循环,那有同学讲你这样写不出问题吗?不出问题啊,因为我没有主键啊。我没有主键,我就可以随便写,对吧。我们要首先可以随便写,再不行你自己写个主键呗。比如for循环范围是25,35,把sql语句中的10替换一下,将主键传递进去,这不就相当于你假设他是主键的话是吧,我没说这个主键自增的,这主键是我自己维护的,只要保证他唯一就ok了,这样的话相当于是不是连续的插了10条进去是吧。测试一下,在数据库中查看插入了10条数据,插进来了,但这里面有个问题。我们一般更多的工作是这么做。把commit提交放在for循环外部,也就是说一批更改做完之后。你确实没什么要更改了,这个时候再提交,但是这东西你也要适当的用。就算你得力度大了一点点,但是你别忘了for这里面要是耗时很多的话,你这事务,这是个更改的是吧,你这耗时很多的话,是不是带来一些问题,虽然说commit写在for外面这样效率高,这样比commit写在for里面刚才那种效率要高。因为他不是力度大小问题,他是在于你每做一条提交一下。你如果提交1000次的话,一千行你要增加1000行,你提交1000次,他是非常耗时的工作。但是你要把这一千行都更改完之后,然后一次性提交,他批量提交,它会也做了一定的优化,这样速度会快。但是其实真正提高速度的地方还不在这些地方。其实这些提高的还是比较有限的。但是你还是能够看出来他们之间是有时间差异的。您可以增加个一万行试一试,然后怎么看时间差异,前面一个start,stop呗,一减不就完了吗。然后一般来讲,如果这个地方要做批量的。我们往往会把这些批量的修改全部写在前列,到最最后做一个提交。所以批量提交我们这么玩。所以既然可以的话,请你写成这个样子,一般来说批量的改完之后统一提交效率要高一点。但是你这块也不要真的来个什么一百万行更改之后再提交,耗时太长了,别人等不起啊,对吧,那万一你要锁住了呢,别人怎么等啊,没法等了是吧。所以说我们说就算你占着锁,你也得快点释放吧。那怎么办呢?切呗。分段提交呗。别切的太碎了,一行一提交别这样,一百万行提交无所谓,别一千万行一提交。一般来讲的话,我们的修改,很少有这种批量修改,你做了什么事了,这么多东西要同时修改啊。是吧,很少有这种现象出现。一般都是个别几行修改一下。所以说这个时候一块修改一次性提交就可以了。这就是批量提交。批量提交把commit提交的东西放在外头就可以了。
# >批量添加数据一次,一条一条添加。
sql = "insert into t values(10, 'dean',30)" # 构建 一个sql语句,将主键传递进去
      line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
      print(line) # 这个行数愿意那就拿,不愿意拿就不要了
      conn.commit()

sql = "insert into t values(10, 'dean',30)" # 构建 一个sql语句,将主键传递进去
      line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
      print(line) # 这个行数愿意那就拿,不愿意拿就不要了
      conn.commit()


# >批量添加数据,使用for循环就可以添加一批数据
for i in range(25,35): 
      sql = "insert into t values({}, 'dean',30)".format(i) # 构建 一个sql语句,将主键传递进去
      line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
      print(line) # 这个行数愿意那就拿,不愿意拿就不要了

      conn.commit()

cursor.close() #把提交放在外部,对这一批添加的数据统一提交


一般流程

  这一般流程我们写了多了,自然也就记住了。
  • 建立连接
  • 获取游标
  • 执行SQL
  • 提交事务
  • 释放资源

查询

  Cursor类的获取查询结果集的方法有fetchone(), fetchmany(size=None), fetchall.

        查询呢他会使用几个方法。因为我们说查询完之后,他那个execute()方法返回的是一个受影响的行数。也就是说你查了十条回来,他就是十行,他就给一个10,你说这10我拿来有什么用啊,我要的是结果怎么办呢?结果呢就需要你使用这些方法叫fetchone看命令是什么?拿一个呗,fetchmany意思是拿多个size有你给,size不给怎么办呢,不过就给你返回空回来,fetchall意思是返回所有结果集,这块的话语句就不是这么写了,这个时候我们实际上是这conn.rollback()东西都不需要了,使用pass替换掉,for循环也不要了,因为我是纯查询的吧。纯查询就没有什么事务的问题了,不需要事务地方一定不要事务。上事务会影响效率。好,我们现在要写的是select * from t把所有的数据都给我查回来。这样的话大家看查询之后是不是 line = cursor.execute(sql)他依然返回查询的行数啊。对吧那我们怎么拿结果呢?我们这么写cursor.fetchone()拿到之后,打印出来print(cursor.fetchone()),在写一个fetchmany我们给个5行print(cursor.fetchmany(5))就拿5个,最后在写一个print(cursor.fetchall())我们来看看他们都有什么区别,可以看到区别就跟上面说的一样,但是他为什么拿的是上一条fetch拿完后剩下来的呀,因为Cursor这东西在啊,游标就是一个指针,看你走哪了,是吧,第一行拿过了,就跑第二行,第二行拿过了,就跑第三行,然后就这么着走,一直走,当你拿完了再拿的话如果fetchone拿一条就返回None拿不到了,如果fetchall拿全部就返回()空元组,因为fetchone这个相当于拿一个值回来,对吧所以他是None。fetchall这个是不是拿一个元组里面套着值啊,所以他拿了个空元组,fetchmany也一样他拿一个空元组回来,也就是拿多个他就拿个元组把多行套起来,行,本身用元组表示。他为什么要用元组啊,他干嘛不用列表。因为确实你拿的那些数据,他确实要把它让你不可变。这跟有些库用法是不一样,有些库拿回来的数据是允许变的,变完之后可以继续调一个方法叫update给他更新回去的。但是他这个回来是元组祖,就是不允许变,给你看的,不允许你变了。那要怎么变呢?你把数据拿出来自己凑呗,凑成update语句修改呗。这个我们其他的有些语言实现的库它是不太一样的。那他就这么做的啊。那我们就这么用。就是我们看到的fetch这些东西,ok,我们再来看cursor.rownumber这是个什么呀,点进去看看,在_init_中显示self.rownumber = 0这是个0,还有一个self.rowcount = -1我们知道count是不是计数用的,number是不是行号呀,什么意思呢,试一试呗,刚才上面把行拿完之后在拿的时候返回的是一个空,我们在他们拿完之后写一个self.rownumber = 0之后在拿cursor.fetchall(),他把他们全那回来了,也就是说游标按道理是从前向后走,一直走到头不会回头,想回头怎么办,你把它重置呗。你想去到哪儿去呢,而且这个游标是不是可写啊,也就是说下面到底显示第几行到第几行,随你便,你想改到第几行就第几行。那有人说那很好啊ok啊,来给一个-2,返回最后两行,原来支持负索引啊,-100是不是从后面边一直指到头了,当然是全部显示嘛,对不对,给个200呢,返回一个空字典,是不是已经指到最后去了,最后当然没有值,所以他比较好给你解决这样一个问题啊,这么像索引呀。我们窥探一下他的秘密,点fetchall进去看看,可以看到result = self._rows[self.rownumber:]这是不是切片呀,对self._rows一个数组(列表)切片,不是列表就是元组切片,对吧.那你绕半天,不就是一个,要么可读数组,要么就是可变数组。对我们来说可变数组就是列表嘛,不可变数组,不就是元组吗.不就这个东西嘛,一个线性结构的东西,而且是一个序列,所以你一看就明白了,这东西啊你有的时候还是要进来看一下啊。那么看一下有个叫东西是def fetchone(self),我们看一下if self._rows is None or self.rownumber >= len(self._rows):说他拿一个如果不超界,超界了return result就给你返回一个None回去,def fetchmany(self, size=None):可以看到如果超界了return ()就给你一个空元组,这里def fetchone(self)可以看到他每拿一个self.rownumber += 1就往前拨一下。fetchall就不用多说了,他给你拿到尾巴上去,然后self.rownumber = min(end, len(self._rows))设置len(self._rows)长度就行了,我们写得这么写。好了,这三个方法看过源码之后,你就懂他什么意思。      rownumber是送游标的是吧,但这个游标实际上相当于是拿索引实现的,所以说到底是个什么东西,这其实是游标对象里面保存了一个所谓的rownumber,由他来表示游标的移动的,对吧,就这个意思。这是游标,用游标来支持我们这个查询,拿一行呢,还是拿多行呢,还是拿所有。所以这个用起来都是比较简单的,只要你看到方法,你下面用起来都非常简单。
# > 构建一个sql查询语句
sql = "select * from t"

# > 查询一行
cursor.fetchone()

# > 查询多行,size=none拿几条自己给定,默认none
cursor.fetchmany(5)

# > 查询所有
cursor.fetchall()

# > fetch
fetch操作的是结果集,结果集是保存在客户端的,也就是说fetch的时候,数据库查询已经结束了,操作的是本地

# > 重置游标
cursor.rownumber = 0

# > 返回的是元组,数据不可改变
import pymysql

conn = None
cursor =None
try:
    conn = pymysql.connect('192.168.189.151','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    cursor = conn.cursor()
    
    sql = "select * from t" # 构建 一个sql语句
    line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
    print(line) # 这个行数愿意那就拿,不愿意拿就不要了

    print(cursor.fetchone()) # 查询一行

    print(cursor.fetchmany(5)) # 游标可以比作指针,从第2行往后查询5行

    print(cursor.fetchall()) # 游标在第6行,从第6行往后查询所有行

    cursor.rownumber = 0 # 重置游标到第0行,-2支持负索引
    print(cursor.fetchall())

except:
    pass

finally: # 有没有异常都执行
    if cursor:
        cursor.close() # 关闭游标,释放资源,cursor值为none,条件为假不进入语句块
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源

  fetchone()方法,获取结果集的下一行
  fetchmany(size=None)方法, size指定返回的行数的行, None则返回空元组。
  fetchall()方法,获取所有行。
  返回多行,如果走到末尾,就返回空元组,否则返回一个元组,其元素就是每一行的记录,每一行的记录也封装在一个元组中。

  cursor.rownumber 返回当前行号。可以修改,支持负数。
  cursor.rowcount 返回的总行数

  注意: fetch操作的是结果集,结果集是保存在客户端的,也就是说fetch的时候,查询已经结束了。

带列名查询(返回字典)

  Cursor类有一个Mixin的子类DictCursor
  只需要cursor = conn.cursor (DictCursor)就可以了

        带列名查询,我们发现class Cursor(object):类打开有一个class DictCursorMixin(object):看见Mixin我们就知道他肯定是要给谁混合进一个什么新的功能。他混合有一个什么功能呢?他是想办法给你混进去一个跟字典相关的东西。所以呢他这个地方混完之后,他就会变成一个叫字典Cursor的东西。但是我们知道mixing不是拿来单独成为一个类来用的了吧。他也不是为了让你示例化来用的吧,你看看这是不是有一个dict_type = dict。对吧,这dict_type字典类型对吧,这实际上就是dict这个类嘛。看最后return self.dict_type(zip(self._fields, row))这是在构造什么呀?这不就是字典dict.zip里面zip嘛,zip是不是构造二元组嘛,这不是给字典在初始化嘛,所以里面是不是出来个字典呀。好,这是我们看到了他这个mixing在做什么事儿。然后我们来看,然后这块class DictCursor(DictCursorMixin, Cursor):这块有一个DictCursor字典的Cursor把DictCursorMixin, Cursor两个混在了一起。这东西就已经造完了,这东西就已经造完了。那这是个什么意思呢?我们还记得这句话吧。还记得cursor = conn.cursor()这个cursor吧,这个cursor在拿的时候说可以给一个参数是吧,cursor=DictCursor没有怎么办?没有就倒进来呗from pymysql底下会有一个cursors 然后import DictCursor这个Cursor给导入进来,因为我们下面要用它,对吧,默认你不用管,默认pymysql那边那个缺省值里面已经给你导过了,你就不用导了,这个我们把它导进来,导进来之后怎么办,cursor = conn.cursor(cursor=DictCursor)我们知道点进cursor可以看到,拿到cursor这个类之后在return cursor(self)这,因为你现在cursor不是None了,不是None是不是就拿return cursor(self)这个来做,这是类的初始化嘛,他就会生成一个新的字典类型的游标,下面什么都不用管了。因为这是字典类型的游标,它只是一种增强了,然后增强了之后下来是不是该怎么样还怎么样。来看下面结果。真是字典了,{'id': 25}是不是把字段名带回来了。就是这么做的啊,只需要改一下东西,只要把DictCursor它引进来就行了。你别用实例啊,人家是拿类初始化,替你初始化的啊,传一个类名进去就可以了。好,这是我们讲的字典游标。这样的话可以带参数,可以带一些字段名的方式,把这个结果显示回来。那有的时候我们需要的,有的时候我们不需要。为什么呢?你说你这个结果集内,如果带一些字段名的话,是不是我对应起来特别简单,我现在真的要给你用网页显示一张表,我这个地方放的跟id相关的,是不是到时候取的时候方便呀,到字典里面把id遍历一下,把id的拿出来就行了,对吧,这样用起来也很方便。那你说DictCursor没他怎么办呢?没他也问题不大呀。你sql = "select * from t"这个地方到时候选的时候,其实是你可以有字段啊,都是有字段的。那这个结果集就按照你字段的那个顺序,他给你把值拿回来。对吧,所以你解构的话,跟这个顺序它是对应起来的。你字段怎么写的,你比如说你写的是name,比如sql ="select name,age,id from t"。如果是按照之前的元组来的话cursor = conn.cursor()这里这样写的时候,他就是有序的,他就是按照name,age,id这个顺序来显示。为什么他用有序的东西就是因为这个原因,但是你都字典了呀。你想我刚才我们说这个cursor = conn.cursor()地方,如果我在换成cursor = conn.cursor(cursor=DictCursor),既然是个字典,你到时候拿的时候是按顺序拿吗?你肯定按字典的那个key的名称拿呀,你既然按字典名称拿,那我这边随便给你什么东西就行了吧。我给你一个普通字典就行,我都没必要给你一个还有序字典,对吧,就可以了。所以你知道他们这个地方的差异在什么地方。但是你要知道他这么要去拿,他给你返回的东西就不太一样了。首先单值{'id': 25}是字典,然后多值呢,这回不是元组了,这里面放的是个列表,用列表然后存的每一行,每一行是个字典。对吧。这东西没必要记换了个东西,然后打印一下,看结果,然后在怎么操作,对吧,没必要记。大概有个印象就行了。我们通过这个DictCursor的话,我们就可以把列名带回来。但是这个列名是带回来了,你要怎么用呢?这个呢到底带不带列名呢,这个主要还看你想怎么用了。就是你未来想怎么去把这个数据怎么给他处理,这就看你想怎么用。所以这一块的话,大家还是要注意一下,就是说有没有必要呢?这就看你自己想怎么做啊。因为我们有时候发现顺序的,要求跟字段是一样的。那我就按这个顺序来就行了。因为给我给我个字典也没用了,对吧。字典比元组还费空间。
# > 导入模块,给游标加参
from pymysql.cursors import DictCursor

cursor = conn.cursor(cursor=DictCursor)

# > 返回一个带列名的字典
import pymysql
from pymysql.cursors import DictCursor

conn = None
cursor =None
try:
    conn = pymysql.connect('192.168.189.151','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    cursor = conn.cursor(cursor=DictCursor)
    
    sql = "select * from t" # 构建 一个sql语句
    line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
    print(line) # 这个行数愿意那就拿,不愿意拿就不要了

    print(cursor.fetchone()) # 查询一行

    print(cursor.fetchmany(5)) # 游标可以比作指针,从第2行往后查询5行

    print(cursor.fetchall()) # 游标在第6行,从第6行往后查询所有行

    cursor.rownumber = 0 # 重置游标到第0行,-2支持负索引
    print(cursor.fetchall())

except:
    pass

finally: # 有没有异常都执行
    if cursor:
        cursor.close() # 关闭游标,释放资源,cursor值为none,条件为假不进入语句块
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源

  带列名方便遍历查询,按字典名称拿数据,(sql = "select id,name from t" # 构建 一个sql语句)也可以在构建sql语句的时候给定列名

  返回一行,是一个字典。

  返回多行,放在列表中,元素是字典,代表行。

SQL注入攻击

  有这样一个需求,找出用户id为6的用户信息
  SELECT * from student WHERE id = 6
  需求修改为,找出某 id对应用户的信息

  userid =5 # 用户id可以变
  sql= 'SELECT * from student WHERE id =(}'.format(userid)

  userid可以变,例如从客户端request请求中获取,直接拼接到查询字符串中。可是,如果userid ='5 or 1=1'呢?

  sql = 'SELECT * from student WHERE id = ()'.format('5 or 1=1')

  运行的结果竟然是返回了全部数据。

  SQL注入攻击
  猜测后台数据库的查询语句使用拼接字符串的方式,从而经过设计为服务端传参,令其拼接出特殊字符串,返回用户想要的结果。

  永远不要相信客户端传来的数据是规范的及安全的!! !

        SQL的注入攻击,这是一个非常要注意的东西。注入攻击,只要想与数据库连接都会讲。什么叫注入攻击呢?来,我们先举个例子。假如有一张登录表用员工表代替,员工表就是登陆表,select * from employees;这是我们的员工表,对不对,假如你要登录,你会怎么登陆。然后呢这是我们现在这样一张表,我现在对他要进行查询,对吧。select * from employees where name='jerry;可以查出来,然后还有密码,password要等于人家送进来的密码,select * from t where name='jerry' and password='ben';这样就成功了,这样的话你是不是保证了,其实你name等于jerry也问题不大,但是我们一般来讲,你怎么知道人家登陆的时候用户名是谁,这个用户登录的时候,这个用户是比较好的。那提供了自己的用户名和自己的密码是吧。然后我们一查,查有此人嘛,来允许你登录,后台的数据为你开放了是吧,允许你登录到后台。查询结果返回一条,这样的话,我们一执行,他是不会有个所谓的影响的行数啊,影响行数如果等于1,ok那说明这用户可以登录。那如果影响的行数查看等于2呢,你要不让他登录呢?那说明你库出大问题了,通过一个用户名和密码能找了两条。你这什么库啊?出问题了,但是不排除你允许人家用户名重复,结果人家密码恰好也重复了。所以有这种情况。如果你用id的话,id是不是保证它唯一了。但是你这里用用户名和密码有很多。所以说我们一般在登录用户名上要做unique key约束,唯一的,要作唯一约束的,这样他一定不会有用登录名出问题的。那怎么能保证这一点,想一想。用户在写一个提交程序的时候,他现在说我是新注册用户,他是不是提交一个用户名啊,这个时候你发现,当你写的用户名马上就有消息传过来了,说该用户名已经被人占用了。这是一个网页开发中的异步提交。但是不管怎么样,这个数据其实他已经查过一次数据库了。当你正在有变化过程中,他会把你变化后的结果拿走了,悄悄的拿走了,扔到后台数据库说来给我查一下这个用户名是否重复,重复了就要让用户赶紧进行修改。这是没办法,这其实增加了这个往返的过程。因为你每变一个字母,他都需要到数据库提交查一次。但这没有办法了,因为什么呢?当你把所有信息填好之后,点个确定。好家伙,这个时候在告诉用户了,这名字用不了,用户一般都很生气的。所以呢为了友好性,我们不得不这么做。就是说用户刚改了一个字母,马上他这个名字就被传到后台去了。查一下数据库。如果不冲突,不会提示,一旦冲突,马上后面有红色,该用户名已被注册。不允许注册或者说该手机号已被注册。就这么做的。好,那这是用户名和密码,但是这个什么问题呢?我们发现如果我写错了。假设我如果这个名字是唯一的,密码写错了是不是永远都匹配不上呀,查不回来的话,你返回值,你返回的行数肯定是0行吧。按道理是不是已经解决了我们的问题了。来select * from t where name='jerry' and password='ben' or 1=1;好家伙,你数据库了多少行吧,我全看到了。你知道 or 1=1这句结果相当于什么呢?这句结果相当于select * from t这一部分。我没在pymysql中的sql语句,假设现在有人在做这种事儿select * from t where name='jerry' and password='ben'。原来name='jerry'和password='ben'这个地方是不是要name='{}'和password='{}'这么写。后面是format两个变量,比如一个变量叫name,一个变量叫pwd。     select * from t where name='{}' and password='{}'.format(name,pwd)这两个变量,是不是用户从前面网页上给你传出来,用户传过来,这个这这个很简单嘛,name = "ben",但是用户恰恰在你拼密码的时候,他做了一件事,原来你里面pwd = "ben"这样写才行,用户万一写错是不是写成这样pwd = "ben1"这都不要紧。关键他做了一件事儿,他在这里面给你做了pwd = "ben1 or 1=1"他写了这东西,那有人说这这这事是不是会冲突,name='{}'这里可能也有引号,变量那里也有引号。你不管怎么说,他通过各种测试,他总能想办法给你把这个字符串,好好给你封个口。很好封的口之后呢,把这个东西拼进来。最后传进来的select * from t where name='jerry' and password='ben1' or 1=1是这样的,他能通过各种方式想办法把这东西给你塞进来。所以说你如果用这种方式的话,就会带来很大的问题。所以说这种叫什么方式呢?这种叫最不安全的,最原始的拼接字符串的方式,写一个sql语句。这种方式在生产环境中不用。太危险,你在内部临时用用还行,一旦对外坚决不许用。比如说你现在要做web开发。你现在写这个东西是做对外的,公开的是吧。你一旦敢写这样的语句。我估计马上要被开了。这根本就防不住sql攻击啊。所以呢他通过各种字符串拼接,他就能把这样的语句,想办法给你传递进来。只要你有拼接字符串的。他都面临这样的风险,我可以精心的去构造这个字串。      我们来分析一下这个语句为什么可以啊,为什么这条语句就能查出所有数据来啊?有一句or,你前面不管你怎么样,因为你前面都是false,是不是要看看or 1=1是不是等于true呀,如果他等于true,这不就是短路的问题吗,前面不短路,后面是不是一定要计算到or 1=1这,所以一旦发现有一个是or的话,那当然你前面是and无所谓,反正你前面先and呗,是不是先and,不管你and完之后,总之你最后要跟我or,or了之后这相当于什么?1=1这个是一个true吧,你前面所有的逻辑,跟我这一or就全变成true了,这整个语句相当于select * from t where True;,这就相当于这句话,所以说这就是为什么可以。      你总有一些查询语句,根据id来查的吧。那我们这种语句当然什么都查不出来啊,这肯定是错的吧。不要紧啊,我每一个参数都在攻击你,你总有一些地方是用id来查。比如说你的商品表。商品表你让人家把所有数据给你爬完啊,那从数据库爬是最直接的吧,select * from t where name='jerry' and password='ben1 or 1=1' and id=5我们看password='ben1 or 1=1'这个我们是不是攻击失败了,把or 1=1传递的时候包进了字符串内,当做字符串使用了,简单呀and id=5你这个地方你送进来,直接拼接字符串就应该是id=5 or 1=1这是不是叫攻击点就有了。你字符串也许你把我包进来了,但是你的数字你把我挡住了吗。你总有id查的,而id往往都是数字。你只要id敢这么做,我就敢攻击。当然攻击的字符串很多啊,不止这一种。这是最简单的一种攻击。通过这种构造我就可以突破你所谓的sql字符串。      那我得想办法,把这个问题给解决掉。那怎么解决呢?首先呢肯定不允许他这样拼接字符串,所以以后写sql字符串的话,绝对不可以写什么.format的方式把它给拼了或者用c风格的方式把它给替换进来,绝对不能这么写。从现在开始就养成一个好习惯,就不这么写了。      那既然你是通过拼接字符串的,我能不能通过不拼接的方式把你搞定呢。我不让你拼接呀,我现在不允许你拼接,当然python呢给我们提供了这样的技术。其实不光python,只要是现在的这种提供这种库的,都得提供这个东西叫参数化查询。只不过大家的语法不太一样。

sql语句注入攻击简单示例

# > 正常情况查看登陆表,用户给的用户名和密码可以匹配到,允许登陆
MariaDB [test]> select * from t where name='jerry' and password='ben';
+------+-------+------+----------+
| id   | name  | age  | password |
+------+-------+------+----------+
|    2 | jerry |   18 | ben      |
+------+-------+------+----------+
1 row in set (0.00 sec)

# > 正常情况查看登陆表,用户给的用户名和密码,密码写错了没有ben1,必需匹配不到,不允许登陆
MariaDB [test]> select * from t where name='jerry' and password='ben1';
Empty set (0.00 sec)

# > 注入攻击 添加or 1 = 1,数据库中的数据全都看到了
MariaDB [test]> select * from t where name='jerry' and password='ben' or 1=1;
+------+-------+------+----------+
| id   | name  | age  | password |
+------+-------+------+----------+
|    1 | zs    |   20 | NULL     |
|    2 | jerry |   18 | ben      |
|    3 | ww    |   30 | NULL     |
|    4 | ls    |   22 | NULL     |
+------+-------+------+----------+
4 rows in set (0.00 sec)

---


python语句注入攻击简单示例

# > line = cursor.execute(sql)

# > 模拟用户身份验证,通过各种方法,这种是最不安全的最原始的对字符串进行拼接,把字符串塞进来,在生产环境中不用

# sql = "select * from t" # 构建 一个sql语句
# name = "'jerry or 1=1'" # 用户给的账号字符串,示例使用变量,攻击失败攻击者不关心字段是否匹配
password = "'ben1' or 1=1" # 用户给的密码,字符串,示例使用变量,攻击成功攻击者不关心字段是否匹配
userid = "5 or 1=1" # 用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配
sql = "SELECT * FROM t WHERE id={} and password={}".format(userid,password) # 构建 一个sql语句
print(sql) # 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
line = cursor.execute(sql) # 用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
print(line) # 这个行数愿意那就拿,不愿意拿就不要了


python语句参数化查询,可以有效防止注入攻击,并提高查询的效率。

  如何解决注入攻击?
  参数化查询,可以有效防止注入攻击,并提高查询的效率。

  Cursor.execute(query, args=None)
  args ,必须是元组、列表或字典。如果查询字符串使用%(name)s ,就必须使用字典。

        参数化查询可以有效的防止注入攻击,并提高查询效率,还可以提高查询效率,那何乐而不为呢?干嘛不用呢?首先我们还是依然使用execute这个方法,查询的方法,整个都没变。但是我们要关注的他一个缺省参数,也就是后面那个args,我们前面什么都没写,是None。我们现在要不要给他写个东西呢?说可以写,args你要写,必须是元组、列表或字典,且如果是字典,请你使用%()s这种形式,这东西看起来不就是c风格的那种东西嘛,c风格格式化这个东西嘛。但是其他语言中都是打问号的。这个地方他用的是c风格的这种方式的啊,不管怎么说都是个占位符。      依然是刚才这个问题。依然是id = "5 or 1=1"这样的一个字符串,我用id来攻击你了。那我们现在这么写,sql ="select * from t where id={}".format(id),查询一下我们只看他print(cursor.fetchall())到底拿了多少条,可以看到全拿过了,拿过来了,我们现在要想办法给他解决掉id这个地方写一个%s,最后是这样的sql ="select * from t where id=%s".format(id)但是这样写,这是不是又拼字符串了。这拼字符串他不认的。这是c风格的,不认啊,那怎么办呢?不要这样写了,这样sql ="select * from t where id=%s"写。然后在line = cursor.execute(sql) 这写,点开execute看一下,他说execute的类型type args: tuple, list or dict必须是元组,列表,和字典,如果是字典的话,最后替换的话,是不是看名称替换啊,那你想想这id=%s写个百分之s,那你后面如果你要写个元组的话,你应该怎么写啊?你是不是要加个逗号呀,人家说只是元组,列表跟字典嘛,这是不是要把你的数据放进来line = cursor.execute(sql,(id,))。刚才说过了通过拼接字符的方式,是不是拦不住别人呀,能看到所有数据,这么写之后,然后这个id作为一个参数,在%s替换进来。但做这时之前的话,要做一些小小改造,因为他可能会抛异常,我们要把异常给他逮回来except Exception as e:      他给你一个警告。(有的没有异常,没有给警告,警告不是异常),说我尽可能的理解了,你就给我传的数据。然后我尽可能友好的给你把这个数据。给你转了,转成目标的类型,然后我把它给用了,他说的是这个意思。他用了之后呢,你发现他是不是把5,还是给用上了,就是说我友好的给你做了一下转换。但是呢我发现了你这里面有一些不太友好的东西,我只能告诉你,我给他把它截断了。我截断了一个非法的double类型的,那我反正我们不管他,总之他把它给截断了,取得个5,他把5拿到之后去给你做了个查询。      也就是说'5 or 1=1"你这一块的话,通过这个参数化查询,我们发现你的影响也就那么一行。你要查5吗,非要我给你查这个5吗,合适就合适,不合适就不合适嘛,如果是用户名和密码匹配的话,对不对,这样不至于你把我所有的数据都拿回来吧。所以呢sql注入攻击的是非常危险的事情。但是通过参数化查询就可以解决,在其他语言里面是可能是打个问号这么写的,对吧。无所谓他这么要求我们就这么做呗。      那他说字典是个什么意思呢?他说如果你要给我构造一个字典。 比如说这里有一个字典,d等于{'id':5 or 1=1}这是不是构建一个字典,那字典怎么玩呢?这line = cursor.execute(sql, (id,))还能用元组吗?就不用元组了,就把d放进去了。但是你有字典是不是靠名称匹配啊,你 id=%s这有名称吗?是不是没有啊,怎么办? sql ="select * from t where id=%(id)s"这么写,这是不是名称对名称,对什么名称对这个字典中这个名称,也就是说你%(id)s这写的名称,我到{'id':5 or 1=1}这找去?测试一下一样可以
# > 普通防注入
line = cursor.execute(sql,(id,))

# > 字典防注入
line = cursor.execute(sql,userid)



id = "4 or 1=1" # 用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配,注意值在在表id范围内
sql = "SELECT * FROM t WHERE id=%s" # 构建 一个sql语句
print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
line = cursor.execute(sql,(id,))# 传递元组,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
print(line) # 这个行数愿意那就拿,不愿意拿就不要了

print(cursor.fetchall())



# > 如果是字典
userid = {'id':"4 or 1=1"} # 构造一个字典,用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配
sql = "SELECT * FROM t WHERE id=%(id)s" # 构建 一个sql语句,传递参数通过字典名称匹配
print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
line = cursor.execute(sql,userid)# 传递字典,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
print(line) # 这个行数愿意那就拿,不愿意拿就不要了

print(cursor.fetchall())


# > 返回一条查询,禁止sql注入

import pymysql
from pymysql.cursors import DictCursor

conn = None
cursor =None
try:
    conn = pymysql.connect('192.168.139.187','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    cursor = conn.cursor()
    
    id = "4 or 1=1" # 用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配,注意值在在表id范围内
    sql = "SELECT * FROM t WHERE id=%s" # 构建 一个sql语句
    print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
    line = cursor.execute(sql,(id,))# 传递元组,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
    print(line) # 这个行数愿意那就拿,不愿意拿就不要了

    print(cursor.fetchall())

except Exception as e:
    print(e)

finally: # 有没有异常都执行
    if cursor:
        cursor.close() # 关闭游标,释放资源,cursor值为none,条件为假不进入语句块
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源


  参数化查询为什么提高效率?

  原因就是--SQL语句缓存。

  数据库服务器一般会对SQL语句编译和缓存,编译只对SQL语句部分,所以参数中就算有SQL指令也不会被执行。

  编译过程,需要词法分析、语法分析、生成AST、优化、生成执行计划等过程,比较耗费资源。

  服务端会先查找是否对同一条查询语句进行了缓存,如果缓存未失效,则不需要再次编译,从而降低了编译的成本,降低了内存消耗。

  可以认为SQL语句字符串就是一个key ,如果使用拼接方案,每次发过去的SQL语句都不一样,都需要编译并缓存,

  大量查询的时候,首选使用参数化查询,以节省资源。

  开发时,应该使用参数化查询。

  注意:这里说的是查询字符串的缓存,不是查询结果的缓存

  那么在SQL语句中where相当于判断语句,并且是由 or 连接的,所以 username=’ ‘ 和 1=1 中有一个为真就为真。1=1一定为真,所以语句又等价于or 1=1 不管前面是什么 反正都会执行or 这条语句相当于True

上下文支持

  我们在看那个cursor的源码的时候,看到他有个上下文,对吧,我们再来细细的看一下这个上下文怎么写的。def __enter__(self): return self.cursor()  和     def __exit__(self,exc,value,traceback):       if exc: if exc: self.rollback()       else:seif.commit() 这是一个什么意思?self指的是谁?游标对象本身,那你进去的时候游标干嘛了?return了,return self,return这个东西有什么用啊?我们讲enter进入后的返回值,是给With语句后面的as用的吧,as后面其实要的就是这个返回值,但是用不用归你,你写不写as是不是归你自己的事,那我现在问另一个问题,那下面这个def __exit__(self, *exc_info)是干什么事的呢?把异常删了,然后把自己关了。就做了这事儿,对吧,,      那我们怎么写呢,哪个地方开始弄游标了,cursor = conn.cursor()是不是从这开始拿了,后面写with,conn连接点cursor游标,然后as cursor,那有这句之后,cursor = conn.cursor()这一句是不是就可以不要了,为什么呢,因为with conn.cursor()这返回了一个所谓的游标对象。这游标对象进with的话马上调as,调sa返回值就是一个cursor,就是他自己本身,那他自己本身是不是就赋值给cursor这个变量。那如果是这么写的话,下面这些操作是不是跟cursor相关啊?是不是放到cursor里面去啊?那最后finally这块的话,cursor这关闭还用它吗?是不是不用了,你with用完就关了嘛。这个我们知道它是能成功查询,对吧, 查询完之后,with是不是把cursor已经关了,是吧,关了之后最后该释放资源,释放资源来走一把。正常返回,传回来没什么太多的问题啊,因为我们用的是fetchall。虽然只有一条,是不是还得用大元组包起来,这没什么问题。      我们来判断一下,如果你要关闭连接,我现在在with外面执行cursor.execute('select 1')会不会抛错,测试一下,他说异常了,因为print(e)这东西,在except这儿执行的?在这执行,他告诉我说,早都关了,cursor.execute('select 1')不可以调,所以这是一种写法。所以这是一种写法。那我偏不这么写 ,我能不能with cursor这么写? 当然可以呀,我们讲with是不是可以啊,你with进来之后没有人接as子句,就没接as子句呗。反正这是我要对象,这就是那个self呗,最后该close还是close啊,当然可以啊,你不会cursor = conn.cursor()在外面获得一个对象吗?这不两种写法都可以嘛,我们说with可以用as子句,我们也可以不用啊,那这样的话这cursor = conn.cursor()对象是不是在外面创建了,with cursor as cursor:这也写,with cursor相当于调__enter__了,调__enter__他的return值是不是给as cursor他呀,那不就一样了嘛,把自己又给自己了何必呢?所以说你如果说没有as curso他r怎么办呢?他把with cursor这个的对象。这个对象是谁啊?我们发现跟cursor = conn.cursor()这玩意儿写conn.cursor()这东西是不是一样的,对吧,with cursor他把cursor这个对象进了__enter__了,把这个对象,在给他return回去,没人接就没人接了,这对象是不是还是自己。但是这个with 语句执行完的时候会怎么样啊?是不是会调def __exit__(self, *exc_info)这段呀,那既然调这段儿函数,这函数做个self close()是不是调close啊,好吧,我们看看他close()怎么写的,说conn = self.connection这个close做了什么事儿就是cursorclose(游标close)做了什么事?第一个conn = self.connection连接是不拿来放conn这儿。如果连接if conn 是 None,也就算了,连接是None我没什么可做的事情了是吧,接下来是不是说while self.nextset()总之他做了一些事,对吧。nextset去执行了一些东西啊,就空在那跑一下,它其实就是为了exhausts all做这事的。所以我们不管,看最终干了什么self.connection = None,最终他只所谓做的什么事啊,他把自己所谓的关闭啊,你可以认为他把自己给结束了,然后他做了什么事啊,他把self.connection连接等于None了。也就是说我没连接可用了,虽然说我这个变量还在,但是呢我没有连接了,你用不了。做东西很简单这块儿啊。好,这是我们前面看到的东西,其实你发现他self.connection = connection这无非就是进来的时候我给你扔个connection连接进来。我们在创建它的时候我们用的是他的cursor的然后后面传一个self。那个self是连接是不是,那个self连接是不是传进来了?他其实说自己关没关闭,其实他干的事情很简单,就有没有连接的问题吗?好,这是我们看到这样一个问题,那这个我们阅读一下代码其实就已经很清楚了啊。这是我们这个cursor这一块儿的东西,我们发现这个with是不是可以关闭掉,好,这是cursor他的上下文,我们连接是不是也得自己关啊,我们能不能把连接也搞定呢,来,我们去连接里面看看连接在哪儿呢?点一下cursor看源码,在这儿,连接里面有一个cursor,这个self传的是连接,他把那个放在self.cursorclass等于传进来的这个cursorclass,对吧,他这么做的,看看他的__enter__方法(3.6.8版本没有这个上下文),如果用with现在能不能关闭连接,我们现在要看清楚它的dif __enter__(self)和def __exit__(self,exc,value,traceback)之间到底有什么关系。也就是说当你的连接,你使用with的时候。他会给你返回一个cursor,用不用是你的问题,也就说as不as有没有as是你的问题,对吧,然后我再退出这个with的时候,我会看有没有异常,比如抛异常了是不是也要退出来了,但是这个时候他就一定会回滚。我们讲上下文的好处就是出任何异常他都能替你做这事儿,对吧,做什么事,替你回滚或者提交嘛,那你不出异常,反正我也要进来,你出异常,我还是要执行。那如果说出异常怎么办?回滚,如果说不出异常提交,所以说我们讲连接的上下文并不是用来做连接创建和关闭的,它是来帮你获得一个游标的。帮你获得一个新的游标之后呢,然后到最后他替你关了没有,他也没替你关游标,他也不替你关连接,他在退出的时候只做一件事情给你提交还是回滚,要么提交,要么回滚,所以我们要看清楚它的上下文究竟指的什么意思,别弄错了,刚才我们在看cursor游标的上下文(在这儿pymysql >cursors.py >  Cursor > __enter__),其实很清楚了,游标的上下文他做的事情很简单,第一个def __enter__(self): return self返回我自己呗,给你用,你愿意你用,你不愿意用就算了。然后呢可是在我退出的时候,我要把游标给关闭,他说的很清楚,我是关闭我自己的,你甭管它,最后关闭是为了把这个东西self.connection = None清成None还是什么的,我不管这事儿,他把self.connection里面所有数据全给你清完了对吧,我们不关心,总之他把自己给关了,关了就不能用了,对吧,处于close状态就不能用了。      对于连接来讲,他不是这么玩儿的,连接,当你使用with语句的时候,它会给你返回一个cursors,但用不用在于你,也可以with进去之后,然后自己再重新获得一个cursors,无非你也是调的连接的cursors方法,返回一个新的cursors就这么做的嘛。然后关键是退出的时候他并没替你关闭所有资源,这资源要你自己关,他说这个关闭不太好,with的退出呢,如果你在这个时候让我替你关闭连接不太好,因为我连接是不是大家要公用的东西啊,我一个连接是不是长连接啊,你只操作这一项吗?你是不是还可能还操作其他数据啊,那这个时候我替你把这些资源都关闭掉,不太合适,本来建立一个连接就很浪费时间了,所以连接要尽量少关,所以他这块不会帮你关闭连接,但cursors要不要关闭啊?万一cursors继续后面想用怎么办呢?这个事儿我不替你关,反正def __enter__(self): return self.cursor()我给你个cursors了,你想不想用啊?那看你了,你不想用你就关闭了,你想用就先不关了,这完全由你控制。但是呢你要退出我这个with语句块儿,我def __exit__(self,exc,value,traceback):       if exc: if exc: self.rollback()       else:seif.commit()一定会把你里面的事务给你提交或者回滚了,他做这个事情。好,那我们来看,既然知道了它的用法之后呢我们下面就知道该怎么用了,对吧

简单的上下文语法

  之前是pymysql > connections.py > Connection > cursor中是有上下文的,现在没了,我们可以在def cursor下手动添加上
        def __enter__(self):
              return self.cursor()

        def __exit__(self,exc,value,traceback):
              if exc:
                    self.rellback()
              else:
                    self.commit()

  好,那我们来看,既然知道了它的用法之后呢我们下面就知道该怎么用了,对吧,那既然是这样的话,好,我这是我们的代码, 大家看这个地方还能这么写啊。
# > 简单连接的上下文语法,连接要么回滚,要么提交,没有关闭
with conn as cursor:

# > 简单游标的上下文语法,游标可以自己关闭资源
with conn.cursor() as cursor:



# > 完成后关闭连接
import pymysql
from pymysql.cursors import DictCursor

conn = None
cursor =None
try:
    conn = pymysql.connect('192.168.139.187','root','root','test') # 返回一个Connection类的实例
    print (conn.ping(False))

    # 获取一个cursor游标
    # cursor = conn.cursor()
    with conn as cursor: # 这个地方还能这样写呀with conn这会怎么样,是不是会进as里面,他会替我们调一下那个cursor方法,是吧,as cursor返回给我们一个cursor
        with cursor: # 既然上面的with会返回一个cursor我们把这个with和代码块往后挪一下。上面的with给我一个cursor我就用呗,然后我用with语句自己关,with语句块出去的时候,我自己关掉,可以吧 ,但是大家知道当你离开with conn as cursor:这个语句块的时候会怎样?他会提交或者回滚,来看有没有异常,对吧,但是我们这是查询是吧,你可以不关心异常。他通过这种方式。但是finally: if conn: conn.close() 这个关闭语句我得留着吧,因为这东西人家不替你做了,对吧,所以 这是一种写法。这个我们也经常这么用,这是我们经常看到的一种写法,就这两个with嵌套起来用。     代替cursor = conn.cursor(),也可以用with cursor:
        
            id = "4 or 1=1" # 用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配,注意值在在表id范围内
            sql = "SELECT * FROM t WHERE id=%s"# 构建 一个sql语句
            print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
            line = cursor.execute(sql,(id,))# 传递元组,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
            print(line) # 这个行数愿意那就拿,不愿意拿就不要了

            print(cursor.fetchall())

except Exception as e:
    print(e)

finally: # 有没有异常都执行
    if conn: # 连接不成功conn值为none,条件为假不进入语句块
        conn.close() # 断开连接释放资源

##############################################写法一
# 那有人说,不这么写行不行,那怎么办呢?

      with conn:  # 这样写。
            cursor = conn.cursor(): # 就要写cursor 等于conn.cursor()这是自己获得cursor ,谁替你关啊,没人替你关,怎么办呢?就算你出with语句是不是一样cursor 可以用啊,我们看过源码的
        
            id = "4 or 1=1" # 用户给id,数值,示例使用变量,攻击成功攻击者不关心字段是否匹配,注意值在在表id范围内
            sql = "SELECT * FROM t WHERE id=%s"# 构建 一个sql语句
            print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
            line = cursor.execute(sql,(id,))# 传递元组,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
            print(line) # 这个行数愿意那就拿,不愿意拿就不要了

            print(cursor.fetchall())

      cursor.execute('e') # 这时候你写个cursor点这东西execute(),照样可以用,因为我们有语法保证,代码看过了,他已经保证了人家不替你关任何东西。所以你自己关。


##############################################写法二
# 也有人这么写,所以说这些写法很多,但是你是不是得先看看人家__enter__和__exit__方法到底是怎么写的,然后你才决定你怎么用啊,反正他也就给你弄了一个,你爱要不要,你非得用自己创建一个,你又何必呢。你看def __enter__(self): return self.cursor() 他反正说管你要不要,他就return self.cursor()给你一个,你还不要。这self.cursor()对象是不是得创建一下啊,这对象创建一下你还不要,这不是垃圾吗,等着清理吗。所以说我们这一块的话,一般来讲的话with conn as cursor后面都会直接cursor往这儿一写,然后下面cursor = conn.cursor():获取cursor就不要了。      那如果说你后面这个cursor就为代码快用一下,那就说赶紧with cursor:用个with 语句把它套起来,如果说你后面还要用,或者你with 语句外面还要用,那你就继续用。但是一般来讲的话,整个上下文这一块儿我们一般写一个地方,不要把它写得很庞大,你这块就做这一件事就OK啦。你不要这个cursor 复用了一下,写了好几个操作写在一起,我指的是业务不相关操作,业务相关的操作,我们短短几句就得把它搞定了。      你不要在这儿写的老长了,你就说一个cursor可以复用,减少资源,这个资源什么释放,这个不用你管,因为你发现cursor ,它这块实际上创建很轻的,很轻量级的,所以很快的,所以你with cursor这块就不用操心这个事情了,对不对,那with cursor这个东西的话你想,那这个cursor,它实际上最后操作的是你本地缓存的一个结果集啊,你有什么不敢多缓存的,慢慢操作吧,关了再开不就完了嘛。      但是呢查询语句要优化啊。      好,这是上下文的使用,通过上下文的使用呢我们就可以把代码改成现在这种样子,差不多就这种样子。
 
 with conn as cursor: # 那你这边是不是拿一个cursor就完了,对不对,何必地下在接一句 cursor = conn.cursor():
 
      # cursor = conn.cursor():
      with cursor: #也有人这么写,这又何必呢,是吧

            id = "4 or 1=1" 
            sql = "SELECT * FROM t WHERE id=%s"# 构建 一个sql语句
            print(sql)# 打印一下sql语句,字符串拼接到or 1=1 这条sql语句就是sql注入攻击
            line = cursor.execute(sql,(id,))# 传递元组,用cursor类实例,提供的execute方法,把sql语句给他就行了,执行后会返回一个影响的行数
            print(line) # 这个行数愿意那就拿,不愿意拿就不要了

            print(cursor.fetchall())


# 那大家知道这块一样会抛异常,虽然说异常被with 他们看到之后,他异常不会替你捕获的,他依然会把他的exit方法都执行完了,也就是该关的,比如说cursor我该关的。通过with cursor这个语法,我是不是该关的关一下,这个with conn as cursor的cursor地方,我该回滚回滚,该提交提交。但是你有异常肯定是回滚吧,回滚之后这块是不是该捕获异常,这是你要写的,该有异常except Exception as e: print(e)自己去捕获。那最后finally:  if conn:  conn.close()该关闭谁关闭谁,这个with cursor语句已经保证了cursor会被关闭,所以就不用管了。所以要不要外面再写一个try在加上except呢?其实还是有必要的,因为 with cursor:这里面没有一个人替你管这个异常啊,一旦出现异常应该怎么处理,没人告诉你,所以还得你自己except Exception as e: print(e)在外面做异常处理。所以异常处理是少不了的。所以大家在做的时候这块要思考一下,怎么思考呢?不要去硬记,忘了就去看每个类自己的上下文方法究竟怎么写的, 

##############################################写法三
# 这块如果是这么写,我with conn.cursor() as cursor:这个地方我for i in renge(3):这么写,我conn.commit()这写这个提交会不会出问题啊?with conn.cursor() as cursor:这是不是已经明确的告诉你conn.cursor()这是游标,as cursor自己把自己return给self了是吧,所以是游标,谁给你关心这个提交的问题啊?没人关心吧,所以你如果是这种情况下是不得conn.commit()自己提交啊。然后我们发现这种方法的话,下面   # sql = "select * from student"   # cursor.execute(sql)   # print(cursor.fetchall())几条肯定会出问题,因为with conn.cursor() as cursor:这个with 出来的时候会把它关了,所以你在这儿再用cursor.execute(sql)游标是不是出问题了。所以这里面的异常自己捕获<except Exception as e:   print(e)   conn.rollbak()>是不是回滚,看怎么写的,这conn.commit()是提交,这conn.rollbak()是出异常回滚,这么写。那我们下面是不是conn.close()该关了。
import pymysql

conn = pymysql.connect('10.88.0.5','root','fspass','pymysqldata')
try:
    with conn.cursor() as cursor:
        for i in renge(3):
                insert_sql = "insert into student (name,age) values('tom{0}',20{0})".format(i)
                rows =cursor.execute()

    conn.commit()
    # 如果此时使用整个关闭的cursor,会抛异常
    # sql = "select * from student"
    # cursor.execute(sql)
    # print(cursor.fetchall())
    print(conn)
except Exception as e:
    print(e)
    conn.rollbak()
finally:
    conn.close()

##############################################写法三(换一种写法,使用连接的上下文)
那我们就说使用连接上下文,因为连接里面也提供了很好的东西嘛,那我们使用连接上下文,如果使用连接上下文的话,连接的with conn as cursor:这个上文吧,就conn他是connect就进去了,是吧,进去之后as cursor:这不是返回一个cursor给我,是吧,那我们就直接拿来用呗,拿来以后这个cursor你拿来用,拿来用以后你看我并没有with cursor,也就是说这个cursor我可能后面还要用,我把它全部给它执行完,这for i in renge(3):是不是批量执行,批量执行完之后,这个cursor有没有关闭啊?没有吧,对吧,我中间没有任何主动的关闭,或者我也用了with ,没有都没有用。那这块怎么办呢?是不是cursor.execute(sql)这个cursor依然可用啊,这个时候我们比如说,上面的代码弄完之后替我sql = "select * from student"再查一遍呗,我们可能后面是不是还写个while语句什么的,然后再来查一下,就是更改完数据库之后,你可能想反查回来,然后想看一下,马上给用户反显,给用户看一下。那所以说我们这个cursor就不建议你立马关闭,那有人说那我再开一个cursor,可不可以,也可以也问题不大。那东西不太耗资源,最耗资源的是连接。
import pymysql

conn = pymysql.connect('10.88.0.5','root','fspass','pymysqldata')

with conn as cursor:
    for i in renge(3):
            insert_sql = "insert into student (name,age) values('tom{0}',20{0})".format(i)
            rows =cursor.execute()

conn.commit()

sql = "select * from student"
cursor.execute(sql)
print(cursor.fetchall())
print(conn)

# 关闭
cursor.close()
conn.close()

# conn的with进入是返回一个新的cursor对象(新的你用不用全在于你自己,如果退出时如果发现有异常就回滚,他并没有关闭cursor和conn连接,不关闭cursor就可以接着用,它可以反复用,但是最终我们还是要求你真的确实不想用了,请你把它关闭掉。),退出时,只是提交或者回滚了事务。并没有关闭cursor和conn.

# 不关闭cursor就可以接着用,省的反复创建它。

# 如果想关闭cursor对象,用with cursor这样写(你把这个cursor对象给它with 一下,它就会在退出的时候自动关闭了,否则你就自己关闭,自己用close方法把它关闭掉。)
import pymysql

conn = pymysql.connect('10.88.0.5','root','fspass','pymysqldata')

with conn as cursor:
    with cursor:
        sql = "select * from student"
        cursor.execute(sql)
        print(cursor.fetchall())
        print(conn)

# 关闭
conn.close()

# 通过上面的实验,我们应该知道,连接应该不需要反反复复创建和销毁(因为这个代价太大,首先tcp的连接代价已经够大了,对吧,像你频繁查询写入,只要你跟数据库操作,你都得建,然后你立马就断掉。这代价太大了。所以说往往数据库这块儿呢它还会有一个连接池的概念,数据库这边都会有个连接池的概念,那我们下面来看看连接池应该是个什么样的东西。),应该是多个cursor共享-个conn

---

数据库的连接池

  数据库这边都会有个连接池的概念,那我们下面来看看连接池应该是个什么样的东西,我们来思考一下这个问题,连接池里面应该放什么?我们肯定放的是数据库连接这块的connection对象啊,那这个对象我们放在这儿呢,目的就为了谁要用谁把这连接拿走,然后在这个连接上是不是就可以拿到cursor,拿到cursor之后能干嘛?是不是数据库操作全部可以在这里做了,是吧,所以呢我们对这个地方呢就提出了一个连接池的概念。      连接池它是数据库的连接它自然里面放的都是数据库的连接对象,那如果让你来设计这样一个容器的话,我们先不要把那些容器的什么魔术方法先加上,总之我先让你设计一个最简单的连接池,你觉得你应该怎么设计。当然这个连接呢我们建议大家就是连接好的,也就是全部已经建立连接,谁要用,拿了马上就能用了,别连了之后是返回的对象还没连过,你还得再给他重新连接是吧。所以呢这块儿我们想这么实现它,对吧。      我们想从这个池里面,需要时候从池里面拿,当然池是有限资源,拿完了肯定就没了呀,对吧。我们用完之后是不是还回去,这类似东西是不是前面写过。我们做它的目的呢是减少频繁创建、销毁数据库连接的过程,提高了性能。      往往呢我们做一个项目的话,大家频繁去使用数据库这个连接对象呢我们想共享,但是呢共享也是有限度的,也不能说这个连接上你想共享怎么多少个,但是呢我能创建多个连接,但是呢也不需要大家为了多个连接都是拼命的自己想创建个他也创建一个,这创建这个成本太高了,那我们能不能先给它创建好这几个连接都可以用,但是你挑一个他挑一个就行了,当没得挑了,比如说没有了,没有了你就先等一会儿呗,反正一会儿别人就用完了。因为这个用一会儿用完了,这是由我写程序的人我保证的。因为我写的每一个操作呢它耗时时间都不会太长,这我可以保证,对吧,所以呢大家可以复用这些连接,那么来想,既然是一个容器,你应该用什么样的东西来解决这样一个问题。      那也就是说这个地方的话,我们需要设计一个连接池,对吧,那这个连接池呢我们应该可以设置它的大小,对吧,设置容器大小,也就是说你里面能放几个连接啊,你得给我说一下,对吧,然后呢我们说使用者需要连接时就从里面get一个,然后不用的时候return回去,      我们怎么设计?我们要训练思路,不要求你写出来,但是你得知道这东西我们肯定现学的东西肯定是能解决的,你有什么思路。这是个业务问题是吧,但是真实情况下数据库连接池啊是非常难写的,非常麻烦的啊,要写好真不容易,代码也不是说一两百行能搞定的,但是呢我们简单的去实现一个可不可以呢?我们现阶段学这么多知识,我们先搞定一个呗,以后随着我们的水平增加,我们再写一个更好的。      
        
  这里的连接池,指的是数据库连接池。

  连接池,是一个容器,里面存放着已经连接到数据库的连接。如果需要连接,使用者直接从池中获取一个连接,使用完还回去即可。

  从而减少频繁创建、销毁数据库连接的过程,提高了性能。

  需求
        设计一个数据库连接池
  分析
        一个连接池,应该是一个可以设置大小容器,里面存放着数据库的连接。

        使用者需要连接,从池中获取一个连接,用完需要归还。

  设计
        面向对象设计,构建一个连接池类ConnPool

        构建时,传入连接的服务器相关参数(主机、端口用户名、密码、库名称) ,还有提供一个池的容量。

        考虑多线程使用。

        使用者get从池中拿走一个连接,使用完归还池中。
# 使用队列来实现一个池。
import pymysql # 导入数据库驱动。       导入pymysql获取连接对象,就是pymysql下的connections下的Connection这位,赋值给conn,可以被put(conn)获取
from queue import   Queue # 导入Q队列。我们说现在我们准备有用,from从queue里面导出Queue大Q
import threading      # 解决还连接的问题,那既然要解决这个问题的话,看来得引入threading了,threading过来之后呢,我们想给def __init__(self,size ,*args,**kwargs):这个地方,来个比如说你叫local也行,self.local = threading.local的括号(),我们要实例化了。

class ConnPool:      #先给这个池起个名字叫ConnPool
    def __init__(self,size ,*args,**kwargs):
        self._size = size # 池子容量,也就是说这东西后来想在哪用啊。是不是想告诉大家说self._size = size最大,你就得用这么多东西了,是吧,先把他写在这儿这是一个连接池的最基本的构造。你有了这样一个容器才能下面直接用。
        self._pool = Queue(size) # Queue(size)创建一个池子并设置池子的大小,赋值给self._pool,Q现在的size多少,现在的池子容量。      Queue拿过来之后self._pool = Queue()这是不是可以,那既然是这样的话,我们点进去看 一下Queue的源码,def __init__(self, maxsize=0):说它是第一个参数给什么,maxsize=0是不是最大size。那最大size给不给呀,你不给他说也可以是吧,不给的话他自己就想办法来给你做些事。但是呢我们最好说,既然我要用这么多,是不是把size给def __init__(self,size)他的size就行了,在传给Queue(size)。当然self._size = size你这个size可以存下来。比如说别人问我现在这个池子容量有多大,你是不是要提供给别人,所以你这size可以存,你要不存也没关系。但是你一般情况下,别人问你最大容量是多大?或者你现在容量是多大了,你是不是要给别人,如果说你允许Queue(size)他这个最大值,你也认为这是一个最大值。你现在最大值没用完,你现在当前是多少,是不是你也可以通过一种机制来提供给别人,是不是就要访问Queue现在的size是多少,反正Queue(size)返回给他,虽然说这东西也不太准确,但是好歹能用嘛,对吧,也可以啊。这东西应该用什么方式提供出去,用properly属性的方式提供出去。用属性装饰器给他提供出去。因为这种就是属性,对吧,你别让他在这直接在点什么东西了,不要给他点了,这东西就是属性就用属性的方式给它提供出来,到底实现只读吗,当然只读啊这size还能准你改吗,对吧,所以说property最合适的他改不了,不允许他改。我们这样的话就相当于已经初始化了一个,那下面怎么办呢?是不是应该有一个get方法呀。
        self.local = threading.local() # local是全局的,所有线程都可以用的属性了,不同线程使用这个属性中的一些东西,给他填个东西,这个只跟当前线程相关,不同的线程使用为每个线程开辟一个独立的空间进行数据存储。      threading过来之后呢,我们想给def __init__(self,size ,*args,**kwargs):这个地方,来个比如说你叫local也行,self.local = threading.local的括号(),我们要实例化了。这是一个相对来说是不是所有线程都用。那他相当于这个线程池被所有线程用了,所以它是一个相对来说它是一个全局的概念。但是呢因为你在不同线程里面用。你每个线程,反正我本来这from queue import   Queue 东西,我写这个Queue 东西就是一个线程安全的,我现在只限定你在多线程的场景要用吗,对吧。所以说我们现在说如果多线程场景来之后,那起码我能保证在线程内,在某个线程内,现在你想,有一个线程拿了一个连接,另一个线程拿了一个连接对不对,那你各拿各的就是了嘛,当你拿完的时候是不是有。有谁啊,threading.local(),是不是有这把锁啊,这把锁保证你在还能get到吗?是不是get不到啊,其实他是线程安全的,你放心。你put多了也不行,你put晚他也塞不进去了。对吧,所以说我们认为你现在能塞回来,当然说到前面有很多种判断方式,对吧,如果你是要做一个第三方库,你要提供给别人,def return_conn(self , conn:pymysql.connections.Connection):你这块肯定要注意的,你不能乱写的,这块我们要做很严格的筛选,不是说你想塞什么进来塞什么进来,我们原来连if isinstance(conn,pymysql.connections.Connection):这句都不加的话,是不是return 我写个1进来都没问题呀,对吧。所以这种简单的操作我们还是要过滤一下的啊。好,这东西加了之后,现在就是说有这个ThreadLocal究竟能为我们做什么事儿呢?我现在怎么改造啊,我有这个Local了,我应该怎么改造呢?大家思考一下应该怎么改造。我先改哪个方法来呀,其实跟线程相关不就两方法,不就干get_conn和return_conn吗。那么get方法应该怎么改呢?
        for i in range(size): #初始化把Q的池子填满。     for循环,取你要给定的size大小,它循环几次,把这个池子填满。然后下面就做连接的事情。
            conn = pymysql.connect(*args,**kwargs) # 创建一个mysql数据库连接。      conn应该等于pymysql.connect('10.88.0.5','root','fspass','pymysqldata'),这就获取了连接,for循环几次获取几个连接。但是下面要把它加进Queue里去,是吧。
            self._pool.put(conn) # self._pool.put()项目加入池子,conn连接项目,(将项目放入队列)。     self.__pool下的put把conn这个连接加入到Queue的池子里去。这样是不是在初始化的工作里面就已经把这个Queue给他填满了。填满了,就等着别人用了,那这边return self._pool.get()拿呗,拿到最后一个拿不了,是不是,这是阻塞模式了blockking,是吧,return self._pool.get()阻塞住了。所以地方到时候阻塞住以后他在拿拿不了。但是你要考虑,如果大量的人在使用的话,现在如果连接不及时归还,就会出这样的问题,我们是借用了Queue的线程安全性,让他来保证我们self._pool.put(conn)和return self._pool.get()这些操作是不可被别人打断的。他有锁,所以他其实就是一种原子性,它get的时候不能被别人打断,他put的时候也不会被别人打断的。因为他已经给你在代码级实现了。他在里面加过锁了,所以你不用担心啊。所以我们通过Queue的方式就很简单,把这个事情完成了。那如果是这样子的话。那我们发现,那def __init__(self,size)这块是不是还得传参数啊。怎么传呢?def __init__(self,size,*args,**kwargs)。那conn=pymysql.connect('10.88.0.5','root','fspass','pymysqldata')这块的话我们就要注意了啊,现在我们def __init__(self,size,*args,**kwargs)这样是不是就可以把这个'10.88.0.5','root','fspass','pymysqldata'参数给他传进来了,对吧,然后conn = pymysql.connect(args,kwargs)我们把参数给他传过去,对吗?conn = pymysql.connect(*args,**kwargs)要解掉,因为你想啊*args这东西一个元组,元组扔过去,他conn = pymysql.connect(args)哪里认呀。所以写的时候,要想一下,经常会忘掉的。    因为你connect这边是不是你的参数我也不确定啊,你是什么我就*args,**kwargs传什么给你就是了,对不对。那参数 不对的话,到时候可能connect这个方法是不是该报错报错啊。那我也关不着呀,你写的不对啊,对吧,你比如说人家这个地方非要写一个host等于什么东西,你def __init__(self,size ,*args,**kwargs):这块你怎么限制啊?我们看connect他这个里面好家伙这里面,是不是参数二十来个都有,是吧,你也写不完呀,怎么办呢,*args,**kwargs甩过去呗。、在这都有是吧,2十来岁左右你也写完啊,怎么办呢?甩过去呗。因为这东西是一次性创建的。def __init__(self,size ,*args,**kwargs):这里size是不是必须给呀,其他*args,**kwargs都是可变的。一个是不是必须给了对吧,其他都是可变的。那如果其他都是可变的话,是不是有可能有默认参数啊,其实什么都不写,connect是不是也使用默认参数啊。connect使用默认参数也能连,他连是本地3306。       我们有的时候是如何使用才决定了,我如何改造他们。现在这是太粗糙了,我们想办法改造改造啊,有的已经开始设计上下文的东西了。线程池也可以提供上下文啊,对,可以提供,关键是你怎么用是不是才怎么设计啊,不然你放那你怎么玩啊,对吧。好,那我们来看看这块应该怎么写。我们来看一下,首先你得建一个池吧。pool这里我们要ConnPool给他初始化,比如初始化来5个,pool=ConnPool(5)我们把这个池已经建立了,池建立以后这里面是不是已经有了连接了?对不对有连接了,pool=ConnPool(5,'10.88.0.5','root','fspass','pymysqldata')我们通过这种方式就已经把这些连接都给他已经建好了下来,不就是get的问题了吗,get和put的问题是吧。

    
    def get_conn(self):     # 获取完Queue的size,使用一个get方法,那我们就怕get方法太没有意思是吧,我们就给他再加一个东西def return_conn,是吧
        conn = self._pool.get() # self._pool.get()从队列中返回一个项目并删除。拿到最后一个拿不了,他就阻塞住blockking,     我们说get方法是不要return回去一个东西啊,怎么办呢?return self点pool这个里面要给他get一个,get要不要阻塞呢。就看你后面想怎么做了。因为比如说今天我给他5个,现在有5个人已经全部把这个连接全部拿走了,那这时候怎么办啊?这时候只能阻塞了,对不对,但是你说我现在不想用阻塞方式,我非得用不阻塞的方式怎么办?抛异常。对吧,要抛异常,拿不到就抛异常。当然你跟人家约定好说我这个版本呢不抛异常,我这个版本呢只会给你返回个none或返回一个faise之类的这也行。总之你得有个标识给我,是吧,所以说你用抛异常的方式也没问题,或者用阻塞的方式也没问题。但是你如果要长期阻塞的话,那你就必须得考虑这阻塞时间最长能到达多少时间,万一这个人占着连接都不给还回来怎么办?那你其他线程全阻塞死了。那你能不能忍受呢?你要不能忍受,你就用非阻塞方式或者阻塞超时的方式,用这些方式来处理啊。然后如果阻塞超时,你拿不到你想要的东西怎么办?抛异常出去,给他主动抛个异常出去,用这种方式可以。所以呢我们现在return self.__pool.get()先用一个最基本的实现,这都是锦上添花的事情。但是你首先得把这个结构写出来是吧。该写put了。
        self.local.conn = conn # 赋值语句,返回的项目丢到local线程全局变量中,conn链接为所有线程都可以用的属性了,被get过了self.local.conn值就不应该为空.      其实跟线程相关不就两方法,不就干get_conn和return_conn吗。那么get方法应该怎么改呢?把self._pool.get()这个值赋值给self.local,这个值是不是告诉你这个东西是不是在这儿啊self.local=self._pool.get()。对不对,他的意思送给local嘛,这可以啊。但是这么用吗?你只说对了一半儿吧。我们说这东西能这么用吗?local是个全局的吧,怎么用啊,conn = self._pool.get(), self.local.conn = conn这是不是送进去了,然后return conn这东西是不是拿走了,当这个线程还的时候,比如现在有人还了,我知道这个线程曾经拿走过谁,是吧,前提是这个线程里面它是顺序用的。也就是说前面这段是不是把这一直用完到他还了,然后中间不能再插入其他人又把它再拿一个东西走啊。不能这么用啊。刚有个人拿走,现在是不是又有人说再get一个。同一个县城里面,self.local.conn这个conn值是不是被覆盖掉了,这个能想出来吧。同一个线程里面刚有个人get过,现在是不是又有个人get,是不是前面值就被替换掉了。所以这个还是有风险的啊,我们只解决了一半,还有一些地方没有解决完。所以说这块用local的时候要小心,我们local是不是self.local.conn给他定义的这东西啊,所以说这个定义了就ok了。那这个地方当然可以做一些判断,你可以做个什么判断,就不允许别人用了。如果说,你看我们现在这个语句,如果说self.local.conn = conn这个线程拿过了,这个conn值,他就不应该为空了,是不是啊,至少不应该为空了,被人拿过了,拒绝他拿。没人还就不继续,又人还了是不是应该清空啊,对吧。有人还了就应该要清空,所以这是我们要注意的地方。但是呢所以说我们在用这些地方的话要特别小心。就是说线程里面不是说做不到,可以做到。但是有问题,有问题可以用一些简单判断,可以把这个事解决掉。那def return_conn(self , conn:pymysql.connections.Connection):这个地方的话,现在有人self._pool.put(conn)要还了,他如果conn还的是对的话,你是不是允许他还呀,对吧,你肯定是要允许他还,他还完了,你要干什么呀?
        return conn # 返回的项目,返回到函数调用处
        
 
    def return_conn(self , conn:pymysql.connections.Connection): # self 是一个实例,需要传递进来一个conn连接,      我们就怕get方法太没有意思是吧,我们就给他再加一个东西def return_conn你肯定不能直接用return了,对吧。    那我们究竟应该怎么办呢?我们说这Queue(size)东西他自己天生加锁嘛,是不是这个锁就不用你自己在加了。那你下面要做的事情就什么?我们说get方法是不要return回去一个东西啊,怎么办呢?
        if isinstance(conn,pymysql.connections.Connection): # 判断还回来的conn链接对象是否是 pymysql.connections.Connection 是一个连接类。      if如果是isinstance是conn这个类型,对吧,这至少能保证类型是对的把,但是保证类型对就安全了吗?我是给你弄个类型,但是我是连其他库的连接可不可以。我给你还回去了。是吧,所以我们这块能不能找一些方法能把它给记录出来,对不对,其实有很多方法可以记录pool.return_conn(conn)这conn对象是不是可以取id,你到时候把这对象的id是不是拿过来看一下,这是不是拿个字典记录下就行了。这个对象的id是多少,那个对象的id是多少。或者说你拿一个列表来记录一下,或者拿一个集合来记录一下总可以了吧。因为我们说内存地址不可以完全一样吧,完全也就该去除嘛。这样的话他每还回来的东西,你去集合里面是不是找一找。他还回来东西,如果不在这个集合中,对不起,那我不认识,我连类型都不用判断了,只要地址不同就不是的,但是也有风险。类型及内存地址一块判断才最安全。你想如果conn对象内存这块回收了。恰好这个内存被回收完之后,这块变成空闲内存。这个时候我要申请一个conn对象,然后我故意把这个conn对象又给你了,恰好就这么准。这个内存地址就是原来的内存地址有可能的。这是极其有可能出现的。那这个时候你说单凭内存地址判断是不是也不合适啊,因为这是有风险的,有风险你就不能干,就不敢这么做。所以又在这个地址上,同时又是我这个类型的。ok我把你取回来了,就这么做。那当然也不排除用户是不是在这儿,有时候给你随便做一些其他手脚。比如说这连接我特意给你关掉了再塞回去,那没办法呀。关掉又不是释放,对不对,他依然可以把这个对象,在他没有完全被清理之前,这毕竟是可以还回去的,没有问题啊。是吧,所以说这个地方用起来其实要做判断的话,很多事要判断,我们就不考虑那么多了。但是至少我们说这一块做一个内存判断,来做一个类型的判断总是是可以吧。什么类型呢?def return_conn(self , conn:pymysql.connections.Connection):是不是就 conn:pymysql.connections.Connection这一块的类型啊。如果你符合这个类型是不是在放进来,至少要做个简单判断嘛,对吧,这样的话我们就可以把这个conn对象再给它返回回去,对吧。 可以运行一把,那有人说,那我这块儿非得想看一下size。如果你要真想看一下size的话,其实你也可以提供一下怎么做呢?@property def size


            self._pool.put(conn) # self._pool.put()'''将项目放入队列。这个线程是顺序用的,当这个连接拿走,在换的时候中间不能插入,刚有人拿走,现在又有人在get一个,同一个线程里面self.local.conn,这个前面的值就替换掉了。      get完之后,self点__pool我们要put()回来,把谁put回来是不是缺点东西,def return_conn(self , conn),这个地方传的是不是就是conn,把conn拿过来self.__pool.put(conn)。但是这个conn是谁呢?那没办法,那我们要导入pymysql,获取一个连接对象给conn,对吧。但是我们发现这只是把架子写完了。这连接池里没东西。当然一种是懒方法,用的时候加,你要我给你加,加到我这个值没有的位置。但是这会带来一个问题,跟我们现在使用方式有关。因为你现在要这样子的话,你现在如果Queue(size)定的叫最大大小的话,现在是人家return self._pool.get()拿一个没有你self._pool.put(conn)创建一个,拿一个没有,你创建一个,你永远满足不了Queue(size)最大值啊。那就相当于你一直是不是在创建啊,因为这是拿走了呀,这相当于是个缓冲区是不是一直就不满啊。所以我们如果你非得要用这种方式的话,那只能怎么做啊,只能提前创建好了。其实这也是一种思路,你要一个我给你弄一个,要一个,我给你弄一个,就是我们自己来实现这个东西了,就不用Queue因为你现在如果用这个Queue的话,是不是return self._pool.get()拿走一个,你再self._pool.put(conn)放一个,发现你都满足不了啊,这Queue(size)一直都不满,对吧,一直都没有满起来。那没有满起来的话,你他总是get的时候的被阻塞,然后你说self._pool.put(conn)来给你一个新。你这不叫连接池了,你这叫发连接,而且更根本就不控制上限是吧,你要个连接给个连接了,对吧。所以说我们这块儿就是说看怎么弄。
            self.local.conn = None # 还完,就把连接给清掉。      现在有人self._pool.put(conn)要还了,他如果conn还的是对的话,你是不是允许他还呀,对吧,你肯定是要允许他还,他还完了,你要干什么呀?self.local.conn = None 这是不是就清掉了。如果要写的严格,是不是要在get方法里加判断,这个他东西是不是None。但是呢还有一些地方有一些风险。什么风险?你自己测试一下。如果说这个self.local肯定存在,这是你建的,对吧。但是conn 在不同线程中,有可能这个conn 根本就不存在。如果不存在,你直接self.local点conn,直接抛异常。还没创建呢,比如说你还没给他赋这个新值的时候,你这个时候直接来做self._pool.put(conn)这事儿,他会出问题的。或者你直接self.local.conn = None要来调用这个conn 值的话会出问题的。当然这self.local.conn = None都是赋值语句,这个问题倒不大,对不对。但是你直接要取他这个值,你直接想取他这个值会带来一些问题。所以说我们在这几个多线程这地方,要注意啊,他有很多事儿,那具体怎么写。      __enter__方法倒是好实现,可是__exit__的时候,不知道连接是池中的哪一个了, __exit__的参数中也没有指明是哪一个连接。如何解决?是否可以考虑Threalocal变量解决。当你__enter__的时候。你要做一些实验了。如果说这个属性不存在。

    @property # 把方法变成属性调用
    def size(self):
        return self._pool.qsize()  # self._pool.qsize()'''返回队列的大概大小(不可靠!)。      那有人说,那我这块儿非得想看一下size。如果你要真想看一下size的话,其实你也可以提供一下怎么做呢?@property def size,那你要这里写size的话, def __init__(self,size ,*args,**kwargs): self._size = size上面这里写size就不合适了吧。 def size(self):这个地方的size就不是self._size这个size了,self._size这个size相当于调的maxsize最大size,就是他池的大小,那size当前大小是不是你可以换个名字,但是你也可以叫size,然后这块儿,我们应该写什么return应该把 self._pool跟size有关的,是不是就qsize()他呀,你把它返回去就行了。就用他的,你就甭自己再写了,就用他的。他做不准,那是他的问题,他不准你也不准一样的,def __init__(self,size ,*args,**kwargs):这块的size到时候修改一下名字,这个就不叫size了叫maxsize,使用这种方式就可以看一下池到底多大。把这个连接打印一下,然后我们把这个池打印一下,我们看这个池到底是多少。
 

    def __enter__(self):       # 那你应该怎么写?让我们来思考一下这个问题。你既然是__enter__的时候,你不就是想要一个连接吗,对吧。在这个里面肯定跟连接相关。def __enter__(self): return应该用的是self.get_conn()吧,用这种方式,这def __exit__(self,exc_type,exc_val,exc_tb)呢是不是理所应当的,self return_conn()是吧,你理所应当就写成这个样子。但是从哪来呀,更有甚者直接把def __exit__(self,exc_type,exc_val,exc_tb)他们改了,改成def __exit__(self,conn)这样的东西了,你好大的胆子,你连参数个数都敢改嘛,不可以啊。他到时给你调这个函数的时候,他要跟参数个数至少是要匹配的呀,是吧。假设他现在没异常,还好,他要有异常,你这怎么用啊,参数都送不进来了。参数签名一般不能乱动,尤其是这种已经人家定死的东西,这魔术方法是定死的东西,不可以把这种参数签名随便改的。这是不是出问题了,这出问题就是我们要解决的问题。也许在def __enter__(self): 这么做return self.get_conn()还是对的,但是在 def __exit__(self,exc_type,exc_val,exc_tb):这么self return_conn()做就不太对了,对吧。那你怎么还回去呢,我们的目标是这样子的,我们的目标是这样子。那么来看首先你得解决什么问题,我pool = ConnPool(5,'172.18.13.183','root','root','test')通过一个pool,我现在想with一个pool,我目的是想得到什么呢?是要得到一个连接吧。但是你说我能不能给他一个cursor呢。好还是不好呢?当然他又他的好处,你直接给个cursor还省的烦了,用完就关了呗。但是你还是不知道刚才是哪个cursor。哪个连接的哪个cursor,你还是不知道。所以有没有办法解决?到底是哪个链接啊?好几个人都在用链接啊。比如说现在有5个,有5个连接全被别人用了,现在有人要还,你知道谁还吗?关键是你要还,东西在哪,你拿不到啊。你说你再写个属性,写个实例属性?,你写个实例属性你怎么存啊,5个人都拿走了,你这5个人,反正5个人拿的是5个不同的东西,对,这是不同的。关键是现在人家要还哪个,人家还没还呢,你怎么知道是哪个。你怎么知道那个人是谁呢?我记下你的线程id这到好像也行。拿你线程id起码是唯一的对吧,线程id是唯一的。那你敢说一定是这个线程用,就这个线程还吗?我们发现线程之间是不是也可以,我这个线程是不是里面再创建线程啊,我是不是可以往里面传啊,对吧,可以往里面传,所以说这个问题还是非常麻烦的问题。所以呢线程池在这一点的设计是非常难的。但是有一个简单的变通方法。解决不了刚才的这个线程里面再创建线程传东西的,他很难解决这样的问题。但是我们发现跟线程相关,我们总是有些手段吧。有什么手段呢?跟一个线程相关的东西。 ThreadLocal是吧,那是个类是吧,那是一个全局变量,但是里面存的东西是不是只跟本线程相关啊。跟其他线程就不相关了,所以在线程里面使用,当然我们前提是这个线程里面只有一个人在用它。我们有这么一个前提。这个线程里边只有一个人在用它,而且是顺序的在用。有这么个前提。你要想好这个模型,因为现在确实出问题了,我现在就想还我都没地方还了,对不对,我怎么还,我怎么知道是哪个还回来,你这exc_type,exc_val,exc_tb参数又不是你定的,你怎么办啊。所以你要借助with语法这就出问题了。所以问题在这儿,所以我们发现这有个难处啊,所以说大家写了上半句,下半句就写到这,就打问号了。写不出来了,对吧。你with参数千万不敢动啊,再次提醒,这exc_type,exc_val,exc_tb不能动的。            那好,那我们来解决一下这个问题。那既然要解决这个问题的话,看来得引入threading了
        if getattr(self.local,'conn',None) is None:  # 判断是否为None,第一次使用时self.local.conn属性有可能不存在还没创建,说明一次连接也没有拿到,(getattr() 函数用于返回一个对象属性值,获取self.local对象的conn属性,如果能取出None,说明self.local.conn没创建)
            self.local.conn = self.get_conn() # 如果没用创建,进行创建,将self.get_conn()调用函数返回的结果赋值给self.local.conn
        return self.local.conn.cursor() # 如果不是None,连接已存在创建了,就可以进一步操作返回一个游标内存地址的引用。      当你__enter__的时候。你要做一些实验了。if getattr(self.local,'conn',None) is None:如果说getattr(self.local,'conn',None)这个属性不存在is None。是不是self.local.conn = self.get_conn()我们给他创建进来。对吧,我们要做这样一个事情啊,看怎么取的。我们讲这个 getattr(self.local,'conn',None)时候你对这个self.local,'conn'对象来取的话,他取这个方法是它内部的代码已经保证了他会跟这个当前线程相关,然后取,如果他是None说明没创建,没创建self.local.conn = self.get_conn() 就继续这么做。如果创建return self.local.conn.cursor() 我返回一个游标给你。这是什么意思呢?如果你是个None说明你这个是不是在这个线程中一次连接都没拿啊。你从来没有get过嘛,如果现在你不是none,什么意思啊?拿过了 ,我是不是就可以更加给你好用点,但是return self.local.conn.cursor()你这个地方到底是,返回一个conn连接还是返回一个cursor在于你下面那个with,以后你想怎么写代码。这块我们不做强制性要求,但是你返回连接也成,你返回cursor也成,问题都不大。好,那我们来看为什么要这么写呢?最后看怎么用,你就知道为什么这么写了。我们来看def __exit__(self,exc_type,exc_val,exc_tb)这块应该怎么写.

    def __exit__(self,exc_type,exc_val,exc_tb): # 既然拿到连接了,判断这个连接有没有异常,如果有异常,该回滚就回滚,该提交就提交
        if exc_type:  # 判断这个连接有没有异常,如果有,进入语句块
            self.local.conn.rollback()  # 执行回滚
        else: # 如果没有异常,进入语句块
            self.local.conn.commit()  # 执行提交

        self.return_conn(self.local.conn) # 把连接不关,还回去,连接在同一个线程内复用,
        self.local.conn =None  # 把游标清空,标注为None,在__enter__函数内,重新获取连接,重新返回一个游标,注意,游标也没有关闭。      我们来看def __exit__(self,exc_type,exc_val,exc_tb)这块应该怎么写,if exc_type: 这块我们知道这是不是所谓异常问题啊,所以异常的话对于self.local.conn.rollback()他来讲的话,是不是当前线程所拿到这个连接,我管你上面怎么写,总之你得有个连接吧,你有个连接,你才进with子句,你没连接你进什么with子句,我肯定不让你进啊,你拿不到连接是不是该阻塞,阻塞呀。对吧,我不让你进,既然说你已经进入with语句块,然后你执行完了def __exit__(self,exc_type,exc_val,exc_tb):出来,我是不是要if exc_type判断一下你到底当前这个连接来讲的话,在他执行过程中到底有没有异常啊。如果有异常,self.local.conn.rollback() 该回滚回滚,如果else没有异常怎么办,是不是self.local.conn.commit()该提交提交啊。然后我们self.return_conn(self.local.conn)是不是要把他return回去啊。然后后面self.local.conn =None清理掉。我们要做这事儿。也就是说连接我可以拿到,但是说你这return self.local.conn.cursor()返回cursor,你这边又做回滚提交这事儿,做什么意思?这不是我们在仿照conn连接中的上下文来写的,Connection类不就这样写的(新版本中没有上下文)。看我们怎么用。with pool as cursor:这不就可以了吗,我拿到了是谁呀?,我是不是return里一个cursor, 只要我能成功拿到连接,我是不是可以拿到一个cursor,但是我们要求是不是这个线程内只有一个人能拿到了,其他人最好不要拿。这一拿是不是出问题啊,所以做一些简单判断就可以了,那这个self.local.conn = self.get_conn()地方只是判断什么?,这个地方判断,这个链接有没有被人拿过,他不阻止人家拿第二回吧。但是呢这个问题我们可不可以这样思考。如果说你现在拿的self.local.conn连接,这连接有拿第二回吗?有第二回get吗。如果if getattr(self.local,'conn',None) is None他不是None呢,我self.local.conn = self.get_conn()有self.get_conn()第二回get吗,是不是我依然会在self.local.conn他上面取东西啊,我依然在self.local.conn他上面取东西的话,我取什么呀?我return self.local.conn.cursor()是不是取个游标cursor回来。你想想有问题吗?我我重拿一个游标就是了,反正连接我们线程都有了,咱俩一块儿用就是了嘛,是不是刚才我们说如果你不这么考虑问题的话,当你去访问的时候,你是不是要get一下前面那个人已经get过了,你自己在get,又拿到一个连接。这样不合适吧。      什么东西可以多次拿?,cursor多次拿。也就是说咱俩在一个线程里面,用ThreadLocal ,在同一个线程里是不是管同一个local里面的conn啊,咱俩现在管同一个conn的话,来,既然你前面这个线程里面,前面有个语句,他self.local.conn.cursor()已经开始使用self.local.conn这个连接的,然后拿这个cursor,对我另一个是不是新的也想用啊,也想用在同一个线程中,这个连接大家是不是用一个就行了。但是大家能用同一个游标吗?你操作的数据库的表是不是都不一样啊,对吧,有可能不一样。那这个时候你用同一个游标是不是不可以啊,但是同一个连接。我可以用你也可以用啊。是吧,我们本来就是想连接复用嘛,对吧。但是游标是不一样的,连接本来就是可以复用的。所以我们通过这样一种方式就可以解决连接在本线程内,被复用的过程,然后呢cursor继续拿呗。你想拿几个拿几个,对吧,别忘了最后关掉就行了。好,那def __exit__(self,exc_type,exc_val,exc_tb):我们这地方关,这个时候怎么弄啊,我们是不是把它给self.local.conn.commit()提交就行了,因为你是不是上面做了很多操作啊,你做了很多操作,是不是我def __exit__(self,exc_type,exc_val,exc_tb):要退出啊,我要退出的时候怎么做呢?我该self.local.conn.commit()提交提交,该self.local.conn.rollback()回滚回滚,然后self.return_conn(self.local.conn)把连接还回就行了。self.local.conn =None连接等于None,连接清空。连接清空是不是下一次 再用的时候。大家想,你把连接已经清空了。另一个人他拿到了一个cursor。但是他这个cursor是不是还可以继续用啊?这个连接清空是你本地这个连接是吧,本地连接,但是你这个cursor对象里面是不是还记得这个连接是谁啊,你只不过self.local.conn =None 自己把它给遗忘了,所以这个cursor是不是用的现在还安全吧。这一块的话,我们先看self.local.conn =None这,当你把它清空,刚才我们说是不是有多个的可以进来拿游标嘛,游标虽然拿到,游标对象在了,我现在把这个self.local.conn =None连接这块,给他写成个None,有没有问题嘛。我们现在问就是这个问题嘛,因为你cursor里面是不是传一个self,所谓那个self ,那个self当然不是cursor的self,那是个连接的self,也就是cursor里面是不是还保存着这个连接对象啊。对不对,cursor还保存这个连接,所以这个cursor只要没关闭,他还可不可以用啊?   ,当然可以用啊,只不过再有人进来。这个时候这self.local.conn是什么呀? 他问你if getattr(self.local,'conn',None) is None:这个东西conn是不是None啊。当然是None,是None怎么办,是不是self.local.conn = self.get_conn()再拿一个连接过来啊,再拿一个连接过来之后,然后再拿一个cursor,那就拿呗。 那我们现在分析这玩意有bug吗?他这个cursor的话,他到最后他自己会关闭的嘛,对不对,就是说你想什么时候关闭,那给你了,我不管,就跟人家Connection里面写的,他这个__enter__是一样吗(新版本没有这个上下文),cursor我给你,但我不管关啊。不是你的事,你不关资源耗尽是你的问题啊。我现在分析是self.local.conn =None当这个东西给他个None,会不会再来程序大bug的问题啊,有没有?对不对,我们发现不可以嘛,不出现这个问题吗?如果是现在有连接,是不是拿呀,但是只要有一个退出,是不是这self.local.conn =None东西就被清成None。被清成None这些cursor有没有问题?,这cursor没有问题啊。因为这cursor内部已经记录了这个对象,引用这个对象,而且这个连接我们说不关,不是释放呀。这self.return_conn(self.local.conn)连接你归还了,不是说是同一个东西吗,对不对,就连接不关,不释放,要还回去。我们要做的是这样一个事情。 你把self.local.conn =None它清了,人家的对象是不是还在,是不是self.return_conn(self.local.conn)又还到队列里面去了,你队列都记得,cursor里面也存的是这个内存地址的引用嘛,有问题吗?没有问题的嘛。我们有的时候是写完了,但是怕出bug 是不是脑子要跟一遍啊?这东西你调试不出来的。多线程下你根本调试不出来,你必须用脑子跟,去想这个线程模型。这是功夫啊,要练,你不要遇到,烦了,不管了,不看了,不看了,不能这么做啊,我们要用脑子跟的。花这么大精力讲这个东西,就是说以后遇到这种代码的话,我们应该怎么去写,写了之后应该怎么去分析,然后找到里面到底有没有bug 的。因为这种bug太难跟踪了,你就是print,全场满屏幕都是print都没用。因为你多线程访问时候都是乱七八糟访问的,你怎么知道啊?哪个访问的,不知道啊。 就算你把线程id返回过来。满屏幕东西根本都不想看。没法看,必须用脑子跟踪,就用刚才这种方式跟踪对不对?所以说这样的方式就导致了我们使用的时候要注意什么情况。所以我们发现现在这种写法用ThreadLocal合不合适啊,还是可以的啊。也就是说,这个只限于在本线程内是吧,大家随便用,大家随便用的话,就算你把它制成None也无所谓啊。我这个cursor已经拿到过了,我cursor里面记录着那个,而那个对象它本身并没有释放,因为他又回归到队列里面去了。他在内存地址是不是一直没动啊,对吧,我这cursor是不是照常使用啊,直到我自己把这cursor关闭就行了。否这这个cursor可以直接往下用的。对吧,那有人说这线程要关啊,这线程要关的话,跟线程相关资源全部关闭的呗,那有什么可说的,对不对,那ThreadLocal里面跟这线程相关的所有内容全部都没了。对吧,那自然就不用考虑那么多问题了。关键是这线程要是没关,这里面处理怎么办。所以我就想你这么一句self.local.conn =None有没有风险,我们是不是要思考一下。      if getattr(self.local,'conn',None) is None:这块要注意啊。因为conn这个东西它有可能,这个属性不存在。比如说我们这么写 if getattr(self.local,'conn') is None 不能这么写。因为这个属性有可能还没创建呢,在当前线程是不是第一次进来的时候,是不是这个属性还不在,所以我们是不是可以用getattr(self.local,'conn',None)这种方法是不是保险啊。用这种方法来判断他究竟是不是None,对吧,千万不敢直接if getattr(self.local,'conn') is None,这东西self.local.conn = self.get_conn() 有可能没有啊,这self.local.conn属性没有啊,这属性没有就报属性异常了。所以说在保险的情况下,我们要用getattr放心,getattr取conn他的时候,self.local内部就已经实现了,一定会把conn它引导到当前线程上去的,不会跑别的线程上去的。好,所以通过这样一个分析,我们发现问题不大。你要是敢把那个conn确实给他释放了,就真的conn你要把他释放或关了,这后面就事儿多了是吧,要真把它给释放掉。你说del conn 删了是吧,那真是在合适的时机就给你把那个对象给清了, 这一清,就完了,所有引用他的包括你的Queue中引用它的都会出问题了。对不对,我们现在用这个Queue就是要创建后连接对象怎么办?都扔在里面不清除嘛,什么时候清除,当然我们自己要写一个Queue,Queue 的close方法在close方法中,我没写啊(打比方),在close 方法中是不是依次的将自己所有的元素弹出来之后,依次的close掉。你叫释放连接资源,对吧。不要看这个代码简单我就这么一说就过去了,我不跟你做这样分析的话,你根本就不知道这地方有很大的风险。这个风险的分析对你实现多线程的时候一定要做这样分析啊。这个很重要,多线程的时候,尤其是大家在多个方向上,来回的去在不同线程中去使用这些资源的时候,我们一定要分析的很彻底。否则多线程是极不安全的 。那既然有这样的话,我们就要拿到cursor,拿到cursor下面的使用方法,是不是跟conn的连接方法差不多了。那我这是不是就相当于自己管的呀,不是ui 自己关呗。
   
# connect('192.168.139.187','root','root','test')

# 使用连接池
## 我们就要拿到cursor,拿到cursor下面的使用方法,是不是跟conn的连接方法差不多了。那我这with cursor是不是就相当于自己管呗,我自己用完自己关呗。因为连接池的特殊性,所以说我们这块呢,可以做一些这样的事情。好,那我们发现这一块是在for x in cursor: print(x)这是在做什么事儿呢?这是以什么意思呢?你想我对一个cursor的遍历,我能遍历什么东西啊,难道遍历它里面有几个游标吗?所以这东西是不是不知道啊,但是这东西我们知道,如果你敢这么用。说明这个cursor一定会调用他的某个方法。还记得我们讲容器的时候,那个方法,对不对。那我们去瞅一下这cursor里面到底是什么东西,我们看一下源码,不就什么都知道了。点一下cursor,看这   def __iter__(self):   return iter(self.fetchone, None) 明白什么意思吧。这不是相当于在拨一下拿一条,拨一下拿一条嘛,是吧,这是我告诉你,如果你是一个查询的话,你完全可以迭代这个curser 。我们想curser 里面不就是个游标嘛,拨一下转一下,他拨什么东西,这不就是游标嘛,往下走,往下走,往下走一直到结尾嘛。这不就遍历结果集吗,对吧,所以说我们通过对cursor他的代码的阅读,我们知道原来它是一个def __iter__(self):可迭代对象啊,对不对。那我当然可以迭代你了,我迭代你,相当于self.fetchone对每一行记录进行迭代了,一行一行迭代出来了,对吧。好,我们通过这种方式,是不是就可以了啊。顺便在这就把cursor的迭代讲了。在那我为什么这么做,你应该明白了,我通过这种方式是不是一样,可以拿一个,反正我最后是只能操作cursor嘛,但是你给我个连接,我是不是把这个连接拿来之后,我还得自己在这个连接with一下as cursor呀,还是这么做。那与其这样,你还不如with pool as cursor给我一个as curso呢,然后我with cursor拿这个curso是不是做下面相应的一些操作,对吧。
pool = ConnPool(5,'172.18.13.183','root','root','test') # 实例化类,对seiz大小,连接传参,root用户权限很大,可以看到的连接很多

# conn = pool.get_conn() # 拿一个
# print(conn) # 拿到的连接,打印看看,我们get到一个是不是可以print(pool.get_conn())打印一下,但是这块应该把他最后要还回去,给他还回去。conn = pool.get_conn() print(conn)这么写

# pool.return_conn(conn) # 把连接还回去,但是这里面其实还有很多问题的,你比如说你还的类型不对,怎么办呢,是不是要做检测啊,永远不要相信用户是吧。你要检测一下这个类型真的是这东西吗。检测起来是不是也很简单,def return_conn 这里if如果是isinstance是conn这个类型,对吧,这至少能保证类型是对的把,但是保证类型对就安全了吗?

# print(pool.size) # 打印池大小。      把这个连接打印一下,然后我们把这个池打印一下,我们看这个池到底是多少。      可以看到少了一个对吧,拿走一个,通过这种简单的方式可以看到。      这是我们讲的一个池,那我们拿到这个池的时候应该怎么做呢?我们拿到这个池的话,下面要做的事情往往都是跟池相关的。比如说with conn 这应该是as谁呀? 是不是就是cursor了。这是你拿到之后究竟自己想怎么做,这实际上跟池本身没有关系,因为池它是用来做连接对象存储的,跟池本身没有关系的。那所以说我们要这么做,然后呢下面是不是用科萨二点这个东西是吧。哎,但是这一块你发现也有它

def foo(pool):
    # 只要可以拿到连接,就可以拿到cursor,要求这个线程内只有一个人能拿到,别的人不要拿。对拿到的连接做连接,这一步跟池没有关系,返回要么回滚,要么提交,不释放资源,断开连接
    with pool as cursor:      # 这是我们讲的一个池,那我们拿到这个池的时候应该怎么做呢?我们拿到这个池的话,下面要做的事情往往都是跟池相关的。比如说with conn 这应该是as谁呀? 是不是就是cursor了。这是你拿到之后究竟自己想怎么做,这实际上跟池本身没有关系,因为池它是用来做连接对象存储的,跟池本身没有关系的。那所以说我们要这么做,然后呢下面是不是用cursor点execute这个东西是吧。但是这地方你发现也有它不好的地方,你这么一写,好像他没有任何这个提示了,是吧。那很简单,是不是上面with conn as cursor:写个cursor然后什么什么东西,继续可以调用是吧。我们总是有一些变通的方式了。别忘了把这些东西最后删除掉就行了,然后cursor.execute('')这个里面是不是写一些语句就行了,写一些语句。当然我们建议你用参数化查询,写完了,这cursor.execute('')是不是查到结果了。ok是不是with conn as cursor:这个cursor也没人管呀。你这cursor还用不用啊?是不是,那这个时候是不是应该写成with conn as cursor: with cursor: cursor.execute('')这样了,那我发现我cursor.execute('')这后面确实没人用了,我是不是这么一关就行了,这conn连接有人关吗?要不要关啊?不能要啊,你不用的时候不能用close吧,你得给他还回去啊,是吧,把pool.return_conn(conn)这个挪下来就行了。嗯,这就是连接池的写法吧。      哎呦,我还自己给你还,我能不能有其他好办法呀,来添东西。def __enter__(self):和 def __exit__(self,exc_type,exc_val,exc_tb)唉,想一想这个东西怎么写。这是连接池了。你想怎么写,我们说是给别人提供好一点,对不对,那你应该怎么写,你看我们这块东西是不想以后改造的漂漂亮亮的是吧?那你应该怎么写?让我们来思考一下这个问题。


        with cursor: # 关闭游标连接连接释放资源
            sql = "select * from employees"
            cursor.execute(sql) #填写查询语句,
            print(cursor.fetchone()) # 打印一下sql语句结果集,返回一行

            sql = "SHOW PROCESSLIST" #观察连接(只是让你观察方便,看看现在到底有几个连接。如果你时多线程的话,看看到底有几个连接。这东西呢只是一个测试语句啊。
),账号权限小只能看到自己的连接,如果是root用户就能看到所有的连接,我们连接池里有5个如果看自己的就可以看到5个,有mysql提供的调一下就行了
            cursor.execute(sql)
            for x in cursor: # cursor可以迭代,遍历结果集
                print(x) # 打印遍历的cursor游标
            
    # pool.return_conn(conn) # 将项目放入队列,在还回去



# 调用
## 把这个pool传给线程就行了,因为他 with pool as cursor:这个with pool的时候是不是调的这个def __enter__(self):方法。调def __enter__(self):这个方法是不是if getattr(self.local,'conn',None) is None:就产生一个self.local。所以这with pool as cursor:下面是不是可以def foo():封装啊,是不是就可以了,那你要不行的话,就把这个pool传进来def foo(pool):就完了,就可以了嘛。            你想嘛,如果是一个线程跑的话,怎么跑啊?把这东西pool = ConnPool(5,'172.18.13.183','root','root','test')是不是全局都def foo(pool):塞进来。塞进来以后在with pool as cursor:这个线程中它调with就会跑到def __enter__(self):这来之后if getattr(self.local,'conn',None) is None:是不是只跟self.local当前线程相关的创建一个属性叫conn啊。就把pool扔进去就行了。            好,这是我们讲的一个简单的连接池的一个实现。但是呢真正的连接池要比这个要复杂的多的多。我们写的非常简单了。但是连接池是一个公共的资源,他一定是个公共资源。对于一个公共资源来讲的话。你就一定要考虑人家可能会使用多线程的方式啊,那有人说多进程呢,对不起这种东西一般来讲只在进程内使用。因为我们来说进程是一个单独的王国嘛,那你还要跨他,跨他你得想办法扔到一个共享的什么区域里面去。这样的话跨进程才能使用。所以我们一般写到这儿,那这东西就是一个多线程的要解决的问题。



foo(pool)

###  print(x)打印结果
# (68, 'root', '10.88.0.1:45892', 'mysql', 'Query', 0, 'starting', 'SHOW PROCESSLIST', 0.0) #这一个是不是执行了SHOW PROCESSLIST这事,你把它改成多线程的话,这个地方你会看到其实它里面实际上也不是Sleep睡觉了。现在这种方式他可能会睡着了,是吧,都睡着了。所以我们通过这种方式呢,就可以看到相应的链接,这SHOW PROCESSLIST是由mysql给你提供的。你直接调SHOW PROCESSLIST这个sql语句就行了啊。
# (69, 'root', '10.88.0.1:45894', 'mysql', 'Sleep', 0, '', None, 0.0)
# (70, 'root', '10.88.0.1:45896', 'mysql', 'Sleep', 0, '', None, 0.0)
# (71, 'root', '10.88.0.1:45898', 'mysql', 'Sleep', 0, '', None, 0.0)
# (72, 'root', '10.88.0.1:45900', 'mysql', 'Sleep', 0, '', None, 0.0)

# 使用ThreadLocal变量只能解决不同线程使用conn的问题,但是线程内必须同步方式使用。有其他一些使用方式的话,你比如说交替执行的话,还会带来一些其他的问题。你比如说像下面这种使用方式,他还会有一些问题的。

# 例如
with pool as cursor: #自动拿连接并归还,还自动提交或回滚
      with cursor:
            threading.Thread(target=xxx, args(cursor,)) # 你这个传那个cursor过去是没问题,但是cursor有可能被我with cursor:这边直接关了,关的话你threading.Thread(target=xxx, args(cursor,))这边又cursor,是不是就出问题了,因为是同一个对象嘛,对吧,你还限制人家怎么用吗?我不可以再创建线程吗?我是不是还可以创建线程。对吧,创建线程,我把这个cursor传过去是不可以啊,所以呢这各种使用方式,别因为你创建出这么一个线程池,别人用的时候,他就自以为就可以threading.Thread(target=xxx, args(cursor,))这么用了。所以有可能会出现一些各种各样的问题啊,使用上面方法可以跨线程传递cursor。但是呢这时候就父线程要是终结了这个cursor就会带来一些问题。所以这里面还是有一些问题的,有些方式,也是没有办法的。因为你很难出处理。除非你写一个非常复杂的结构来解决多线程去使用的问题。但是多线程安全不代表你跨进程就可以用啊,这是两码事了啊。

# 使用上面的代码可以跨线程传递cursor对象给另一个线程使用,但是,父线程可能已经执行结束,关闭了cursor ,里面使用关闭的游标会抛异常。

# 总结
# 这是一个连接池的简单实现,需要扩充很多功能。
# 可以考虑实现为线程池设定最小连接数和最大连接数,保证启动的时候,最少已经有了几个可用的连接。
# 使用队列的get, put方法实现也不是一个好方式,可以使用标记方式哪些连接已经被使用了。








通过Queue队列的线程安全性,来保证这些操作是不可被别人打断的



benet1
import pymysql # 导入数据库驱动
from queue import   Queue # 导入Q队列
import threading

class ConnPool:
    def __init__(self,size ,*args,**kwargs):
        self._size = size # 池子容量
        self._pool = Queue(size) # Q现在的size多少,现在的池子容量
        self.local = threading.local() # local是全局的,所有线程都可以用的属性了,不同线程使用这个属性中的一些东西,给他填个东西,这个只跟当前线程相关,不同的线程使用为每个线程开辟一个独立的空间进行数据存储
        for i in range(size): #初始化把Q的池子填满
            conn = pymysql.connect(*args,**kwargs) # mysql数据库连接
            self._pool.put(conn) #将项目放入队列

    
    def get_conn(self):
        conn = self._pool.get() # ''从队列中删除并返回一个项目。拿到最后一个拿不了,他就阻塞住blockking,
        self.locla.conn = conn # 赋值语句,返回的项目丢到local线程全局变量中
        return conn # 返回的项目,返回到函数调用处
        
 
    def return_conn(self , conn:pymysql.connections.Connection):
        if isinstance(conn,pymysql.connections.Connection): # 判断还回来的对象是否是conn已知的类型
            self._pool.put(conn) # self._pool.put()'''将项目放入队列。
            self.local.conn = None

    @property # 把方法变成属性调用
    def size(self):
        return self._pool.qsize()  # self._pool.qsize()'''返回队列的大概大小(不可靠!)。

    def __enter__(self):
        return self.get_conn()

    def __exit__(self,exc_type,exc_val,exc_tb):
        self.return_conn()


# connect('192.168.139.187','root','root','test')

pool = ConnPool(5,'192.168.139.187','root','root','test') # 实例化类,对seiz大小,连接传参

conn = pool.get_conn() # 拿一个
print(conn) # 拿到的连接,打印看看

print(pool.size) # 打印池大小


with conn as cursor: # 对拿到的连接做连接,这一步跟池没有关系,返回要么回滚,要么提交,不释放资源,断开连接
    with cursor:# 关闭游标连接连接释放资源
        cursor.execute('') #填写查询语句,
        
pool.return_conn(conn) # 将项目放入队列,在还回去

元编程

  元编程用在什么地方呢?写框架应最多了。写框架用的是非常多的,框架里面就适合这种思想。因为框架呢往往想什么东西都适应它,适应它之后呢,那你就根据很多东西要判断。然后呢用户写一些简单的东西,然后你把它生成什么样的东西。所以元编程经常要干这种事儿。那所以说元编程在框架中用的是非常多的。那离开框架呢,很少用到,但元编程呢,今天因为我们要实现一个类似于SQLAlchemy这样一个东西,一个orm的一个框架。我们要实现他,为什么呢。因为我们直接讲SQLAlchemy这个这个库吧,用也就用了。但是你用不好,你根本不知道他后面再替你做什么事情,你就是生硬的去记住我们说第一步干什么,第二步干什么。所以说我们要给大家写一个类似于它的一个orm框架出来。这样的话大家就能够理解他究竟在背后做什么事情了,就是学了这个框架之后呢,框架内部还有很多知识要学。SQLAlchemy这个这个库呢,其实学起来也不简单啊,非常烦。但是呢我们尽量把它总结下来,就把最常用的东西总结下来。好,那么在这之前的话,为了做这个铺垫呢,我们要学一下元编程。什么是元编程呢,这个概念其实很早就已经有了。元编程的概念,它来自LISP和smaltalk,就是非常非常老的两门语言了。像我们面向对象语言都是基本上从smaltalk这边脱胎出来的。那我们说什么是元编程呢?我们现在是像写代码class是不是就开始定义了。可以认为这东西叫直接编码。那这样东西我能不能通过编码来实现啊?你比如说我现在写一个class像这样东西来,然后定义一个类。现在我能不能通过编码来实现一个类呢?所以说这个东西我们现在能不能用代码,来生成我们未来想用的程序呢。也就是用代码来写代码。这种东西都称为元,那用代码来写代码,就是用代码来编写程序。那这个就是用代码来编写程序的,就是用代码来编码了。那这个东西叫元编程,也就是说还是在写程序,但是我写这个程序呢是生成未来你想要的那段程序。这就是元编程,想想我们动态导入那个模块的时候。那个模块本来我们就写import什么东西,但是因为我们想灵活想灵活,我就想在运行时我能不能想办法把它导进来,我写几条代码把他导进来,但是我导谁你甭管。送参数,我就变得很灵活,你给我个参数,然后我就把它导进来,参数是谁,我就想办法找谁去来导进来,这就是一种很灵活的编码的方式来实现的。用代码来生成代码,这就是元编程。还有一种举例,什么是元呢?比如说了我们要生产一个茶杯,生产茶杯的,这就是你写代码,你产出的东西,你代码产出的东西就是茶杯了。但是生产茶杯的机器谁生产?也就是生产机器的机器,那就叫元机器。所以说就是这么个意思。所以我们说生成代码程序称为元程序,编写这种程序呢,就称为元编程,元编程这个概念要这么去理解的话,就应该没什么问题了。那python语言呢,它能够通过反射来实现元编程。那有反射这种机制的,往往都称这种语言叫元语言。就是他能实现这种元编程的语言,那不管怎么说,元编程指的就是这样一个意思。那我们重新来认识一下type类,type这个东西呢很有用用。


  元编程概念来自LISP和smaltalk.
  我们写程序是直接写代码,是否能够用代码来生成未来我们需要的代码吗?这就是元编程

  用来生成代码的程序称为元程序metaprogram ,编写这种程序就称为元编程metaprogramming
  Python语言能够通过反射实现元编程。
# 我们点type看一下,我们前面说过,type他是一个内建的函数,对吧。但是呢本质上我们点开一看class type(object):,好家伙,这家伙是个类呀,对吧,所以在python中大小写已经不能区分了,你还是要点开看他如何定义的。所以呢,他是class定义,它是个类,他是类它是直接继承自object。我不建议大家现在直接理清楚,type和object之间到底有什么关系。这两个关系很复杂。这两个关系非常复杂,这俩之间是纠缠在一起的。所以呢我们知道对于这些东西来讲,我们说都是从object开始继承的。但是呢。我们发现我们前面所用的所有类,我们去看他是从谁继承下来。我们发现他好像是从type上下来的,我们说他是所有的class定义的类。他本身是type的子类一样,这个意思,是吧。因为我们看他,这个是他的类型,我们讲它是类类型,对吧,我们指的这个类谁啊,就是type,他就是type类型的。学完元编程,你就知道为什么了,所以呢type和object这是两条线啊,type(object)这俩关系说不清楚啊,这俩关系绕在一起的。还是你要理还是可以理清楚的,但是要看你从什么角度来看。这个地方实现的还是非常好玩的东西,那我们来看他写了三行。
type (object_or_name, bases, dict)      # object_or_name说type这后面可以写一个object对象或者一个name名称,一切皆对象。type 后面是一个bases实例的话,它显示是他自己是谁的实例,也就是他是谁的类。然后呢如果是个类名写在这儿,他就会显示是type。对吧,所以说要看你写什么对象了,所以他返回的是这个对象的类型,实例返回,实例的类型。那对于类呢,返回他是谁的对象,他就是type对象。至少目前为止我们是怎么发现了的,我们发现是这么一个结果。那也就是说一般情况下。只要你写了个class,后面那个名称,这个名称它实际上就是属于type类的实例。这个意思。
type (object) -> the object s type
type (name, bases, dict) -> a new type     # 他说你只要这么做,我就给你返回一个new type新类型。不是一个新实例,他没这么说,但是我们说一切皆对象,对吧,你返回的肯定也算一个新的实例嘛,对吧。但是呢我这个地方,更确切的想给你表达的是。他是一个新的类型出来了。所以这个地方不太一样,不然他后面也不会写一个叫tape的东西。

def __init__ (cls, what, bases=Ndne, dict=None): # known special case of type.__init__ # 就在这儿,当然这个源码不在这儿,那我们来看一下,大家看这cls一进来那你就明白了这第一个,返回的竟然是一个很特别的,我们以前看__init__ 返回回来的是什么呀?应该是个self,写的更合适嘛,他难道真写错了吗?这个地方很特殊,type类的这个__init__很特殊。他第一个传进来的参数并不是self了。你想嘛,这cls是一个class 定义的他的什么?,他的那个type,这个type的话,如果是self的话,其实这地方并不是你传统认为的那个self了。所以这个地方传来的是一个class啊,这个class呢我们不多说,我们看他后面这三个参数。这三个参数对我们来说呢才是至关重要的。what指的是什么意思呢?这个就是你那个所谓的类对象的那个类名字到底是谁。然后bases就是你现在class定义的一个类名的,那个后面写的那个继承列表,然后dict呢,类对象,这个类本身是一个对象嘛。就这个类来讲的话,他的dict是什么?这其实就是类属性字典嘛,这三样东西指的都是那个class 定义的那个类。所以呢class定义这个类呢,它名字叫什么?what。他的继承列表是什么?bases。他的类属性字典呢?dict。它指的是这个东西,因为他们都是type的实例。这是type这块我们要注意的东西。所以呢通过看type 的__init__ 这样一个函数,我们发现他跟其他的不太一样啊,我们记住就可以了。

      """
      type (object_or_name, bases, dict) 
      type (object) -> the object's type
      type (name, bases, dict) -> a new type 
      """
      pass

# type (object) -> the object s type ,返回对象的类型,例如type(10)
# type(name, bases, dict) -> a new type返回一个新的类型

#       好,      那我们来看,我用你的type,我用你这种定义方式我能干什么呢?看下面这个例子。我们想通过这个例子呢,看一看你这里面究竟放的是什么。我们看它定义里面就是说,你给我type (name, bases, dict) -> a new type这么写name, bases, dict也就是说cls这个东西你甭管。我想办法给你解决,我们来试一下。好,我们现在准备用type这东西了是吧。那我们随便给他起个名字XClass等于type

XClass = type('x', (object,),{})       # 那我们随便给他起个名字XClass等于type,然后我们后面第一个写什么呢?给他起个名字,这名字随便你,比如说你起个名字叫x 就随便你。然后后面这个object,他要求是个元组。一般你可以这么(object,)写,你也可以给他一个空元组,这都没有问题。想怎么写就怎么写,这是他集成列表。然后呢后面给一个字典,你比如说给你个{}空字典,对吧。这是一个new tpye
print(XClass)       # 那对于我们来讲的话,这个x 究竟是什么呢?
print(type(XClass))       # 然后呢我们现在给他一个,我再看看你的类型总可以吧。
print(XClass.__name__) 
print(XClass.__dict__)       # 然后呢,我把你这个类型的字典,我给你拿来总可以的吧。
print(XClass.mro())       # 这回我再瞅一眼你的mro总可以的吧
print(XClass().__dict__)        # 然后呢,我想办法把你能不能实例化一把,我看看你的字典。

# 运行结果
<class '__main__.x'>       # 你看class '__main__.x'这个类是不是有个x名字,这名字type('x', (object,),{})是不是定义是x他,只不过这是一个new type对吧,
<class 'type'>       # 我们看type(XClass)他是一个什么类型,他是一个<class 'type'>类类型吧,所以他是个一个type的对吧。这个对象它是一个type的一个实例。

{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'x' objects>, '__weakref__': <attribute '__weakref__' of 'x' objects>, '__doc__': None}      # 他的__dict__': <attribute '__dict__' of 'x' objects>字典,他的字典 <attribute '__dict__' of 'x' objects>是这东西,然后呢,他是__module__': '__main__这个模块这没问题啊,我正在运行,就__main__这个模块嘛。然后他是__weakref__': <attribute '__weakref__' of 'x' objects>一个弱引用不理他,然后__doc__': None文档是不是没有,然后__dict__': <attribute '__dict__' of 'x' objects>这个是不是也没有,这个地方的话,实际上这是一个所谓的x字典的对象。那我们想办法写点东西吧。      XClass = type('x', (object,),{'a':100})在走一遍,结果{'a': 100, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'x' objects>, '__weakref__': <attribute '__weakref__' of 'x' objects>, '__doc__': None},这个是不是'a':100有了,是不是在这儿,对吧。所以字典里面是有东西的,他会把它放进来啊,他会把这个{'a':100}这些值给它放到属性里面去。      但是呢{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'x' objects>, '__weakref__': <attribute '__weakref__' of 'x' objects>, '__doc__': None}这些东西呢是因为他为了保证他整个解释器这边运行对象正常,他替你塞进去的东西。然后我看看mro。
[<class '__main__.x'>, <class 'object'>]       # 看看mro。是不是就是按照我们所给的这个是不是继承啊。所以这一块呢,就看你想怎么写了。这样的话我们就拿到了一个新的type出来了。
{}       # 新的type出来以后,依然可以把它来XClass().__dict__做这样的东西。你发现这东西XClass实际上拿过来用法跟我们前面那个用法,其实差不多。对吧,只不过有人说,那你XClass和x这两个要不一样多难受啊,那怎么办啊,你自己给他改成一样就完了嘛。      XClass = type('x', (object,),{}),XClass这是一个对象啊,这x是他起了一个名字,这俩可以不一样。我们用这种方式可以发现你看XClass()这用法跟我们前面比如说class A。然后把A()加个括号,实例化不一样嘛,没什么差异。所以呢通过这种标识符。我们依然可以来这么做,你既然可以这么做,我就不用class 这种方式来写了。      所以呢用这种方式呢,我们就可以动态的去创建一个new的type。然后我们这个new type 依然可以拿来做各种各样的编程。你看type('x', (object,),{'a':100})你可以不但地动态生成'x', (object,),你还可以动态生成{'a':100}类属性,这是类属性啊,不是实例的,实例是空的啊。动态生成类属性,你可以做很多很多事情。所以呢我们通过这种方式,就可以解决这样一些问题。

# 我们知道对于一个类定义的时候,我们一般怎么定义啊。来对比啊。
class A: # class 一个A这么定义
    def __init__(self):# 然后我们def __init__(self)是吧,这么过来
        print('init')# 过来之后呢,print一下,我们写一个init。这是你这么定义的。

    def show(self): # 或者说在定义一个什么函数比如def show(self)
        print('show')# 然后print打印一个show。这一般你都这么写的是吧,那我现在对他XClass = type('x',(object,),{'a':100})应该怎么做啊?不就是捆到{'a':100}字典里面去吗。对吧。你看我们这个词典是放在哪儿的呀,这{'a': 100, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'x' objects>, '__weakref__': <attribute '__weakref__' of 'x' objects>, '__doc__': None}是不是就是相当于是类属性的字典了,对不对,那我们想办法给他构建一个不就行了。

def __init__ (self):# def __init__(self)这名字,字典你往这一放,你自己写一个参数呗,对吧,这是你自己是不是在构造这个东西啊。
    print('x.init')# 那你这里这个地方写一个print打印x.init就是自己定义的init,不就完了嘛。      那XClass = type('x',(object,),{'a':100})这个地方应该怎么办呢?
XClass = type('x',(object,),{'a':100})       # 我们就想办法是不是要XClass = type('x',(object,),{'a':100,'__init__'})这么写啊。这是一个属性啊,'__init__'这是他方法名,所谓的方法名嘛。但是这也是个类属性啊,然后怎么办呢?把这个__init__是不是给他XClass = type('x',(object,),{'a':100,'__init__':__init__})写进来就行了。      那我们来试试看能不能用。
print(XClass().__dict__)

# 打印结果
x.init       # 这是XClass()实例化的结果。这XClass()是不是先待实例化,这先实例化的时候是不是要调def __init__ (self):      print('x.init')它,所谓这个函数啊,这个def __init__ (self):函数说你得给我一个self进来了,是吧,我没用,我只是说给你print打印一下,这self当然可以进来,这self不就是XClass()它实例化之后的那个对象嘛,对吧,我们通过这种方式是不是'__init__':__init__就捆绑进来了。这样的话就可以完成我们,但是参数你必须满足要求,def __init__ (self)人家得有一个参数啊,这参数是不是传进来,他自己这个实例本身啊,所以这个参数你要给,我们就通过这样的方式就可以构造出动态的,用代码来写出一个类,而这个类是未来我们马上要用的东西,就后来我们马上要用东西,这就是用代码来生成,class A:      def __init__(self):      print('init')      def show(self):      print('show')这种代码的。这就是元编程。
{}      # 这是打印的这个实例的字典

# 所以通过这种方式,所以说你这个{'a':100,'__init__':__init__}构建词典对你来说就没什么问题了。你反正知道这XClass是一个新类型。这个新类型的话就跟你class A:      def __init__(self):      print('init')      def show(self):      print('show')这种创建方式是一样的。这是同样东西,这两个创建方式是一样的。你发现只要有XClass这个名称,你下面是使用都一模一样,你看见没?      只不过这class A:默认的A这个名称,默认这个名称跟类名是一样的,我们type('x',(object,),{'a':100})故意写'x'了个不一样的。      所以在用class 的时候,他就没有机会让你重新改名,这type('x',(object,),{'a':100})块'x'只是个名称,这只是个名称给人看的名称,这XClass才是我真正想用的对象。拿这个XClass对象来实例化,对吧,你能拿一个字符串来示例吗,肯定不行,拿他来实体化谁啊,对吧,不对,字符串不是这个意思,这XClass才是我们真正想要的那个new type。这东西才能拿来实例化,我们通过这种方式就做到了,class A:      def __init__(self):      print('init')      def show(self):      print('show')这种代码能做到的事情,有了'__init__':__init__}这种动态加入,def __init__ (self):      print('x.init')这种方法的东西,你就可以实现,任何你想要的东西了。这就是元编程。      类是继承type,相当于从type上过来的,结果呢,我们的实例发现这边这条线又走的是object,但是呢type又继承至object。很奇怪是吧。那先不管那么多,我们就知道,反正得这么写,是吧。学会了,然后我们慢慢再去理解他,通过这样一些方式,大家可以观察一下里面究竟是什么样子。所以通过这种方式呢,我们就可以观察出来,观察出来原来这个new type 有这样的用处啊。所以呢我们就可以借助这样一个type。      而且我们前面没用过的,这样三个参数的方式来构造出一个新的类,这些东西啊,那这就是元编程。      那我们现在想换一种写法。

上例并不稀奇,我们用它创建更加复杂的类

# class XMeta(type):      #现在呢,我们现在比如说我们现在叫XMeta那这回呢我从type他上面继承下来。
    pass

print(XMeta)      # 打印一下
print(type(XMeta))
print(XMeta.__dict__)
print(XMeta.mro())
print(XMeta().__dict__)

# 打印结果
<class '__main__.XMeta'>       # 他说你要这么定义,这是没问题的。
<class 'type'>      # 他也是从type上来的,对吧。
{'__module__': '__main__', '__doc__': None}      # 然后呢,这块儿你要打印字典也没什么问题。
Traceback (most recent call last):
  File "/root/jovyan/work/test.py", line 26, in <module>
    print(XMeta.mro())
TypeError: descriptor 'mro' of 'type' object needs an argument      #  他说mro 这个type对象的话还需要一个参数,唉,上面可以下不可以是吧。看来是有一点点差别,这也是个什么东西呢?哎,这个东西可能跟我们理解的,刚才这种写法是有一些差异了。   

class XMeta(type):
    pass

print(XMeta)
print(type(XMeta))
print(XMeta.__dict__)
# print(XMeta.mro())      # 注释掉不打印mro,那我们看他能不能实例化呢?报错,也不能实例化了,打印结果,提示他需要1个到3个参数,刚我们还学过1到3个,对不对,type('x',(object,),{'a':100,'__init__':__init__})是不是可以这么做。他说现在也需要1到3个参数,那看来生成这东西表面上看他是个type类型,但是实际上他未必是这个意思,是吧,。所以说这么一做意思有变化了。这样他生成的XMeta这个类就不是一个我们上面这个class A这种普通类型了。      他不是一个普通类了,他本身就是一个,你看这名字,我起的XMeta名字他本身就是一个跟type相等的类。      虽然他们来自于type,只不过是给你一个模板,让你用用,你自己写个type肯定写不出来,怎么办呢?,让你继承一把,但这个继承导致了一个结果。这个类就不是普通类了,这里类,我们称为元类。
print(XMeta().__dict__)   


# 我们来看,元类里面我们可以实现它其中的一个方法,就是new方法。这个new方法呢,我们把它敲了。敲了之后看看他究竟在做什么。
class XMeta(type):
    def __new__(cls,*args,**kwargs):      #  def 我们来写写new方法,写到这,或者def __new__(cls,whta,bases,dict):如果是这样的话,后面要改一下print(2, whta)      print(3, bases)      print(3, dict),dict虽然说我们这个名字冲突,但是需要知道这时局部变量,所以问题不大,这要保证是一致。因为他全部把这些参数都继续向后传,自然有人接管好。后面需要return参数也需要保持一直return 
super().__new__(cls,whta,bases,dict),这种方式直接把他们拆掉了,用起来也很方便,因为什么呢?原来人家送的就是3个参数,你愣是*args把人家写到一起的,是吧。因为可变参数是不是收集到一起了吗,所以我们只要展开就行了,
        print(1, cls)      #  我们看看他这些东西都有什么用,我们把这些东西都给他打印一下,这个动作,这个东西的话,他在没有实例化的时候是不这么做的,对吧。所以呢我们想想办法怎么办呢?我们这么来做class A(XMeta):。
        print(2, args)
        print(3, kwargs)

class A(XMeta): # class我们这回写个A,这个A呢,来自于XMeta。继承至XMeta
    pass

A() # 实例化A,现在我们除了类里面的打印,外面就没有打印语句了。我们知道当A所谓的实例化过程,他应该是自己先要把自己给new出来,然后再去调他自己的init对吧。但是我们发现这个时候他应该用自己的new,但自己没定义是吧。然后找到谁那去了?找到他自己属于哪个类型,是不是找这个XMeta类型里面看有没有能把自己构造出来东西。那么来看print(1, cls)      print(2, args)      print(3, kwargs)这三样东西,这个时候你看 , A(XMeta)我从他XMeta是不是继承下来,对吧,是不是也可以调这个东西啊,那我们再换一种写法啊。

# 打印结果
1 <class '__main__.A'>
2 ()
3 {}

# 换一种写法,这么写
class A(metaclass=XMeta):       # 要注意了,如果您要class 呢,你要使用元类呢,就相当于你要指定元类,指定元类必须用这个metaclass等于送这样一个关键字传参的方式,送一个参数进去。你如果用继承的方式就是另一码意思了。      我们写一个mataclase等于XMeta,我们这么写,看他会怎么样。我metaclass=XMeta这么一指定,相当于他原来class A:      def __init__(self):      print('init')      def show(self):      print('show')这种东西它来自于type,它是type的类对象,是吧。      现在呢,我强行的给你更改了你以后就是XMeta的了。那怎真的是这样的吗。来试一下。      什么是 metaclass,很多书都会翻译成 元类,仅从字面理解, meta 的确是元,本源,翻译没毛病。但理解时,应该把元理解为描述数据的超越数据,事实上,metaclass 的 meta 起源于希腊词汇 meta,包含两种意思:      “Beyond”,例如技术词汇 metadata,意思是描述数据的超越数据。      “Change”,例如技术词汇 metamorphosis,意思是改变的形态。      因此可以理解为 metaclass 为描述类的超类,同时可以改变子类的形态。你可能会问了,这和元数据的定义差不多么,这种特性在编程中有什么用?      用处非常大。在没有 metaclass 的情况下,子类继承父类,父类是无法对子类执行操作的,但有了 metaclass,就可以对子类进行操作,就像装饰器那样可以动态定制和修改被装饰的类,metaclass 可以动态的定制或修改继承它的子类。
    pass
A

# 打印结果
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A'})
3 {}


# 现在呢,我强行的给你更改了你以后就是XMeta的了。那怎真的是这样的吗。来试一下。
class A(metaclass=XMeta):
    pass
print(type(A))

# 打印结果
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A'})
3 {}
<class 'NoneType'>       # NoneType你连type都不是了,这怎么实现的呢?大家想一想这个XMeta地方它调用了谁啊,是不是打印的1,2,3啊。      既然是个NoneType,那就说明,return ?少东西了吧。因为他返回是return none嘛。也就是说new这一块儿通过我们的观察,new这块儿能return一个什么都没有东西嘛,就是none嘛,所以这块儿应该怎么做呢?一般来讲的话,你你简单讲应该是super 点__init__这东西,对吧,那传谁呢?cls,*args,**kwargs有什么传什么呗。来,我们打印来看看啊。还是NoneType怎么回事呢?对吧,那就肯定跟调用方法有关系了,是吧。所以呢return super().__init__(cls,*args,**kwargs)这一块儿应该调什么?这是我们要去解决的问题。但是不管怎么说呢,这return super().__init__(cls,*args,**kwargs)一句话上看来有return的话,就有一些,比如说你大胆的去尝试嘛,你比如说我给你return个1,我凑凑看,<class 'int'>是吧,对不对,那就说明这个return 是不是有用啊,对吧,就你测试了嘛,你就发现这个return有用。如果说这return 1一块儿你作为,就相当于什么,class XMeta(type):这XMeta是那个type啊。这就相当于是个type,你是不是在定义class A:他啊,对吧,假设你在定义class A:      def __init__(self):      print('init')      def show(self):      print('show')它,他是不是在type的 def __new__(cls,*args,**kwargs):      print(1, cls)      print(2, args)      print(3, kwargs)      return 1这样一些方法,对吧。如果type这个方法他没有返回值那不就不出问题了嘛,      所以这个一定要返回值,这个没有返回值就会出问题了,一定会出问题的啊。所以我们想办法要解决这样一个问题。但是我们现在是class A(metaclass=XMeta):这么来解决,(metaclass=XMeta)这么来写的话,就不是继承啊。这么来写就是说替换你的那个元类,原来大家都是定义的,用class 定义的话,都是用的type,这回呢,换掉,用我自己class XMeta(type):     def __new__(cls,*args,**kwargs):      print(1, cls)      print(2, args)      print(3, kwargs)      return 1 刚做的这个东西,你发现它实际上是,就这么一写的话啊。

# 你发现它实际上是,就这么一写的话啊。运行一下我们看,我干什么了嘛,大家看我这代码里面干什么嘛,我有主动的去调用A什么东西吗?就是你在定义的时候,你从上往下扫的过程中,这个类是不是已经他说,这个class的声明方式就是,是不是要创建啊。class 声明,他就要把A这个类要创建出来。也就是说他原来是type的这个类对象,是不是要创建出来。现在呢metaclass=XMeta你说换了这回你的那个元类的模板换了,原来是type,现在换成你刚才自己写这个XMeta,他为了把A这个类对象创建出来,他没办法,他必须是不是要调用XMeta这下面的这个new啊。他要创建它。所以呢这就是我们要出现的一些问题啊。所以呢我们下面要用这样的方式来解决他啊,这样的方式,一样对你来说,他一样是他super对吧,然后呢我们把这个new写这。其他都不管,cls,*args,**kwargs原样照抄就行了。
class XMeta(type):
    def __new__(cls,*args,**kwargs):
        print(1, cls)
        print(2, args)
        print(3, kwargs)
        return super().__new__(cls,*args,**kwargs)
class A(metaclass=XMeta):
    pass

print(A)      #打印一下

# 打印结果
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A'})
3 {}
<class '__main__.A'>      # 就拿回来了,所以说我们通过这样一种方式。就可以把一个类型换掉。但是我们发现A(XMeta)这样的话就是继承了。这继承跟你换元类是两码事儿。,那如果是这样子的话,没写完是吧,再来。

# 那如果是这样子的话,没写完是吧,再来。
class XMeta(type):
    def __new__(cls,*args,**kwargs):
        print(1, cls)
        print(2, args)
        print(3, kwargs)
        return super().__new__(cls,*args,**kwargs)

class A(metaclass=XMeta):
    pass

class B(A):pass       # 可以吧。我们看继承了,我们跟大家讲new 方法,你别乱用是吧,你不懂他干什么就别乱用。好,那我们先来看看这种方式的话。他究竟是什么意思,他跟XMeta之间有没有关系,如果是这么一种继承的话,那我们来甚至我们可以要看一下啊,最后要看一下。b的类型。
# print(type(A),type(B))       # 最后要看一下。b的类型。我现在什么都不打印,我看看谁会给我触发

#  运行结果,1,2,3,1,2,3看第二
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A'})      # 第一个参数我是谁,是吧,我的类名叫什么?看看第一个cls送的是你是谁就是谁。第二个是谁,第二个是不是就是args, 那个args送的是什么进来,args送了一个()元组进来。这个元组里面包含了我是谁是吧?我的继承列表什么,这是不是咱们看的这cls,*args,**kwargs三个参数是不是合成一个元组放在args里面去了。第一个cls是谁,我是谁<class '__main__.XMeta'>我是谁嘛,对吧,我们的元类是谁我是谁。然后是不是把XMeta是不是都打印了一下,所以cls这个参数其实我们就不太关心了。因为你们如果按照这一方式是不是一条线下来,你们的元类是不是都应该一样。你从A上继承,但是你如果写成class B 这个跟人家这个XMeta还有关系吗?一点儿关系都没有了,这又跑到type上去了,是吧,对想象。所以呢我们通过class B(A):这种方式来看,通过这种方式的话,即使你是继承,也是从XMeta这儿一脉相承下来的。一脉相承下来,他就会到def __new__(cls,*args,**kwargs):这儿来。      那大家看第二个参数,现在我是谁,是不是就这个对我们来说太重要了,这个是不是就说人家写那个wallet 是吧,后面是不是写了一个所谓的那个原祖就是basics 嘛,是吧?
3 {}
1 <class '__main__.XMeta'>
2 ('B', (<class '__main__.A'>,), {'__module__': '__main__', '__qualname__': 'B'})      # 那大家看第二个参数,现在我是谁'B',是不是就print(2, args)这个对我们来说太重要了,这个是不是就说人家写了个what是吧,后面是不是写了一个所谓的那个元组就是bases嘛,记得继承列表,最后是不是这个dict啊。print(2, args) # what,bases,dict,就这么个东西啊。好,我们看原来一句代码都可以不写啊,这原来还在后面发生这些事情。所以你学完元编程之后,你会发现原来我们整个一个定义不是死的。只要这个模块一加载,当解释器从上往下去给你执行的时候。他就会做很多动作,只不过你认为的开始还没有开始。实际上已经都开始了。就是一个class A 它其实都已经有很多代码已经运行过了,对吧,class B 这东西我们还觉得哎,代码还没运行呢。代码早就运行了,对吧,自打从import开始,假如你如果有import 的开始,自然有import,它就开始已经开始工作了。我们的import 你要把它导进来,说是导那个模块是不是创建模块对象。然后模块里面是不是依次从上往下执行一遍啊,我们在讲执行过程是不是像cs这样的模块,早就给你导入了,对吧。其实有很多工作提前都已经完成了啊,提前都已经完成了。就算你在定义class,它背后实际上也有一堆一堆代码全部执行过了。所以这一块呢是一样的。那我们最后print(type(A),type(B))把它放开来看一下。我要看看你们俩的类型究竟是什么,其实<class '__main__.XMeta'>这句话已经告诉你了,对吧,都是一家人嘛,对吧,才进这个门嘛,对吧,
3 {}

print(type(A),type(B ))
# 打印结果
<class '__main__.XMeta'> <class '__main__.XMeta'>       # 跟type是不是就没什么关系啦,也不是说真没关系,他们还是class XMeta(type):来自于type,只不过呢,被他自己的元类def __new__(cls,*args,**kwargs):是不是我们逮着了都在这儿打印打印了是吧,都一样的。好。其实在这块呢你还可以打印一个,我们可以来看一下,看看它的super究竟谁呀。

# 看看它的super究竟谁
class XMeta(type):
    def __new__(cls,*args,**kwargs):
        print(1, cls)
        print(2, args)
        print(3, kwargs)
        print(4, super())
        print(5, type(cls))
        return super().__new__(cls,*args,**kwargs)

class A(metaclass=XMeta):
     id = 123      # 这东西是不是就跟我们那个类属性嘛,这是id是类属性吧。比如说我写个id等于123。这个时候我们来看一下啊。
   
    # def __init__(self):     # init这东西是不是你要定义的话,init本身它是一个类属性,对吧。在类型字典里面可以能看到的。肯定是能看到的。但是这个函数要调用是不是得A实例化之后才行啊,对吧。那B这边呢。

    #     print('~~A,init~~')

class B(A):pass

    # def __init__(self):      # 那B这边呢,也是一样,这东西它是在B的,那个类似字典里面,init是可以看到的。但是呢这个函数的调用它是必须在实例化之后才可以用的。我们能不能用元类来做一些事情呢。
    #     print('~~B.init~~')

print(type(A),type(B ))


# 打印结果
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A', 'id': 123})      # id这个类属性是不是已经在这已经可以找到了是吧,因为是类属性嘛,我们说print(2, args) # what,bases,dict后面这里面这个dict就跟类属性相关嘛,对吧,所以这发现跟type函数那种调用方式其实差不多。
3 {}
4 <super: <class 'XMeta'>, <XMeta object>>       # 看不出来是吧
5 <class 'type'>      # 看这,所以说你定义再多的元类。最后呢,对于python来讲,他的设计不可能很复杂。只不过你由他生成了一个新的元类而已,所以我们通过这样一个方式,好,我们通过这样一种方式就可以解决我们现在所面临的问题。我们通过这样一个元类的改造,就可以把自己定义的类,自己定义的类他自己的派生类。我们就可以把它们串成一条线,只要他们自己这个类在被构造的过程中,我们生成这个类实例嘛。在构造的过程中,他就要使用这样一个元类,使用这样子元类的时候,他们就会到元类这个里面的new方法里面来。因为他们都没有重新实现new方法,对吧,到new有方法里面来我们就可以在new 方法里面拿到我们想要的数据。其实最重要数据就是print(2, args) # what,bases,dict第二个。这是我们想要的,你看我们第三行打印是不是都是空的呀,用不上。
1 <class '__main__.XMeta'>
2 ('B', (<class '__main__.A'>,), {'__module__': '__main__', '__qualname__': 'B'})
3 {}
4 <super: <class 'XMeta'>, <XMeta object>>
5 <class 'type'>
<class '__main__.XMeta'> <class '__main__.XMeta'>

# 我们能不能用元类来做一些事情呢。
class XMeta(type):
    def __new__(cls,*args,**kwargs):
        print(1, cls)
        print(2, args)
        print(3, kwargs)
        return super().__new__(cls,*args,**kwargs)

class A(metaclass=XMeta):
    id = 123
    # def __init__(self):
    #     print('~~A,init~~')

class B(A):pass
    # def __init__(self):
    #      print('~~B.init~~')

print(type(A),type(B ))

C = XMeta('tom',(),{})      # 我们能不能用元类来做一些事情呢,对不对,既然我们说你是个元类,那我们现在来定义一个所谓的c我们现在用XMeta,XMeta的话后面是不是还是我们说的三个,第一个应该是什么?第二个应该是什么对吧。比如说C对吧,你随便起个名,比如说叫tom 对吧,我们说这是不是跟那个type很像,你就假设type('tom')这地方写的是type。然后后面是不是有一个bases,你写个空元组看行不行啊,我给个空字典看行不行啊。

# 运行结果
1 <class '__main__.XMeta'>
2 ('A', (), {'__module__': '__main__', '__qualname__': 'A', 'id': 123})
3 {}
1 <class '__main__.XMeta'>
2 ('B', (<class '__main__.A'>,), {'__module__': '__main__', '__qualname__': 'B'})
3 {}
<class '__main__.XMeta'> <class '__main__.XMeta'>
1 <class '__main__.XMeta'>
2 ('tom', (), {})      # 这是不是刚写出来的东西,那也就发现通过继承的方式,通过这种class A(metaclass=XMeta):方式修改元类的方式。通过class B(A):继承的方式,这些我们很好理解对吧。那这种C = XMeta('tom',(),{})方式呢,这种方式不是你指定不使用元类了,这不是也跟type一样了。所以这几种方式都会到元类这里边来用他的new方法来构建这个类的对象,类对象。就是用你这个元类来构建,我这个类,是这个意思啊,因为这块实在是跟这个类的实例啊,这块不好说,对吧。我们就说用这个元类来构建你这个类。这么说是比较准确的。好,那我们发现这几种方式是不是都可以来做这样事情啊。哎有这样的东西究竟有什么用呢?      你想想,对于一个框架来讲,他可能还真就有用了。对我们平常来讲这样写太累了。
3 {}

总结

  元类是制造类的工厂,是生成类的类。      (制造杯子的那个机器待有个机器造它。待有个厂子来生产制造杯子的机器,这就是元类或者你认为叫元类工厂,就是专门来做这些事儿的。这个元类是制造类的机器或者说工厂,他就是用来生成类的,所以他就叫生成类的类。type就是)

  定义一个元类,需要使用type(name, bases, dict),也可以继承type.      (定义一个元类,需要使用type来做,也可以通过继承type来做这两种方式。都可以)

  构造好元类,就可以在类定义是使用关键字参数metaclass指定元类,可以使用最原始的metatype(name, bases, dict)的方式构造一个类.      (构造好元类,就可以在类定义时使用关键字metaclass。也就是说我们以后真正想用的方便不是说用三参数的方式。因为用三参数的方式,我们发现前面在构造的时候是不是写写太累了。你XClass = type('x',(object,),{'a':100,'__init__':__init__})像这种方式是吧,对吧,各种不方便,那怎么办呢?我们说了你只需要class XMeta(type):定义一个元类出来,对吧,然后呢你只需要在class A(metaclass=XMeta):这儿给我做些事。那有人说那构造元类的时候,我要不要def __new__(cls,*args,**kwargs):这东西。你可以不要,没有问题,我们在这儿其实是后来想在这儿做拦截的是吧,我们在这想做一些事情。所以说告诉大家def __new__(cls,*args,**kwargs):这个方法可以拦一下,对吧,因为def __new__(cls,*args,**kwargs):这个方法,关你有没有底下这些类。有没有实例化,这个def __new__(cls,*args,**kwargs):方法是不是都执行。class A 它只要执行到这句的时候,他就要构建一个type的实例出来,是吧。这个时候怎么办?class A(metaclass=XMeta):修改一下,对不对,metaclass修改一下。那然后下面的继承一脉相承,都会认为自己都是从XMeta出来的。      当然我们还可以用最原始的方法,metatype,跟type的使用方式是一样的。太原始了,用起来不方便)

  元类的__new__()方法中,可以获取元类信息、当前类、基类、类变量信息。      (元类中的这个new方法,在这个里面我们可以获得到元类自身的信息。元类叫什么,元类类class送进来的,然后呢后面几个参数分别就是你的name bases和dict。这分别指的是你当前那个类是谁,你当前这个类他自己相关的信息就全部传进来了。当前类以及当前类的基类,以及当前类的类变量,这些信息全部都传进来了。)

  元编程一般用于框架开发中。
        开发中除非你明确的知道自己在干什么,否则不要随便使用元编程99%的情况下用不到元类,可能有些程序员一辈子都不会使用元类      (因为你发现,不用他依然可以解决很多问题。如果你的问题可以不用元编程解决。如果不是很繁琐的情况下,不用元编程是可以解决的。你就不要使用元编程。)

  Django, SQLAlchemy使用了元类,让我们使用起来很方便。      (前面写的小框架也没有使用,也就是说做框架不一定需要用,就看你想灵活到什么程度了。你越灵活,你可能就需要大量的动态生成代码。这个时候不用反射了,就得用元编程,只有在这种情况下用。)

ORM

  ORM ,对象关系映射,对象和关系之间的映射,使用面向对象的方式来操作数据库。

        ORM第一个O指的是对象,中间那个R指的是关系,最后那个M指的是映射,这叫对象和关系之间的映射,也只是跟模型,跟模型之间的映射,我们一般称为跟数据库这一块叫模型。那就所谓的mvc里面,那个m也叫模型,但是这地方这个m指的是跟模型之间的映射。所以它本身指的还是映射。     对象和关系,我们将关型数据库就是关系嘛,对象怎么跟关系之间建立关系呢,建立一种联系呢。我们想办法把它映射起来,那映射些什么呢?对象和关系之间能建成映射,那就是说对象和关系的映射呗,说的就这个意思,对象的关系究竟怎么映射呢?他下了这张表。

              关系模型和Python对象之间的映射
              table => class ,表映射为类      (table一张表,一张表呢,映射到class上去)
              row => object ,行映射为实例      (然后一行row,我们映射到一个对象,也就是说一行行叫一条条记录。这一条条记录呢,正好能是一个数据的完整表达,比如说你的工号是多少,你名字叫什么,你领多少钱,这是不是一行啊,这如果是一行,是不是刚好对一个员工来讲的话,就是一个员工实例嘛,员工类的实例嘛。然后里面写几个属性。)
              column => property ,字段映射为属性      (然后里面写几个属性,员工id是多少?员工名字是什么?员工工资是多少,这不刚好能映射起来。对不对,那这张表表示什么,我们create table,你看写什么,说是一个表中定义一个表名,然后有哪些字段,哪些字段都什么数据类型的。然后你想我们定义一个类,这个类里面有多少属性,这些属性都是什么类型的。这不是刚好一样。是吧,大家可以发现,它刚好是一样的,那我们就有了这样一个对比之后,发现原来是可以这么做的。那你这一列列不就是id,name,加上薪水,对不对,这三样。那我这块定义三个属性id,name,薪水,这三个属性不刚好跟你对应起来,你说你是整形,我也有整形啊,你说你是varchar我这有String啊,你说我varchar后面要写个最长长度,那我String也写个最长长度不就完了嘛。刚好能把他们完全对应起来。所以我们说这两个之间可以存在一种映射关系,但是我们为什么不直接用sql语句呢,非得要这样来做呢,是因为有些场景下。我们发现面向对象的方式操作起来方便。你操作sql数据库,sql语言的话总是来讲的话,那东西就是非面向对象的东西。非面向对象,我们把数据能不能封装起来,按照一个类或者类的实例的方式来操作他呢,你想刚才那张表,我现在映射到一个类上去了,我以后操作类里面年的方法行不行。因为我们讲这个类定义完了,它属性定义完,未来他的实例就是一个数据的封装。那数据的封装,这数据是你的,我在你这个类里面我在定义几个方法。这几个方法就是操作你这几个数据的嘛。因为你这个实例知道自己的数据是什么,我当然方法就定义在你这个类上,你当然懂你的数据怎么操作啊,我换到别的类,别的类不懂啊。因为别人类没你的数据啊,对吧,你现在这个员工类,你一个员工类里面有这些数据之后,我就在员工类上去定义一些操作数据的方法,谁的数据谁操作,就这么定义。      可以认为这也是一种就近原则,你的数据是吧,那这方法本来就是操作你的数据,就定义在你这个类上。我就不要再单独再拿出一个类在上面定义方法,不是说不行,可以,但是我们一般情况下直接操作这个数据的方法,是定义在这个类本身上面。不会放其他类了。对吧,其他类不懂你的,还得想办法,要不要传参过来呀,要不要从你继承啊,反正中间要想很多的事情,想办法,因为什么,他没数据啊。对吧,写个函数,我专门操作这个类行不行,可以啊。我们经常写一个什么东西之后,然后上面参数写个什么类型,然后冒号后面写上类型嘛。可以操作,不是不可以,可以操作。但是呢就是说一般情况下,那你如果这么做的话,你专门是为了操作这个类型的数据的话。你还不如把这个方法直接定义到他上面去嘛,除非你是个公用的方法,也就是说你除了操作这个类型的数据以外,你还操作其他的类的实例嘛,对吧,如果你要这么做的话,那你就把它定义成公共方法。如果你这个方法只是为了这个实例,这个类的实例服务的,只是去处理他的数据的话,你就把这个方法应该定义在,这个类上去,就不要定义在其他类上去了。这是我们定义时候一些原则。但是不管怎么样,我们都是通过面向对象的方式来操作数据的。对吧,关键现在数据怎么来啊,我们前面用的这个pymysql,select查回来了,然后有个结果集,然后我们遍历结果集依次来处理。      现在呢,你能不能把这些数据给我封装到一个类里面去,一个类的定义里面去,你把这些东西给我描述好,这样我就能用面向对象的方式来操作数据了。那种操作方法其实写函数就行,pymysql写函数就ok了。就没有必要去封装成什么面向对象的方式。我们现在就说。我以后的在这个类上我要做一个修改的方法,在类上去生成一个修改的方法,以后调这个修改方法我就可以指定相应字段去修改。我要这么做。那如果说我现在要增加一个数据的话,那就跟这个表相关的这个类,我上面要增加一个,就所谓的添加数据的方法,添加数据的方法,到时候只要给这个添加数据方法里面传参就行了。传参之后,只要调这个方法,他就会把数据塞到他所关联的那个表上去。那我要查询怎么办?我在这个类上我调查询的方法就可以了。他就会给我返回一个个对象,就一个个这个类的实例,返回一个个这个类的实例是什么意思,一行记录一个实例嘛。就这么玩儿啊,大概有个印象。那为了这么做以后呢,我们下面呢就是想举个例子,我们来看看这样操作究竟有什么好处。那这样呢,也是有一些原因的,就这么做。原因呢就是说,往往呢面向对象,你可能会,但是呢,就是说你对sql语句的掌握没那么好,甚至有的这种orm框0架的作者本身就是讨厌写sql语句的。而且他甚至说我不太会写,但是我会写面向对象。然后我就用面向对象方式来操作你。那我操作的过程中肯定需要,因为数据库只认一样的东西。sql语句这怎么办呢,中间肯定会有一个转换过程。转换过程怎么办?交给框架做我们就不用做了,对不对。那我们今天就是要自己写一个小框架,对吧,我们要做这件事。)

举例

  有表student ,字段为id int , name varchar , age int映射到Python为

        假设有一张表,如果有一张表叫这个student 的话,那我们未来就想定义一个啊,这是一个最简单的方式啊。有的时候我们。比如说多表我们也想生成的,对吧,最简方式单表,我们先从单表开始。      有个单表到student。那我们知道开始之后,我们要给他映射一个类。这个类名也应该叫student。然后呢它的字段有id int,那我定义一个属性也是int就完了嘛,然后name varchar,那我定义一个name叫string,不就完了吗,然后age int,这是我们映射的东西。但是你想,属性在python中。就像我们明确知道属性有两种,一种是类的属性对吧,一种是未来示例的属性。那你觉得像定义字段这些东西,我们应该定义成类属性,还是实例的属性啊,实例不可以吗?self.id 不可以吗?刚好一行行记录来对应一个个实例的嘛,你定一个类属性的话,大家公用了这不出问题了吗。class student:    现在定义成类,id=1,name='tom',那下一个还能用吗,定义成类属性可以吗?定义成实例的吧,你确定啊,定义成实例的?好好想一想啊,这个问题到底应该怎么定义。因为我们发现在定义类属性是不是所有的实例公用,所以说你要定义成这样肯定不太合适啊。所以我们知道一行行记录是不是对应,这个类的一个个实例,是吧。所以说呢按道理应该来讲的话,应该是self.id 等于这种方式会更好,def __init__(self):      self.id = 1,但是呢,我们说给大家提前预警过,这东西呢在python中我们知道有一个很牛东0西是吧,描述器是吧。有个描述器是不是能解决我们很多的问题呢,所以说到底应该定义成什么,按道理,按照面向对象思想来讲的话,一行行嘛就应该对应一个个嘛,对应一个个的话,按道理讲说这个属性。最终应该是应该是实例的,最终应该是实例的是最合适的。但是呢,因为有一些特殊的原因,加上python 特殊的语法,特殊的一些技巧,对吧,所以呢这个地方的话是不是我也可以用一个,其他样东西能把这个东西解决掉,对吧,叫描述器。。那描述器这东西能不能解决我们的问题,那你现在如果有个描述器,大家想像描述器还可以赋值啊。还可以读还可以写,应该是个什么样的描述器啊。数据描述器是吧,数据描述器是最合适的啊,也就是说要实现什么方法?__get__和 __set__都要实现,删除一般很少用。所以呢如果要借助描述器,你就必须得用一个数据描述器才行。
# 我们刚才是有两种映射对吧,两种映射到底用那种方法。
class Student:
      id = ?某类型字段
      name = ?某类型字段
      age = ?某类型字段
最终得到
class Student:
      def __init__(self): # 我们的目的是这样写。但是我们说你如果用上面类,用类上面这个属性来说,你类变量的方式,你如果这么一写的话就会带来问题了。我们知道所有实际功用,这肯定是有问题的。但是我们如果要写成这种方式的话,按道理目前能解决我们的问题,对吧,这是其他语言都是这么玩的。但是到了python这边呢,他因为有描述器这些东西啊,所以他就可以用其他的方式来解决这样一个问题啊。那我们来看我们要实现一个orm框架。应该怎么做。orm框架其实实现的东西无非就是说从表到类。
            self.id = ?
            self.name = ?
            self.age = ?



这是一个ORM框架的雏形,从这个例子就可以明白ORM框架的内部原理 。

学习一个ORM框架
1,看Model类如何描述表,属性和字段如何映射
2,增删改查方法调用如何转化为SQL语句并执行

实现ORM框架

  那我们来看我们要实现一个orm框架。应该怎么做。orm框架其实实现的东西无非就是说从表到类。从字段到属性的映射。但是我们知道有实例的属性,知道有类的属性,这个东西我们看怎么映射才行。然后呢不仅仅得完成这样映射,我们还得完成数据类型的描写,大家想我们为什么self.id = ?这个地方要。要提出这样的一个问题,你想self.id = ?你这个东西说明什么呀?或者说你这样一个东西能说明什么。我们知道如果说你的属性想跟字段之间建立映射关系。字段呀,我们定义一个字段的时候,我们在写sql语句这个东西的时候,大家来看。你为了写他对吧,你看你写多少东西,你看`dept_no` char(4) NOT NULL,,你觉得这东西是一个String能表达的吗?不管你是一个class Student:      id = 1类变量,还是一个class Student:      def __init__(self):      id = 1实例上这种变量对不对?不管怎么说,你发现你的这种表达根本就不满足我字段的要求。我字段除了我有某些数据之外,我还得有某种其他的特征的表达,你有吗?你没了,你String还是怎么样?你String能表示NOT NULL吗?你String能表示PRIMARY KEY吗?做不到,对不对。所以这个时候的话,我们发现要是这id = 1后面呢,能够有一个什么样复杂的表达就行了。什么东西,能是个复杂的表达,不就是描述器吗,因为他后面背后是一个类在帮助你来做完成这事儿。对吧,那个类当然可以描述啊,那个类描述是谁呢?你既然是字段的,那就用这个描述器来描述字段的各种特征嘛,class Student:      id = 1 描述器,来描述字段的各种特征,比如说你是不是pk的,你是不是unique的,你是不是还有什么其他的各种约束,是吧,你是否能不能为空啊,是不是像这些特点,你是不是还是外键啊,对吧,你说这东西你能拿一个1能描述吗?肯定描述不出来嘛,对不对,1只是里面的一个数据而已。那这个数据很好办嘛,这个数据我放在一个类里面存起来不就完了嘛。那这些特征我是不是放在一个类里面也可以存起来。所以说这个地方想来想去,怎么样解决最合适呢?你就得用描述器。那既然是描述器,而且还得是个数据描述器,那class Student:      id = 1 这些地方是不是大家,都待定义不同的类啊,那么来看你描述的是1,整形,是不是有整形的要求,你描述的如果是string ,是不是有string的要求,各应该有各的要求。好的,我们由字段来构成。那么表呢是由字段构成。但是这个字段呢因为它的描述太复杂了。我们根本不能用一个单纯的字符串或者数字,能够把这个事儿办成。所以呢我们必须用类属性,然后加描述器的方式。才能够达到我们的目的。      那字段的实现大家来思考一下字段的问题啊。

字段类的实现

  字段有名称name、字段名称column、类型type、是否主键pk、是否唯一键unique、是否索引index、是否可为空nullable、默认值default、是否自增等,这些都是字段的特征,所以字段可以使用类来描述。      (我们要想办法来做这件事。但是你看了这些东西之后,你马上想到了,这东西封装成一个类就行了呀。封装成这个类之后呢,这个类就可以解决我们的问题。你为这个类实例的时候就可以解决这样的问题了。就是这个类在实例化的时候,你把这些东西传给他,你说,你现在要解决一个名字的问题。这些问题,这些问题,你想每一个字段是不一样啊,所以这个东西应该用所谓的一个类的实例的属性是不是来保存啊,因为每个字段都不一样了。)
  
  字段类要提供对数据的校验功能,例如声明字段是int类型,应该要判断数据是不是整型。      (字段类要提供对数据的校验功能,这天然就应该放在跟字段相关的类上面去做嘛。因为字段类是不是保存着字段本身的很多特征啊,那这种处理当然需要由他自己来实现啊,所以这个方法就应该绑定到,我们未来对字段生成这个类上去,在这个类上去建这个方法,校验数据功能,这是我们最简单想到的功能,对不对。比如说你传了一个字符串,这个字符串呢,你在数据库中定义它是五个字符的长度的,那你这儿就必须解决这样一个问题,由你自己来解决这样一个问题,就数据校验它要写了六个长度,你就不允许再填进去了,你就必须把它拦截住。)

  字段有多种类型,不同类型有些差异,使用继承的方式实现。

  字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了。
# 那我们第一个要解决的问题肯定是要定义一个什么东西,对吧,那怎么定义呢?
class Field:      # 这是不是我未来准备做什么呀?是不是做了一个所谓的这样一个东西是吧。大家来想一下这个问题,大家想一想。如果说我用一个Field能不能解决所有字段定义的问题。就这一个类能不能解决所有字段,比如说int的,datatime的,这些东西是不是能解决掉。      比如说我现在字段吧,你想字段定义嘛,是不是int型需要解决,string类型也就说相当于varchar,data的,还有枚举类型的enum,float的,还有很多其他类型的这些,像这些类型都要在这一个里面解决,是不是不太合适啊。当然我们写着写着改也行,但是我们现在是不是在想这个问题啊,看看后面我们要不要想办法调整这个事情,对吧,那你现在是不是要构思我们下面未来是想怎么写这些类的关系,所以说你一个Field是可以解决很多问题,但是呢,总是有限的。对于每一种数据类型来讲的话,至少他的校验方法不一样吧。你怎么做啊,你说我在一个里面,建一个方法里面,就判断我当前是干什么事儿的。这到不是说不行,是吧。所以呢这个时候我们发现大家有差异,但又有很多共同点。比如说我不管你是什么类型,是不是都可以做unique key啊,那我们来问问你是不是支持unique key这是不是共同的东西。只要你是个字段,你都可以作为主键的可能性,你都有可能,对不对,所以主键这样,比如说缺省值,我管你什么类型,是不是都可以设定缺省值这是不是就是公用的。但是呢,比如说字符串有长度问题了,对吧,比如说整形,我就认为他没有长度问题,这是不是个性化的东西啊,所以像这种情况下我们应该怎么解决啊。有很多公用东西,但是呢,其他的有很多不一样的地方,怎么办。你用什么样的设计方案?对呀,继承嘛,个性东西是不是稍微做的不一样的地方,你修改修改就可以用了嘛。所以呢我们这块做一个简单实现。
    pass
      
# 这是不是就可以了,每一个做一些自己不同的东西。
class Field:      # 所有字段共有特性
    pass

class IntField(Field):      # 继承Field共有特性,添加Int独特特性,
    pass

class StringField(Field):      # 继承Field共有特性,添加String独特特性,
    pass
# 这个地方的话是不是有一些初始化函数,先丢这。我们说未来我们一定希望他是不是有一个所谓的数据校验功能。
class Field:
    def __init__(self):      # 初始化函数
        pass

    def validate(self):      # 数据校验功能
        raise   NotImplementedError      # 但是对于一个字段来讲的话。他的校验方式应该怎么处理呢?这东西你想每一种子类型才是不是具体类型啊,你具体类型具体对待呗,我这儿不实现。因为我不知道你是int,对不对,因为这是子类要实现。咱们现在有这种设计,我才说这样的话,对吧。因为我们有了继承的关系,每一种继承都是他真正具体的类型。那么具体的类型里面就应该知道他这种具体类型如何验证。你就不要问我基类嘛,我基类,告诉你我是个框架嘛。我是个架子呀,你子类才知道自己具体的情况是什么,由你具体来解决数据校验的问题。我这儿对不起不实现。      你把这样一个类型往外抛就行了啊,把这样的东西往外抛,抛一个实例还是类啊,随你,对不对,不行,你在里面写几句话,raise   NotImplementedError('not')也就是说,就是没实现对吧。      我们通过这种方式就可以告诉它基类不实现。因为基类它只是做一个大的架子,其他子类继承之后,由他们具体实现,我这不管。
        pass

class IntField(Field):
    def validate(self):       # 这地方是不是也得有数据校验功能,因为每一种类型是不是至少你的校验方式应该跟别人不太一样吧。你比如说日期的校验的方式,比如字符串的校验方式是不是都不一样了,对吧,这是我们要解决问题。
          pass

class StringField(Field):
    def validate(self):  # 这地方是不是也得有数据校验功能
          pass
# 传什么呢,我们说有很多的首先是字段,你给个名儿呗。字段给个名字,我们可能还有一个什么呢,字段本身的名字,比如说fieldname,我们先这么填,不合适再改,你是不是主键pk我们给他个False,你是不是unique的默认也给他个False,有没有缺省值default给个零不合适吧,给个None吧。是否为空nullable等于True,一般情况下给的宽泛点,可为空,索引也一样是个布尔值index等于False默认不是索引,是否自增auto写到这合适吗,int要不要自增,是不是,这特性不能放这儿是吧,既然是这样子的话,那是不是还有一个所谓的个性化的东西,个性化是什么呢?
class Field:
    def __init__(self,name,fieldname,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name       #  以后呢这东西都写成代码生成器了。就不自己这么敲了。这些地方,都是可以有一些变通的方式,就没必要这样体力活,自己敲。
        self.fieldname = fieldname      # 因为name和fieldname这两个名字你是不是总得给我一个。我们知道,如果给你个名字,我们这块字段是不是得有自己的名字,那这几个名字之间到底应该怎么去处理呢?我们想想办法,比如说字段名,你要是不给。就像刚才我们那个类名,在type后面是不是还可以在给个名称,那是他给人看的名称,但是他自己是不是待有一个自己的一个真正的实例这样一个名称,所以这两个名称可以不一样,但是呢,有可能,你比如说,我们这儿fieldname = None是不是可以写个None啊。
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self):      
        raise   NotImplementedError
        pass

class IntField(Field):
    def __init__(self,name,fieldname,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):      # 基类里个性化一堆的东西都要拿过来,我们把auto_increment=False,等于True还是False这个倒无所谓,那下面这地方应该怎么写。
        self.auto_increment = auto_increment      
        super().__init__(name,fieldname,pk,unique,default,nullable,index)            # 然后我们是不是照抄就行了,我们把这块给他补齐了,因为self.auto_increment = auto_increment这块是不是属于他个性保存的东西,这里不写,super的这些东西是不是属于上面保存的
    def validate(self): 
    pass

class StringField(Field):
      def __init__(self,name,fieldname,pk=False,unique=False,default=None,nullable=True,index=False,length=32):      # StringField这里就好办了,照抄IntField就可以了,auto_increment这快要做一下改动,跟字符串没关系,改成length等于给个小点32,我们就是通过这样一个长度,告诉他这东西你待有个长度判断,通过这样一些简单的方式呢,我们就可以解决这样一些问题。总之呢我们发现什么不合适,先给他写在这。
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass
# 所以这两个名称可以不一样,但是呢,有可能,你比如说,我们这儿fieldname = None是不是可以写个None啊。如果是这样子的话。相应的下面的都该一下fieldname=None,就是说你至少给我个名称嘛,不给我名称我就None。那么看这块应该怎么处理。
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:      # 如果fieldname是None
            self.fieldname = name      # 如果他是None的话,这块是不是要有一种解决方案,是不是这个self.fieldname应该等于name
        else:
            self.fieldname = fieldname      # 否则,就用fieldname传进来的,这样是不是传进来的,你就写一个名字就可以了,其他的就别管了,通过这种方式,我们就可以把它构造完成。第一步差不多了。
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self):
        raise   NotImplementedError
        pass

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

class StringField(Field):
      def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,length=32):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

# 那如果这么一构造的话,那我未来想怎么写呢?未来这地方不就写一个StringField,然后里面给名字就行了,你比如给个名字就叫name不就完了。对吧,那有人说那你后面不用写嘛,对啊,按默认值来嘛,它是pk吗,不是吧,id = 1这个才是我们想要的主键吧,但是假设我们这个名字它是有要求的,对吧,这是一个登录名。那我们是不是unique,应该等于True是不是要改他的值啊,也就是说这个在我们的表中设计他是唯一的,名字不能冲突。所以说我们就通过这样一些设计来给他解决。那我们的名字这边到底要加不加索引呢,要看你表里面怎么设计了,一般索引这个东西你写对了,写错了也无所谓。这东西干嘛用呢?写后面这些东西呢,都是为了生成表的时候用的,我们是不是在这在详细定义表,我用属性的方式是不是在描述字段。你到时候就可以用这个,你写好的类,用它反过来去创建表,我们不用sql语句,我们就用自己写的类。当然最终他是调sql语句来创建是吧,我们通过这种调用方式,剩下都是该缺省缺省,但是呢对于String来讲,是不是还得必须给个东西,给length,这个length我们放到后面去了,你可以把length放前面来,
class Student:
    id = 1
    name = StringField('name',unique=True,length=64)      # 这里length给他改大点64

    def __init__(self):
        self.id = 1
        self.name = 'tom'

# 把length放前面来
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False): 
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self):
        raise   NotImplementedError
        pass

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):         ,      如果length放前面应该这样写, length写前面等于32,name我们在这也是可以为空的等于None,如果你要怎么写的话,后面这个length就不要了,因为对一个字符串来讲,我当然知道你是字符串。
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

class Student:
    id = 1
    name = StringField(64,'name')      # 我们这块就可以直接写个64就完了,是吧,假设他这地方是可以重复,你是不是就这么写,就完了。只不过我们这个name我们没搞定,我们把name给搞定了,比如说我们通过某种方式能把name拿到,也就是说Student好办的呀,你只要能保证Student对应的是数据库里的表就ok了。其实这块大小写无所谓,因为这东西对数据库来讲是不是大小写无所谓,类这块我们是不是要求大家首字母大写,用大驼峰的方式来写。如果我们name还能解决,把这个Student塞到name这里,那基本上就全部解决了,就写个StringField(64)就行了啊,这就是我们想解决的问题,现在呢我们不允许他这么做,你必须给我个名字,对吧,所以说我们这块是不是还得想办法写一个'name'这个地方我们就说,指的是name这个字段的。把Student表名也可以取到,但是我们现在更想取的是,因为字段就取字段名就可以了,对吧。      这是未来我们想用的东西对吧。那这么一写,这是不是就是描述器了?不是,哈哈 那怎么才是个描述器呢,来先把这两个方法写在这是吧,一个是get方法,get 方法,然后set方法是吧。

    def __init__(self):
        self.id = 1
        self.name = 'tom'

# 那怎么才是个描述器呢,来先把这两个方法写在这是吧,一个是get方法,get 方法,然后set方法是吧。下面就是我们要解决的问题了。我们应该怎么写?这个地方究竟该怎么写?
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):      # 我们知道这个self拿到的是Field他的实例,也就是说最后拿到的是你给的IntField这个实例。因为都到父类这来了嘛,那也就是说你实例本身是谁就谁。instance这个呢,这个就是指的Student当前实例,owner指的是Student,那我们下面应该怎么玩呢?
# 先把这句保护的东西先写了。如果instance是None
# 返回self,但是我们真正要return什么东西,你想想我们目的是在干什么,对吧,我们想要干什么?      我们是知道用描述器,但是我们现在描述器已经描述了呀。关键不就是这id = 1一句话嘛。这句话是不是调def __set__(self,instance,value):这个方法,你把这句话想清楚他在干什么,我们应该怎么做这个事情。      这块没想清楚业务应该怎么办。我们回顾发现,一旦你这个数据描述器这self.id = 1      self.name = 'tom'这块儿。一旦有设置值,按我们以前的测试结果是,他以后Student的实例的dict那个字典。他是空空荡荡的。因为我们说是不是描述器优先级高于他是吧。什么叫高于他啊,不就拦走了嘛,对吧,拦走不往里面写就完了嘛,不给里面写,是不是不就进不去了。 不给里面写就写不进去了是吧?我们解决方案是什么?我自己写个不就完了嘛。对不对,这还不是就由你说了算吗,set拦截掉了,剩下是不是就由你说了算了,那我们怎么解决这个问题呢?
        pass

    def __set__(self,instance,value):      # 那我们怎么解决这个问题,
        self.name = instance.name      
        instance.__dict__[self.name] = value      # instance谁呀,Student以后的实例,点,你总待有个dict把,原来的dict是不是没人用啊。用不上嘛,那是因为写程序的人没用,那我现在想用行不行啊。代码由你写的,当然是由你自己了是吧,然后干什么呢?还填个什么东西呢?你看我这name = StringField(64,'name')name,'name'。是不是好像这个self.name嘛。这name是不是就是'name'他嘛,这从什么时候进来?是不是def __init__(self,name在这进来的啊,是不是传在self.name了,这name是不是就是id = 1你这id是不是我也叫id 啊,你是name,是不是我也叫name啊,对吧,那这self.name东西不就是我们未来想保存的所谓的属性名和字段名字嘛。对吧。虽然说他们可以不一致,但是我现在更关心的是,你是谁呀,是不是现在我们的方法是不是通过什么,我们现在通过的方法是不是自己手动保证啊。保证name = StringField(64,'name')这个'name'名字是不是跟name = 这个名字一致啊,我们现在手动保证,如果能手动保证,那至少也能保证啊,那我们大胆的instance.__dict__[self.name]这么用不可以啊。你就拿来用了,这self.id = 1地方是不是,如果这个地方我要写的话,是不是应该写成self.id = IntField然后这边应该给个名字吧。对吧,给个名字就给个什么名字啊,你一定要写id 呀,你不写id 他在哪找,是吧,对不对啊,你当然呢有人说那不一致行不行,不一致也可以,反正你get的时候是不是写一致就行了。对吧,好,那么通过这种方式,就是你给我指定的名字,是用来做字典中的一个key,是吧,然后等于谁呢?你爱等于谁等于谁,你等于多少这值是不是我从value那拿到了。      那有人说这个__dict__不是不该用吗,没人说不该用啊。只是以前我们不用而已嘛。对吧,现在我们为了方便是不是存到__dict__他上最合适啊。而且instance这东西存的是跟实例相关,没问题吧。每一条记录对应一个实例,各玩各的字典嘛,是对的吧,对不对。
        pass

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self): 
    pass

class Student:
    id = IntField('id')
    name = StringField(64,'name')      # 那你非得说我这StringField(64,'n')地方叫n 怎么办,那你叫n也行,反正StringField这东西在他上面存着,以后对name他的操作一样会到get的instance里面来,或者到get的instance里面来在传到self.name,总之你class Field:      def __init__(self,name      self.name = name这个实例里面是不是记得这个名字啊,也问题不大。但是呢,别给自己找事是吧,

    def __init__(self):
        self.id = 1
        self.name = 'tom'      # 这是有的时候你是想不出来的,你是不是得先把class Student:这个例子先写出来,写出来之后然后我们再反推它是怎么过程。我们发现这id = IntField('id')和name = StringField(64,'name')  如果定义成描述器的时候。当我们真的对他self.id = 1      self.name = 'tom'赋值的时候,不管你是在哪儿赋值,不管你是不是__init__这个函数中赋值,总之只要你敢给这个描述器的属性赋值。他就会调用set方法,就会跑set方法里面来。跑到set方法里面来,我们能拿到的是self,instance,value。那我们来看instance指的谁?指的Student当前实例。当前实例的话,我们说__dict__字典现在没人用啊。对不对,你实例记录自己的数据,最好的办法不就是在实例自己的字典上玩嘛,所以说我们要把这个字典用上,把字典怎么用上呢?你总得给我个key吧。这key是谁呢?那你给谁就谁。但是name = StringField(64,'name')如果'name'这个名字你跟他name = 不一致,是不是就要求你这些名称就待互相独立,不得重复啊。不然这属性会不会造成互相覆盖啊什么的,是吧,就出问题了。所以呢,我们self.name借用他们名字不一样,名字不一样是不是作为key是正合适的。      我们在这每一个Student就是最后未来的实例,那一个个不同的实例上面,然后存value它。写self.id = 1      self.name = 'tom' 进来的值。      那def __init__(self):      self.id = 1     self.name = 'tom' 这个是不是也调def __set__(self,instance,value):这个方法,因为这是基类才有的东西嘛,是不是各子类没实现啊,是不是调父类的,对吧,那跑def __set__(self,instance,value):这来无所谓啊,各做各的事儿就是了嘛,因为各自的id = IntField('id')      name = StringField(64,'name')这个名称是不是不一样啊,是不是在字典种各放各的地方。      我们存数据之前,是不是得做点事儿?对吧,你这时候不校验什么时候校验啊,一个错误之存进去之后再校验吗?你把原来一个合法值都覆盖了,然后你在说这值不合法。对吧,这样不合适,是吧,因为在def __set__(self,instance,value):      self.name = instance.name这儿不拦就变成既定事实了,你还得写更加复杂的。一旦要换值,还有记住一个前一个值,你在这直接拦一把不就行了嘛。
#  我们存数据之前,是不是得做点事儿?对吧,你这时候不校验什么时候校验啊,一个错误之存进去之后再校验吗?你把原来一个合法值都覆盖了,然后你在说这值不合法。对吧,这样不合适,是吧,因为在def __set__(self,instance,value):      self.name = instance.name这儿不拦就变成既定事实了,你还得写更加复杂的。一旦要换值,还有记住一个前一个值,你在这直接拦一把不就行了嘛。
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)      # 那我们虽然说self.name = instance.name 他没有实现。但是呢,我们self.validate()总有办法实现,我们这块校验谁呀,是不是校验value。但是你发现def validate(self):我们这块是不是定义都有这样的问题了。所以我们把value加上来。代码就这么一点点改的。      我们通过这种方式是不是你只要敢这个self是自己def validate(self,value):父类的,你试试看。是吧,肯定不行。那我们知道现在这后面有个具体的一个实现啊,具体实现的话,调用的时候是不是调用各自,自己的实现。比如他class IntField(Field):      def validate(self): 比如他class StringField(Field):      def validate(self): 。是吧,这是我们要解决的问题。那我们现在来想,如果get的时候,你现在知道get的时候应该返回谁了吧。对吧我们说这instance 里面是不是有个字典啊,这字典里面应该有谁?return instance.__dict__[self.name],你也没有其他办法了吧,你只能这个时候用自己的名字,是不是往回返啊,你还能有什么呀?就自己的名字呗。对吧,因为你写的时候是靠你名字写进去的,你取的时候当然要看你的名字拿回来啊,不然就乱了嘛,对吧。好,我们通过这种方式的话,就可以把这些数据给他存进去拿过回来,同时别忘了def __set__(self,instance,value):       def validate(self): 在这加个校验就是了。一定要加个校验,你不加一个校验,这个数据就不合法,对吧。我如果self.validate(value)这么用,看来有问题。因为你self.validate(value)这么用,有可能还会执行instance.__dict__[self.name] = value下一句吧。所以我们在用的时候,这class IntField(Field):       def validate(self,value):地方应该怎么办, 是不是抛异常是最合适的。也就是说,你未来在使用的时候,如果不合法怎么办,抛异常,不然的话instance.__dict__[self.name] = value这句是不是就执行了。那你self.validate(value)这个校验还有还有用吗?和可以使用if self.validate(value)判断,但是这种情况下,我觉得最好的实现还是True和Field对吧。那我们现在不这么做的话,应该怎么做?是不是抛异常,抛异常也可以。def __get__(self,instance,owner):      和def __set__(self,instance,value): 描述器这块算是写完了,你只要把它想清楚存哪剩下事儿就不用关心了。因为我们要知道就让你回忆这个知识点数据描述器。他的那个实例的dict 就是student实例dict。一般来讲的话,一般来讲你是用不上的,对不对,用不上。但是我们为了存数据,也就是说你的优先级高,我们知道这个知识点之后,然后我们在怎么做。我们就想办法把数据给它塞进去,是吧,各管各的数据,是吧。所以我们就来这么解决了 。      那么下来解决校验的问题。
        instance.__dict__[self.name] = value
        pass

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
    pass

class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
    pass

class Student:
    id = IntField('id')
    name = StringField(64,'name')

    def __init__(self):
        self.id = 1
        self.name = 'tom'


# 那么下来解决校验的问题。怎么校验?从最简单的方式就是说我不允许你为,什么东西嘛。这是不是最简单的判断方式,对吧,你比如说string,我首先是不是先看了你长度如何,是吧,对不对,那我是不是还得先看看你是不是为空啊。假设空在我们这指的意思是None,因为我们看,跟数据库对应,数据库中没有None,而我们能想一想能有一种办法,能让对应起来啊,那比如说None是不是就对应数据库那边的null啊,假设是这么理解的话,那个nullable就知道什么意思了,nullable是不是在我们python中就意思说不能为None,是吧。找到一种对应关系。好,下面我们是不是先解决这个nullable的问题,是吧。是否为None的问题。
# 那第一个数据如果进来,他是不是None呢,就是我们要判断的问题,对吧。
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:      # 如果你是None,恰巧现在我的要求是。如果self.pk,什么意思?一个组件能为None嘛,你就给传个None过去,就对应null过去,可不可以呀?肯定不行呀
                raise TypeError("{} is pk,but {}".format(self.name,value))      # 所以我们是不是要raise抛异常,抛一个问题出去。那么我们知道这raise后,后面还执行不执行了?,怎么不执行了。
            if not self.nullable:      # 如果self.nullable这样的话要怎么样,if not self.nullable是不是要反过来呀,如果你是None,是不是允许啊,对不对。但是这个时候我们是不是pk最优先啊,主键最优先吧。对吧,你如果是None,我首先第一个要判断是主键,然后再去关心。因为他这几个按道理,他之间是有一点点关系的。但是我们在写的时候没办法定义这么多。对吧,所以我就告诉你,你如果是pk你后面又允许他为空,说实在这个地方我当然是pk优先啊。只不过我们发现在数据库里,你一旦定义的,他是一个PRIMARY KEY,它后面一定是not null,对不对,不为空的。所以你在定义时候,如果定义有pk,有可能你后面是允许的。  就你定义的时候并没做这种关系之间的约束嘛,对不对。那所以说我们就不做这么多事儿了。反正我第一个先看你pk,我后面都不管。如果人家说你现在这个组件,你现在是为None,我管你后面定义什么。现在就该抛异常了嘛.结果呢,你说你不是主键,不是主键,我来瞅瞅。如果你现在是None,但是呢,你又不允许他是None是这个意思吧。怎么办,是不是raise一样告诉他,你的类型是不是写错了。
                raise TypeError("{} required.".format(self.name))
        else:      # 然后呢,我再来看看你还有没有其他情况,是吧。
            if not isinstance(value,int):      # 你现在给的我的是value它本身不是None,那就是一个实实在在的值,对吧,实实在在值,那我这块要不要做一些判断呢?对,判断一下类型。如果isinstance谁?是不是就是value,拿过来之后,我是不是要求你必须是int。我们现在先做一个最粗略的判断是吧。那如果是这样子的话,如果我长度超长了怎么办,超长了把这种数据,提交数据库时报错,然后让他抛异常回来,对吧。你说就只能做到这个程度了,但是你要做的好,你就应该在你这一块儿,就应该把这个验证做完善了。拿太太多了啊,我们就不管了。
                raise TypeError("{} should be int".format(self.name))            # 如果是这样子的话,那我们怎么办呢?当然你说返回一个类型异常也可以啊,因为你都不是我的int类型,对不对,我要求你这个类型必须是什么样的东西,结果你现在也不对。就可以抛类型异常,都没什么太大问题。我们通过这几种方式,就可以把这样的一些事情给他抛回去了啊。下面就是一些你怎么写的问题,抛的异常信息,比如raise TypeError("".format(self.name,value))告诉他,self.name哪个字段,value什么样的东西,写错了,这地方格式化的问题就有你自己来定了,你可以写的更加好


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):      # 这块是不是跟class IntField(Field):也是同样的问题,def validate(self,value): 复制过来就可以了。我不管你类型是什么,总之你得给我解决什么问题啊?pk验证一下吧,就是主键验证一下,然后呢,是不是可以为空,你是不是验证一下,然后你的类型是不是对呢,是不是我们看一下是吧,pk 验证一下吧,是吧,就主件验证一下,然后呢是不是可以为空,你是不是验证一下,然后你的类型是不是对呢?这里if not isinstance(value,str):改成str。      完了吗?还有一个呢是吧。
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:      # 如果,也就是说,如果能直到我这条语句的话,他的类型你至少能保证了吧。那我们肯定要取一下这个value的长度了,是吧,value长度如果大于self.length,这肯定是不是不行的,对吧,这个地方就不是类型的问题了。是值的问题了,值出错了,但是你写个类型出错,你说你这也什么类型啊,这么长是吧,也可以,问题不大,对吧。所以说呢,这一块的话,你可以格式化稍微美化一下是吧。
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Student:
    id = IntField('id')
    name = StringField(64,'name')

    def __init__(self,id,name):
        self.id = id
        self.name = name      # 我们来看self.id用户现在通过一个描述器,对吧,这id 是个描述寄了,因为他后面是IntField('id')这样定义的嘛,这是个类属性,对吧。我们现在如果说给实例要赋一个属性进去的话,这东西id不走自己字典,他会调IntField和StringField这里面的set方法,调    def __set__(self,instance,value):这的set方法,第一步是不是就self.validate(value)直接来校验了。校验是不是根据自己现在,是谁,是谁的实例比如IntField或StringField,在做相应校验。当校验如果能通过,一定会没有异常,对吧。也就是说instance.__dict__[self.name] = value他可以放心的做下面这句,下面这句是干嘛?,是不是给各个自己的实例,Student自己的实力,在student自己的实力上来做什么事儿呢?来给他添东西,填啥,你有什么,你有什么self.name字段名儿,我给你添什么,把他value送进的这个值,我就instance.__dict__[self.name] = value借助你这儿的东西把它存下来了。因为这种设计是合适的,当然你也可以用其他方法来存。但是我们这样做合适的原因,是因为每一个student自己的这些value值当然要放在他自己的属性字典里面去了,这不最合适嘛。对不对,关键怎么存决定了你是不是应该怎么读的问题。怎么存进去,怎么读出来嘛,对吧,我们通过描述器,get和set这样一些方式就可以把它改造好。好,这应该说一个大概的雏形差不多出来了。那我们后面要解决什么问题啊,你比如说这有id 有name,你看这是不是又有self.id有self.name,那如果有20个字段,那是不是就要写20个相关的赋值语句,对吧。我们这一块是不是未来会这么写啊。def __init__(self,id,name)对吧,self.id和self.name这块是不是也要改掉了,self.id = id    self.name = name对吧,刚才是因为我们写个1呀,写个2呀这些东西,大家是不是好理解,写代码,写完之后我们再回来看看 def __init__这个东西是应该怎么做了。        为了以后用着方便呢,我们这儿再定义两个东西啊。

# 为了以后用着方便呢,我们这儿再定义两个东西啊。
:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):      # 一个str方法
        return "<{} {}>".format(self.__class__.__name__, self.name)      # 这块定义一下,return一个字符串,format,一定要知道当前的self的__class__是谁__name__。什么意思呢?就是说你到时候你这个实例,你这个self字段实例本身究竟是谁,我要看一下self你的__class__类的__name__名字。主要是为了以后让你区分出来。现在用的是IntField呢,还是StringField主要是为了让你看清楚这个,然后呢我们把。然后呢self.name就告诉你我是用的是哪个类型的field类,具体是Int的还是String的。你反正想办法把这两个东西给他打印出来就行了。      

    
    __repe__ = __str__      # 然后呢我们再给它定义一个。当然这个定义的话你可以这么写,__repr__等于__str__,可以这么写

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Student:
    id = IntField('id')
    name = StringField(64,'name')

    def __init__(self,id,name):
        self.id = id
        self.name = name


# 对于Student类来讲的话,我们下面就要严格的按照我们的要求来定义了。第一个是id这么个东西。第二个呢这块的话你如果想连着往后写是不是我们讲,这块你总得给个值嘛,你不能这么跳过去是吧,那怎么办呢?那这块的话你给个None也行,或者说我们这块直接给个id也行,对吧,反正我现在一样就是了,对吧,然后像什么pk这块儿,我们说它是第三个参数如果您是顺序写就问题都不大。那我们讲主键,那就应该True是吧。然后呢,主键这个UNIQUE KEY要给不给啊,UNIQUE实际上是可以不给了,因为主键约束是不是就可以定义这事儿了,是吧。然后默认值default呢,主键一般不给default值,那nullable呢,如果你想写的话nullable应该False吧,你想写就写,因为这个主键保证你不写也问题不大,所以呢这块要不然index,主键本身是有index肯定有,而且主键是唯一的。对吧,用主键搜索速度,那是数据库中最快的。如果他连就主键搜索都保证不了,那肯定出大事了。这表不能用了。所以写不写无所谓,甚至这个空写不写,因为主键保证这个空可以不写了。所以说你一般来一写的话,就是这块了auto_increment,这是因为IntField是不是有这样一个参数啊,那我们来看这东西你要不要等于True,这个你要写清楚,对吧。其实呢这个东西呢你写在这儿都是为了,最后你再用代码反过来生成数据库中表的时候用的。他这些定义,并不是说写到这以后能做什么。当然我们写nullable=False这个是有用的,对吧。因为auto_increment这属性我们是不是不做校验用啊,因为你是不是自增的,那我看给你个1你能说他是不是自增的,这说不出来是吧,auto_increment这东西实际上的是建表的时候用的,像这个nullable可能还能参与一下。因为为不为空,是不是可以现在判断一下,但是我们发现判断时pk是优先的。这些地方,大家还是要注意一下。那我们通过这样一些判断的话,就可以做这样,这是我们讲的int的判断。然后String判断以,String我们这会做一个调整,跟我们上面写的不太一样。这样的话我先StringField(64,'name')判断一下你的长度是吧,长度64,长度给了。然后'name'这块呢,你给不给名字,你给名字,是不是fieldname=None这块是不是我们有保证啊。那就是这个地方的话倒没什么问题了。关键是这要不要unique呢?对吧,假如这个地方如果是一个登录名,你unique就待写是吧。所以这块我们暂时不写了,不写的话,这个unique并不能在你这儿保证,因为你的实例,未来这个Student的实例,它是一行行数据,你这个数据怎么能管得着另一个对象里面,他到底跟你重复不重复啊。你想啊student,1号同学和2号同学,他们俩是不同的实例啊。你现在你怎么能在自己实例的判断方法中知道的别的实例呢?对吧,你怎么知道人家别的实例里面有没有名字给你重复啊,比如说1号同学的被一个人拿走了,拿走以后他修改了,他把名字修改成了张三了。现在二号同学被你拿走了,你拿走以后,你把名字也修改成张三了。这按道理,如果我们做的unique这种限定的话,你有办法吗?在我们python这个环境,你是任何办法都没有的。只有到什么时候出问题了,当其中一个人把它更新上去没问题。当另一个人再更新的话,因为数据库有约束对吧,他不允许两个人都叫张三。数据库这个字段,名称字段,他做unique约束的话,他就不允许的。所以在那个时候我们才能知道的啊。所以unique这些东西呢有的写在这儿干嘛用呢?有些东西写在这儿,纯粹就是为了建表时候用的。因为你用这些语句在建表的时候,你是不是要扫这个字段,它存储信息啊,哦原来你还有一个index,你是不是就要为他要创建个索引啊,哦原来你有缺省值,你要为他创建个缺省值的那个时候用的。只不过呢判断是否为空,我们现在可以做,unique刚才我已经给你分析过了,你为什么说这东西你拿来,但你现在用不上呢,因为你没办法到管人家其他实例。你当前实例只能管自己实例,你管不了其他人。好,那我们这块应该怎么做呢?那是不是这个nullable可以做一下,是吧。那你说名称必须有,那就是这东西是不是必须得是nullable=False。对吧,因为这个是我们属于数据校验时就可以用的。我们在客户端这边就做校验了,别把数据都已经存到数据库了,由数据库在给你抛异常。我们能栏一道,就栏一道吧通过这种方式,我们就可以使用它了,对吧,这还有个age字段是吧。age字段是不是就是一个数字型的。但是呢,age = IntField()他这个里面呢,我们实际上告诉他叫age就行了,'id',True这东西是不是默认生成就行了。那这块是不是pk呢?不是吧,unique默认就是Faise, 能不能为空呢?年龄不给就不给了呗。所以这条写完以后就很简单,age,如果我们后面再能做更多的事情了,'age' 都不用写了,直接干嘛呀?对吧,age = IntField('age')我默认是不是就可以跟age =这个属性名就可以一样。我都可以不做了。所以我们通过这样一些方法就可以解决这样的问题。那如果是这样子的话,这一块的话self.age = age你还得写。为什么这块要写,因为这个属于,是不是后来填充数据时候用的呀,对吧,这个填充数据给你定义没有关系吧,你定义好,填充数据是不是由外界给你提供进来的,什么时候给你提供,由某些语句,你比如现在你用那个pymysql是吧,你用pymysql然后去查数据库,查完数据库那结果是不是一行行的结果,是吧。那结果集里,是不是一行行的,你把这一行行是不是塞进来,就是一个个,一行创建一个实例,一行创建一个实例,就这么做。然后就每一个实例你拿到之后我们是允许修改的呀。我们用的是描述器啊,数据描述器啊,是不是允许你修改,你当然可以修改。你修改完之后呢。你是不是想用他的某些方法,是不是想操作一下,我想用你的方法,我想让你操作一下,操作什么呀,操作数据库呗。比如说,要讲我们未来怎么想用了。
class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Student:
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self):      # save方法,
        # 连接数据库      # save方法就待连接数据库。连接数据库之后,拿到这个连接以后,我们下面就可以用前面写的一套东西。比如with conn拿到了什么cursor是吧,拿到cursor来做这事儿,对吧。
        # cursor execute(sql)      # 拿到特色cursor以后调execute这种方法,是吧,调这种方法是不是里面送一个sql语句啊,最后是不是执行完成啊。就这么个过程对吧,就这么个过程,这就是我们在save中要解决的问题。      # 那大家来思考一下s = Student(5,'jerry',20)这个问题,你5,'jerry',20这些数据不管是查回来的数据被你改了,还是说你现在直接构建了一个之后,构建一个之后,你说你现在改,那等于还是新构建的嘛,总之它都是新的嘛。你这个是新的就是insert,无非就是insert的和update,delete我们先不考虑,那通过这两种方法的话,我们都可以通过save来做。所以连接数据库。好,那我们先来考虑一下你这个数据操作方法里面的连接数据库这一段儿。应该写在student的一个save方法中合适吗?大家知道吧,不管怎么样,首先说他放在这儿不合适,但是你封装不封装成类。你是用函数的方式其实也是可以的。总之数据库连接部分是不是应该提出去了。我们说这块不管你有函数封装还是类封装。所以连接数据库的语句是不是应该写的Student外面位置啊。

s = Student(5,'jerry',20)      # 比如说我未来有个实例,我这实例怎么用,比如说这是个5对不对,我们给他送进去这个名字,比如说名字现在改成叫jerry了。我们现在在模拟什么,在模拟insert的是吧,想让他以insert进去,然后年龄给个20,ok。这是s是吧
s.save()      # 那我能不能通过s调一个,比如像save这样的方法,为什么用save,因为我们为了统一,比如说以后你还想实现什么update。update是不是也是修改之后也要save,然后insert 是不是也要让数据save进去,那我们就用save再统一来做的了,我这一调save,好了,这数据是不是最后就要想办法写入数据库里面去了。那这块的话我们得写个save方法。

# 连接数据库的语句是不是应该写的Student外面位置

import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Student:
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,conn:pymysql.connections.Connection):
        # 连接数据库
        # cursor execute(sql)
       sql = "instrt into student (name,age) values (%s,%s)"      # 我们知道这块儿是不是应该写个sql语句。
        try:      # 那只有这样其实还是不太合适,你还得有异常判断,对吧。所以呢我们这一块的话应该怎么做呢?是不是应该这么做try。
            with conn as cursor:      # 是不是这块儿就相当于,那我们把这块稍微补充一下吧import pymysql,对吧。这样的话我们发现这块是不是就可以得到一个conn,as给谁呀?,是不是给cursor,然后下面是不是就是你那条语句。
                with cursor:      # 然后呢如果说你这块儿,然后cursor是吧,你不用了,你就关了。因为下一个谁我还真不知道,你用完了,你赶紧关掉,对吧。那连接你可不敢关啊。是不是。那如果连接不关,try我这块,当然一般情况下你未实现他的原因是,这一块执行完之后,当然你要去判断,因为save语句往往不是select语句,他是update语句或insert语句。所以这一块我们应该做什么,是不是conn的什么?commit是不是提交是一定要做的吧,对不对,这块提交一旦出现异常怎么办?
                    cursor.execute(sql,(sqlf.name,self.age))      # 然后cursor对吧,点execute,是不是这个地方把这个sql语句拿进来,当然你后面按道理应该有个age什么东西是吧,就是那个元组啊,列表啊,字典啊,按道理我们是不是推荐大家使用参数化查询啊,对不对。你记住这一点就行了。我们现在实现框架,你要实现参数化查询,这一块儿就写的要多一点。好,那只有这样其实还是不太合适,你还得有异常判断,对吧。所以呢我们这一块的话应该怎么做呢?是不是应该这么做try。然后呢如果说你这块儿,然后cursor是吧,你不用了,你就关了。因为下一个谁我还真不知道你用完了,你赶紧关掉,你就关掉,对吧?      然后这块儿应该写谁呀,是不是给个元组,self.name,self.age。这块是不是就我们发现,终于用到get方法了,就是通过这么一个应用,我们来解决这样一个问题的,对吧,那连接传进来倒也是可以,也没什么太大问题。所以呢,通过这样一个简单应用,我们就可以做到这点。这就是我们未来想怎么用。但是你要说这东西现在是框架肯定还是不行的。我们发现其实有很多事情还可以自动化完成。所以我们想办法最后要改造这些东西。

                    conn.commit()      # 所以这一块我们应该做什么,是不是conn的什么?commit是不是提交是一定要做的吧,对不对,这块提交一旦出现异常怎么办?

        except:      # 这块提交一旦出现异常怎么办?当然你打印异常,打印异常完了之后你或者说在打印之前,你第一件事应该完成的是conn点什么呀。应该是rollback对吧,

            conn.rollback()      # 打印异常完了之后你或者说在打印之前,你第一件事应该完成的是conn点什么呀。应该是rollback,对吧,你最应该做的就是这种事儿,那连接要不要关?,不关。因为这连接大家公用,就是在一个应用程序内,大家公用。如果一个应用程序你分好多子系统,每个子系统又比较繁忙,那么应该用连接池,每个子系统应该用一个连接,从连接池拿个连接,是这么玩。好,我们通过这种方式是不是就可以完成我们对数据库的操作啊,对吧。

# 连接数据库      # 所以连接数据库的语句是不是应该写的这个位置啊,然后下面。怎么办呢?下面是不是想办法应该把conn这东西给他传进来。连接是不是一个就够了,对你来说。搞那么多的连接干什么呀,对吧,我们一个连接就够了。你拿到这个连接之后,这块应该怎么做呢?
# 

s = Student(5,'jerry',20)
s.save()


import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        if fieldname is None: # 如果fieldname传递进来的是一个None
            self.fieldname = name # 那么字段自身的名字就用,name
        else:
            self.fieldname = fieldname  # 否者就是用fieldname传递进来的字段本身的名字
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

# 把表定义为类,把(id,name,age)字段定义为类属性,类属性所有实例公用
class Student: # Student的实例的dict的字典,还是空空荡荡,描述器的优先级高于Student,被描述器栏走了,不给里面写就写不进去了
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField('id','id','True',nullable=False,auto_increment=True) # 这里指定的名字,是用来做字典中的一个key
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = 'name'
        self.age = age 

    # 连接数据库
    def save(self,conn:pymysql.connections.Connection): 
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
        try:
            with  conn as cursor: # 得到一个conn的连接,给cursor
                with cursor: # 游标用完就关掉
                    cursor.execute(sql,( self.name,self.age)) # 传递sql语句,self.name,self.age就是用到了get方法返回来的值
                    conn.commit() #  提交

        except:
            conn.rollback() # 提交异常,回滚

Session类的实现

  每一个数据库操作都是在一个会话中完成,将cursor的操作封装到会话中

        如果我认为你现在在这个线程内,我认为你在一个线程内建立这套连接的话,然后你这个线程内去使用它的话,我认为你这都是在一个会话里,这样的话大家使用同一个会话时候,我甚至还可以让你们在同一个会话里维持同一个cursor就行了。我就不要不要频繁去关闭这个cursor了,我拿一个就够了。对吧,但是我刚才特别提到线程,一般会话的东西,它是不可以跨线程,不允许你跨线程,就这个意思。所以一跨线程就复杂了,就特别复杂。所以呢,我们想解决这样一个问题,能不能把它封装进去,我再给你提供一个session类。那如果提供一个session类,那save这块儿怎么办呢?这一块呢,我们到时候传进来了,就不是conn,也不是cursor了,我直接给你传一个session就行了。那这个section里面,它其实里面还有关联一个connection。那这样的话我们就再给他封装一次,是吧。
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Session():      # 假设我们在这儿写一个东西。class Session()。
    def __init__(self,conn:pymysql.connections.Connection):      # 你想我们既然Session在这儿,那就不用多说了。这地方是不是把conn:pymysql.connections.Connection这个东西给他传进来。
        self.conn = conn      # 我们至少应该把它传进来,

    # def close:      # 传进来之后呢,当然我们不能说在这直接def close,反正不用的时候,你再把这self.conn.close()给他关起来,你别做这事儿,什么地方做这种事儿,如果你的conn是从外部传入的,你就别管关了。如果你是在这建连接,是由你来做的,那就你这上面就应该提供关连接这个功能,现在这个连接都是从外部送进来的,那你就不要再管这事儿了。      那我们下面要实现什么呢?
    #     self.conn.close()

    def execute(self,query,*arge):      # 那我们下面要实现什么呢?我们这么做,我把这个save放这儿来,那也就说 def save(self,conn:pymysql.connections.Connection):这个pymysql.connections.Connection还用传吗,是不是不用传了,自个儿就已经有了,那这一块儿的话,是不是就是self.conn我们一点点改。我们可以通过这种方式来解决这样的问题,关键就是self.name,self.age这是谁了,对吧,那这东西的话究竟应该怎么去实现了,对吧,这是我们后面要解决的问题,但是不管怎么说,save这东西可以送进来,那这地方有几个实现,也就是说save本身是我Session的save,如果是我Session的save的话,那你想那这个东西就应该,这个对象是不是从外面送进来比较合适啊,所以这个地方呢我们暂且给它起个名字叫object,是吧,但是这个object是谁,我们现在还没想好,总之呢你得给我传一些参数进来,然后我在这边会给你做一些事情。但是这些逻辑应该怎么处理呢?      那么save到底放在哪边合适呢,这要看你以后想怎么写的问题了,其实我们最后想解决的问题是一个Session的save能不能搞定所有Student这种对象。这是我们想做这种事儿,能不能搞定这样的事情,对吧。那如果我搞不定这样的事情,你看比如说像self.name,self.age这些参数传进来好像也不合适,对吧,那我们目前能想到的解决方案,就说def save(self,object)这是我们的目标,我们想就说通过一个Session这样对象能搞定所有,到时候怎么办呢?      这边是不是有个session对象,然后我们就改成了一个Session.save(s),能够把一个S能解决掉的问题,就是把一个实例放进去能解决掉的问题,是吧?这是我们的目标,但是现在这么做好像有点不行,对吧?那现在目标搞不定怎么办?你先def save(self,object):把它object=None扔这儿呗,看以后能不能解决,是吧。所以传不传无所谓了。那现在我们看def save(self,conn:pymysql.connections.Connection):这一块,那么我们现在只能做到一步。
        # 连接数据库
        # cursor execute(sql)
        # sql = "instrt into student (name,age) values (%s,%s)"      # 你现在有Session了。这sql语句是不是你知道啊,这sql语句是不是从外面传进来的,这sql语句是不是跟每一个方法自己本身相关,所以这词汇语句呢,你还是要放class Student:      def save(self,object:None):      sql = "instrt into student (name,age) values (%s,%s)"在这儿。


        # type(object).__dict__.items()      # 那我们Session这块应该怎么用呢?我们看这Session块儿的save的话,相当于在干什么呀?相当于def save(self,object=None): 你现在如果是要传的object对象,这是未来要做,那你现在能做的事情不就是,给我个cursor给我用呗,现在能做的事情不多吧,是吧,因为你现在不知道对方送传过来是什么,就算对方现在传给你一个东西,它里面几个字段你清楚吗?现在是不是不清楚呀,那有人说那有没有办法解决?当然有办法解决,你比如说object对象都给你了,你还不知道object这对象,这个对象里面它是什么类型吗?你能知道它是什么类型,它的类属性你能拿到吧,肯定是能拿到的,对不对,我们通过比如这个object,你直接给他来个 type(object),你能知道这个是什么吧,你现在通过这样的一些类型,你拿到这个类型的话就能做一些事情,比如说你遍历它类型字典能遍历吧,你遍历类型字典这里面这么多,这class Student:       def __init__(self,id,name,age):也是它的类属性啊,类变量啊,有什么用啊?但是你有手段能把name这些东西过滤出来吗,现在比如说现在传进这个是student的实例,现在我想让你把student的实例它的类属性这些ID,name和age拿出来,你有办法拿到吗?拿不到吗。type(object).__dict__这是什么,对不对,这字典的话能不能?type(object).__dict__.items()可以吧。      这拿到是k和v吧,这拿到k和v有用吗,是不是里面有可能还是这class Student:       def __init__(self,id,name,age):      def save(self,object:None):东西啊,这不都是可调用的吗,但是这东西还是太粗了,你想要谁,你想要ID,name和age这几个,你没看到他的特征吗IntField,StringField?他什么特征呀,对,描述器嘛,他这是不是有描述器啊,你判断它这个k的这个所对应的K里面是存的什么呀?我们想k存的是id吧,类型里面是不是存的是它所对应的描述器对象啊,而这个对象又是所有Field基类的实现吧,实现的那个实例吧,你过滤不出来吗?你当然可以过滤出来。      所以这边的语句,其实你到最后真要把这个实现的话,你会发现这一块儿实际最后可以变得很通用,但是你可以写的很复杂,因为那我再换一张表呢,你怎么办,对不对。那这主键的问题我们还有好多要解决呢,对不对,那你这块的话,当然校验他已经做过了,就是我们下面主键这块,你是不是还有些问题要解决,主键到底instrt的时候带不带上去啊,如果它是自增的,你带上去的,你万一跟人家冲突怎么办?你不冲突,你跳着走,到这也不会出什么大事儿,是吧。还有一些细节问题你要处理掉,但是我们发现是不是可以搞定了,拿的到吧,对吧,拿到的。      那我们来看session究竟能做什么,session呢它这里面实现一个def execute(self,query,*args):      if self.cursor is None:      self.sursor = self.conn.cursor()      self.cursor.execute(query,args)方法对不对, 那这个方法呢,它依然是把这一块儿东西拿过来拿过来。那我们来看看session,对吧,这样一个东西,它能替我们做什么事儿呢?大家看,你得把self,query,*args你的查询语句扔给我,对吧,然后呢因为我自己这边self.conn = conn是不是有conn我能去处理这样一个cursor,我自己能拿到一个cursor,然后我用完我自己关,你不用关,那现在就说了,那这def save(self,object=None):传的最好让他object=None传什么过来啊。是不是直接去传一个语句过来,是吧,这是未来我们想解决这个问题,但是那个是最后想解决的,现在呢,万一他要参数化查询怎么办啊。是不是要写成def save(self,query,*arge):这样子是吧。      好,那我们下面这块的改造是不是也就差不多了?这self.name,self.age是传进来的东西是吧。 那后面是什么呢?是不是就得是个元组args啊,是吧。
        # k,v

        try:
            with self.conn as cursor:
                with cursor:
                    cursor.execute(query,args)      # 这self.name,self.age是传进来的东西是吧。 那后面是什么呢?是不是就得是个元组啊,是吧。好,我们通过这样一个改造,就把这样东西给它传进来了。那class Student:      def save(self,session:Session):这一块儿到底怎么玩儿呢。
                    self.conn.commit()
        except:
            self.conn.rollback()
# class Teacher:pass      # 你假如你还有其他表,你比如说这个地方我们再有个class,比如叫Tracher那接着这边呢,当然他自己是不是也要写一堆一堆字段,对吧,一堆字段写完之后,他自己可能有一个__init__,然后呢,它下面是不是也有save,他只要把自己sql = "instrt into student (name,age) values (%s,%s)"的sql语句是不是写好,写好之后然后这块session.save(sql,self.name ,self.age)传几个参数就完了,然后他也调谁呀?他是不是也掉这个session里面调这个def save(self,session:Session):对吧?然后把session中的这个save方法是不是调一调,对于他来讲是不是,里面这些东西写的一模一样,这是不是叫函数抽象的时候是不是把重复的东西抽象出来了,对吧,这就是重复东西,只不过这个函数抽象在了一个类里面去了,是吧,变成了类的方法了,对吧,所以呢,我们通过这样的方式就可以解决了。所以class Session():            def save(self,query,*arge):这个地方到底是不是save啊无所谓了,这是不是已经可以变成execute一个通用的方法了,对不对。对于class Student:      def save(self,session:Session):这个类里面是不是他的意思就是说我是要保存的,对他来说是不是有业务意义,对于class Session():      def execute(self,query,*arge):他来讲的话,他其实已经不管你的业务是什么,对他来说给我个sql语句,我就帮你执行,对吧,,然后我该提交是不是提交啊。哎,就这么个过程。好,这是我们说这一块儿的话,你如果后面实现,你就这么实现一下就行了。这就是我们抽象它的原因,因为你会发现你再写个类吧,里面的seve写法也是一样,里面又这么写一遍。我们不断在重复这条语句,那何必呢,你唯一不同的不就是sql = "instrt into student (name,age) values (%s,%s)"这东西吗,是吧,所以我们抽象出去。但你抽象出去,我们一看,抽象给谁呢?你抽象在student这个类里面也不合适啊,因为Teacher这里面是不是也要用啊?所以我们再把它往外提,但是我们发现用类包装还可以做一些其他事儿,这是我们要做的,但是我们说,你仅仅这么做好像还不太方便,为什么呢,我们说,我们喜欢用的时候说你能给我们提供一个上下文支持就太棒了,是吧。那上下文支持怎么实现呢?那肯定是用__enter__了,对吧?应该怎么写呢,首先你要想我要用你的__enter__方法,我是想干嘛呀,对吧?这才是我们想做的事情,首先先不要去太关心了,他这个return值,就是__enter__方法return值先不要关心他,首先是我们进去之后,它中间万一还要写几条语句呢,那几条语句要干嘛,是吧,最后你再想,为了方便,我是不是要给他那as子句后面要填的东西,是吧,了给他用,我再给他写个return值,是吧。这是你要考虑的问题。就说我们现在如果说,诶我想做这样的事情,我应该怎么做呢?__enter__我干什么呀?__exit__我干嘛?

class Student:
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

# 连接数据库
# 
    def save(self,session:Session):      # 那现在我们看def save(self,conn:pymysql.connections.Connection):这一块,那么我们现在只能做到一步,OK,现在是不是只能def save(self,session:Session):在做这一步。下面这一步是我们未来的目标,我们看看能不能实现这一步,现在能做的,还是说他们自己得提供一个save,只不过这时候的话是不是用法就不一样了,对吧。 用法不一样啊,那我们Session这块应该怎么用呢?      这一块儿到底怎么玩儿呢。那这块应该怎么做呢?# 你现在有Session了。这sql语句是不是你知道啊,这sql语句是不是从外面传进来的,这sql语句是不是跟每一个方法自己本身相关,所以这词汇语句呢,你还是要放在这儿。然后呢Session有了吧,
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s),
        session.execute(sql,self.name ,self.age)      # 然后呢Session有了吧,Session是不是点save,是不是把sql传进去,把sql传进去,有没有参数,是不是得有啊。这个参数是不在上面,是不是用这个可变参数收集,因为多长我不知道嘛。所以这块怎么写,self点第一个是叫name,self点age吧?哎你这么一传,是不是就差不多了,但是呢,我们发现cursor.execute(sql,args)这块要改一下啊,这块叫query了。好,你就把这样的事儿,是不是逻辑就挪到哪儿去了,是不是挪到class Session():      def save(self,query,*arge):这里面来了,就说你class Student:      def save(self,session:Session):这块唯一要解决什么问题啊,你要解决的每一个业务方法的问题啊,就说这个数据他拿来之后,你要凑什么sql语句,就是你想怎么去更新这张表,还是查询这张表,还是删除里面数据,对不对,这是你要做的事情,但是真正替你做操作的你就别管了。操作这事儿呢,移到class Session():      def save(self,query,*arge):这边来做,因为对于其他人来讲,是不是也是一样?你假如你还有其他表,你比如说这个地方我们再有个class。


s = Student(5,'jerry',20)
# Session.save(s)      # 这边是不是有个session对象,然后我们就改成了一个Session.save(s),能够把一个S能解决掉的问题,就是把一个实例放进去能解决掉的问题,是吧?这是我们的目标,但是现在这么做好像有点不行,对吧?
s.sace()

import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        if fieldname is None: # 如果fieldname传递进来的是一个None
            self.fieldname = name # 那么字段自身的名字就用,name
        else:
            self.fieldname = fieldname  # 否者就是用fieldname传递进来的字段本身的名字
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value):      # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session():
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        # 拿到cursor 掉execute(sql)方法里面送sql语句
       
            with  self.conn as cursor: # 得到一个conn的连接,给cursor
                with cursor: # 游标用完就关掉
                    cursor.execute(query,args) # query传递sql语句,*args传递属性,从外部传递sql语句,sql语句跟每个方法本身相关,self.name,self.age就是用到了get方法返回来的值
                    self.conn.commit() #  提交

        except:
            self.conn.rollback() # 提交异常,回滚


# 把表定义为类,把(id,name,age)字段定义为类属性,类属性所有实例公用
class Student: # Student的实例的dict的字典,还是空空荡荡,描述器的优先级高于Student,被描述器栏走了,不给里面写就写不进去了
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField('id','id','True',nullable=False,auto_increment=True) # 这里指定的名字,是用来做字典中的一个key
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = 'name'
        self.age = age 

    # 连接数据库
    def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
        Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去


Session类是封装好了,但是使用不方便,为其增加上下文文支持
# 上下文支持,我想做这样的事情,我应该怎么做呢?__enter__我干什么呀?__exit__我干嘛?这两个能帮助我们做什么事儿?
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Session():
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        # 连接数据库
        # cursor execute(sql)
        # sql = "instrt into student (name,age) values (%s,%s)"

        # type(object).__dict__.items()
        # k,v

        # cursor.execute(query,args)
        # self.conn.commit()

    def __enter__(self):      # 如果是上下文的话,也就说你在Session自己这儿上下文跟自己有关系吗?因为上下文是当自己被with,就是你自己的实例被with的时候才是吧。所以说你这个时候,你这个地方跟自己其实关系都不太大,那这个地方应该怎么做呢?也就是说当别人with你的时候,对吧,with你实例的时候才会调它。也就说假如说在class Student:      def save(self,session:Session):我们想把这个session带上,是吧,我把这个session带上我为了干什么呀?也就是说未来你想这么用,才能用上下文。假如说就算你这块儿class Student:      def save(self,session:Session):      with session as s:最后想这么做,就算你这块想这么做,那你想干什么,这是我们想要猜测的东西,对吧,也就说未来想这么做,那我们想在save中你想要这样做的话,你能做的事情是什么?我们发现能做的事情,这东西sql = "instrt into student (name,age) values (%s,%s)"是不是字符串有什么好说的,这东西是不是就不用放在with 中也行啊,那现在能做的事情就是把session.save(sql,self.name ,self.age)这东西想办法给挪进来呗,那既然把这东西要这么放过来之后的话,那你再,看我们刚才这种改造合适吗?我们现在改造能不能完成功能啊?按道理现在是可以完成功能的。因为你__enter__什么都没做嘛,那你这with session as s,我也没用S嘛,我照样用session它嘛,我这上下文这么写是不是也OK啊,也没什么问题嘛。但是我们现在想,哎你能不能再把try:      with self.conn as cursor:      with cursor:       cursor.execute(query,args)      self.conn.commit()      except:      self.conn.rollback()
这种东西是不是也给我想办法解决掉,是吧。那这种东西应该怎么解决呢?那我们知道这地方就得想办法,哎,这个地方你想办法是不是应该返回一个游标啊,对吧。返回一个游标,这游标从哪来呀。这self是不是维护一个conn呀,那你给他要一个不就完了吗,你把这个游标返回给我吗,我想要东西,那你在想,如果是这样子,是不是其他人是不是也在调用,其他的实例是不是也要调save方法,是不是也进来了,一进来之后是不是跟session要一个,要一个就要一个呗,同一个连接里面你要有一个,我要一个,各玩儿各的呗,各管各的数据嘛,各操作自己的数据嘛,谁也不互相干扰嘛。本来就应该这样吧,因为你知道如果有多个实例的话,student如果有一百行,那是不是各自有各自实例啊,各自有各自实例,什么时间人家改了这个实例,然后要调seve方法我们根本就不知道对吧,人家也没限制是不是多线程啊,所以各玩各的嘛,对吧,各自玩各自的吗,各自用各自这个cursor,这应该是比较合适的吧,对吧,所以你如果是这样子的话,with session as s:你可能要拿到一个with session as cursor,对吧?那如果拿到cursor以后怎么办呢?这是那我们session.execute(sql,self.name ,self.age)这块就变成什么了?是不是用cursor的方法也可以解决这样的问题。所以说就看这块你想怎么写,如果你要真的返回一个cursor给他,那也就是说你在这儿就可以拿到cursor,在with session as s:      with cursor:这个地方你就可以with cursor,你用完自己关去吧,对吧,我们知道既然有cursor的话,那这块是不是直接拿cursor的execute就行了,只不过人家后面要什么呀?是不是要一个元组列表或字典啊?元组列表和字典这是不是也可以解决啊?      with session as cursor:      with cursor:      cursor.execute(sql,self.name ,self.age)那如果是我们要写成这个样子的话,通过这种方式来的话,session能不能给我们解决这个问题,可以把,def __enter__(self):       return self.conn.cursor()他是不是相当于从里面拿一个个cursor给我们用啊,对不对,然后刻字给我们用,然后我们with session as cursor:      with cursor:这边是不是as cursor,然后with cursor,那with cursor的话,这个是由cursor对象他自己写的,就是cursor这个类里面是不是写的那个上下文啊来解决这个问题嘛。所以我把它执行一下,但是你要执行一下这东西用完了这东西,有人现在给我们管提交的事儿吗?那既然我def __enter__(self):这能进来,我是不是走的时候要解决这个问题啊,对吧。走的时候你可以就提供解决方案,对吧,,那最后的话我们别忘了,如果你要不用的话,其他人一般也用不了啊,怎么办。把你自己刚才拿到这个cursor是不是就想办法关了,那这个cursor我怎么知道我刚才自己是哪个啊,是不是我这么一弄的话,自己记不得呀,记不得你想这一个个实例应该怎么办呢?是不是首先def __exit__(self,exc_type,exc_val,exc_tb): 退出的时候我们if exc_type得判断一下异常。如果说这项有异常对吧,这叫有异常c f 点c o n 怎么办?
        pass

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:      # 是不是首先退出的时候我们if exc_type得判断一下异常
            self.conn.rollback()      # 如果有异常怎么办,让他回滚
        else:      # 然后呢这叫没有异常
            self.conn.commit()      # 没有异常,提交。这是我们要做的事情,cursor的事你自己管。那如果用这种方式来,这块try:      with self.conn as cursor:       with cursor:      cursor.execute(query,args)      elf.conn.commit()       except:      self.conn.rollback()还用还用这么做吗,入过你要调他的话,那这一块的话我们以后就不让你这么做了,只保留cursor.execute(query,args)      self.conn.commit()就是我们这一块的话,想办法就是说得让你拿到一些东西,我们想办法来做这样事情啊,这块我们一会再改造。      好,通过这样一些改变的话,我们发现可以在这个地方用这种方式来做。也就是说你class Student:      def save(self,session:Session):      with session as cursor:      with cursor:cursor.execute(sql,self.name ,self.age)
这块要不要做事务由你自己控制去吧。如果你想用事务我给你提供一个方便的上下文的方式,让你来做这个东西。但是这个cursor的关闭由你来控制,万一你class Student:      def save(self,session:Session):      with session as cursor:      with cursor:cursor.execute(sql,self.name ,self.age)这句没写完,是吧,你下面是不是又准备使用啊,那你cursor继续用。      我们来看怎么改造,我们这一回呢,改造的方式又发生了一些改变。我们用def __enter__(self):      self.cursor = self.conn.cursor()      return self这个cursor记录以后,然后我们给他return 返回的是这个self。      我们这么来做,然后让调用的方式又变了。def save(self,session:Session):       sql = "instrt into student (name,age) values (%s,%s)"       session.execute(sql,self.name ,self.age)因为我返回的是一个session吧,对吧,所以我session.execute(sql,self.name ,self.age)可以这么来调,这也是一种方式。      就是说我们现在这个变化def __enter__(self):      return self.conn.cursor()就看你return 什么东西,我们就可以拿到什么样的东西。我们用这个cursor来做。也是可以的,这几种方式其实都差不多。就是说你用什么样的上下文来写,那就决定着你以后想怎么来做这个事情。


class Student:
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        with session as cursor:
            with cursor:
                cursor.execute(sql,self.name ,self.age)

# 连接数据库
# 

s = Student(5,'jerry',20)
# Session.save(s)
s.sace()
# 我们来看怎么改造,我们这一回呢,改造的方式又发生了一些改变。我们用def __enter__(self):      self.cursor = self.conn.cursor()      return self这个cursor记录以后,然后我们给他return 返回的是这个self。      我们这么来做,然后让调用的方式又变了。def save(self,session:Session):       sql = "instrt into student (name,age) values (%s,%s)"       session.execute(sql,self.name ,self.age)因为我返回的是一个session吧,对吧,所以我又可以这么来调,这也是一种方式。      就是说我们现在这个变化def __enter__(self):      return self.conn.cursor()就看你return 什么东西,我们就可以拿到什么样的东西。我们用这个cursor来做。也是可以的,这几种方式其实都差不多。就是说你用什么样的上下文来写,那就决定着你以后想怎么来做这个事情。啊,想怎么来做def __enter__(self):      return self.conn.cursor()这个地方的话,刚才是def __enter__(self):      return这么来做的,是吧。class Session():      def __init__(self,conn:pymysql.connections.Connection):          self.conn = conn      加上  self.cursor=None如果是怎么写 就是有风险的方式。刚才我们那种实现有他的好处,是不是各拿各的各不相干。但是呢如果说我们要换成现在这种写法。如果说是这么def __enter__(self):      self.cursor = self.conn.cursor()       return self我们来分析一下,你的session 如果共享的话,是不是会带来问题啊。你自己一个线程又无所谓,我们认为是不是从上往下是不是依次执行,到问题好像不是太大。当然在最前面关还在最后面关了,这都无所谓啊。我们来看这种写法。def __exit__(self,exc_type,exc_val,exc_tb):      if exc_type:      self.conn.rollback()      else:      self.conn.commit()     self.cursor.close()我们来看这种写法。如果是这么做的话,会带来什么问题?你想这是同一个session啊,有没有问题。你只要想这同一个session,当你使用with的时候会带来什么现象?我如果 with session as cursor:这么进来会怎么样。如果我用with 进来之后,我拿到的是谁啊,def __enter__(self):      self.cursor = self.conn.cursor()       return self他return的self,我拿到的就是self本身,那这个self.cursor呢,有没有问题啊。只要你用一次with,他这种写法是不是每次都要覆盖一次cursor。你每进来一次,这个cursor肯定要被覆盖,因为你只有一个cursor吧。对吧,你又没做任何跟线程安全的东西啊。所以这是不是要限定这个session 了?限定这个session 应该用在什么样的范围之内。你要这么写的话,这个session。就必须是线程级的。必须是线程级别,也就是跨线程是绝对不可以的。因为你多线程一上的话,是不是在另一个线程里面执行的函数,马上就会,因为他是相当于是并发操作嘛,另一个线程里面是不是也待,如果你传的是同一个session对象的话,大家用同一个session对象的话。这个def __enter__(self):      self.cursor = self.conn.cursor()       return self是不是就用了这个全局session 对象以后,然后一旦with,这边又一个with过去了,是吧,出问题了。你一条with是不是进def __enter__(self):,他with是不是也进def __enter__(self):,这时候是同一个对象的__enter__嘛,进同一个对象__enter__方法以后,第一个进来之后self.cursor 等于 self.conn.cursor()一个新cursor 对象嘛,覆盖还倒无所谓,关键是你刚覆盖完,另边想干什么啊。想拿cursor 操作了,他一个对象被你覆盖掉了,彻底覆盖掉了,这是一种很大的安全性的问题的。关键是那家伙要给你关了呢。你刚好不容易,刚执行完,查了一期结果过来,结果这个cursor一下给你换了,你一查,还不是自己数据了。这也是个问题吧。一个是人家,结果一覆盖,大家用的是同一个cursor了,对吧,那原来cursor就丢失了,漂浮了,就没人要了,对不对,等着垃圾回收呢。结果呢,你现在,那个给你关掉了,你现在还正想用呢,人家还要关掉,有人说我都查一下数据也无所谓的嘛。关键你查完之后,你的数据呢,你拿什么找呢。我们说在cursor上是不是可以查数据啊,操作数据,查数据嘛,是不是操作cursor对象。结果你连cursor对象都变了。所以这东西线程不安全,如果这么写是线极其不安全的。所以如果你发现有一个类,他如果是这么实现,他代表什么意思啊,他就要明确的告诉你本session是线程不安全的。也就是说你必须把session用到线程内对吧,用到线程内去。但是呢有人说,那我要是把with想给你嵌套起来了。你要真是在同一个线程内拿with嵌套起来,我也没辙。因为这代码写的就是你在with session一遍,就拿到一个新cursor了。以前的呢对不起,记不住啊。他不是递归啊,他记不住了。他是直接覆盖他,不是递归,他记不住。所以这个地方是有很大风险的。如果要这么用,所以呢,这个session为什么这么写呢,他就是告诉你,在很多框架,就包括我们后面要用的框架,他session都是线程级别的。不允许你线程使用。原因在这儿,刚才我们那个写法说各拿各的cursor,哎,那也是一种实现,就看你想选择用什么方式来实现了。两种实现方式都可以。那我们这次选择的就是用现在这种方式来实现了,这两种方式都已经给你比较过了。他们的好坏,我们都是在分析他,就两种代码如果你在并发时使用的时候如何,因为你是控制不住别人用多线程的方式,使用你写的框架的,你无法控制这件事儿,对吧。所以呢,我们在用的时候就要注意这样的情况了,当然,这个class Session():      def execute(self,query,*arge):      if self.cursor is None:      self.cursor =self.conn.cursor()      self.cursor.execute(query,arge)地方呢我们做了一次保护。我们说如果这个cursor现在还没有的话,我建议你赶紧给你用一个,然后再做下面事情,所以说这个也是一种解决办法啊。那如果是这样子的话,那我们就要改动改动了。class Session():      def __init__(self,conn:pymysql.connections.Connection):      self.conn = conn这地方加一个self.cursor = None对吧,这地方class Session():      def execute(self,query,*arge):      if self.cursor is None:      self.cursor =self.conn.cursor()      self.cursor.execute(query,arge)要判断一下,如果你if self.cursor:这么写也没有问题,或者你写一个if self.cursor is None:如果时None你要怎么样是吧,然后下面再来执行,我们这块怎么写呢,是不是self.cursor 等于谁呢,self.conn. 我们是不是要拿到这个cursor()拿一个回来,然后这块self.cursor.execute(query,arge)就可以执行了,我们通过这种方式也可以,但这种方式是线程不安全的session是线程不安全了。我们前面那种方式只要谁用谁自己拿,谁用谁自己拿,各是各的,只要每调用这个函数一次,他都会重新,先去conn上去拿一个的。这种呢是线程安全的,线程安全的方式来拿的,所以问题不大。现在这种呢,大家在用的时候一定要注意风险。所以我们在这给大家加一句注释class Session():    # 这时线程不安全的,因为并发情况下肯定是不行的,跨线程就不用讲了。跨线程你如果一个进程里面就有一个线程问题也不大,对不对,关键是你跨进程,每个进程内又是多线程的。这就是我们说过多线程的问题,对不对。跨线程本身就是各是各的东西,就完全不相干了。通过这方式也是一种,就是怎么去分析这个问题。那如果我们把它改造成这种Session的话。那我们的使用,应该怎么写呢,是不是with      Session就可以了是吧,with session相当于干什么呢?相当于是不是把cursor 给你弄个新的吧。cursor有个新的了,那你下面不完不关,当然关不关这个事,你想怎么解决。但是里面的使用改怎么写了,这里没有self的事,这class Student:        def save(self,session:Session):      with session as cursor:      session.execute(sql,self.name ,self.age)
是不是就session。这个session执行一下就行了。所以说,最后这个代码就变成这个样子了。     但是这个代码说实在的不是很完善,这样写不是很完善,但是呢,我是要告诉你这是一个线程级的。这是一个线程级的session,因为大多数框架的实现都是线程级的session。他不是像我们前面第一种写法这个是可以跨线程,问题不大。所以呢我们在做这件事情跟我们前面分析写那个线程池一样的,就是你一定要做多线程上的考虑。当出现并发的时候有没有问题,一定要做这方面考虑,不考虑的话在多线中使用的时候就会产生各种异常。到时候很难分析。多线程一旦出问题非常难以分析。因为他都是可以拿到数据的,但是拿到数据以后你会发现,有的时候数据错乱,一旦发生并发数据就错乱了,这是为什么呢?对不对,你就要进行分析的。最后分析的时候非常麻烦,这也是一种解决问题的办法,这个呢我们就在模拟。好,那我们来看with session as cursor:      session.execute(sql,self.name ,self.age)这是不是也可以拿到最后def __exit__(self,exc_type,exc_val,exc_tb):      if exc_type:       self.conn.rollback()     else:         self.conn.commit()      self.cursor.close()是不是还替我们提交一下,对吧,我们这种方式来做,替我们来提交一下,看看到底要不要提交。其实你如果sql = "instrt into student (name,age) values (%s,%s)"这一块儿是个查询语句,这时候commit也没什么影响。但是呢,如果你这一块儿,如果最后写的是instrt语句,那么看这时候拿到一个cursor之后返回一个session。我们通过session执行,执行完之后,当我们离开with的时候,替我们是不是提交一把。所以如果说sql = "instrt into student (name,age) values (%s,%s)"这块是个select,你也就不用with了,如果说select语句,他这块class Session():      def execute(self,query,*arge):是不是要看一下if self.cursor is None:有没有cursor,没cursor是不是 self.cursor =self.conn.cursor()拿一个,拿一个是不是下面self.cursor.execute(query,arge)就执行就行了。这样也行,就是我们讲Session,但是这个有风险啊,一定要注意啊,这就决定着你日后用这个代码的时候,你应该控制他,让他在线程安全范围内去还要合理的使用。但是一般来讲的话,一般session 这种东西都不应该跨线程,全局使用,整个应用程序中全范围内去使用他,这种session 一般很少。因为我们需要去想象,如果是一个web ,我们现在有个web 开发环境,有个web 框架。web 框架拿每一个请求的时候,他应该是什么级别,对不对,你不能说来一个请求,来给你一个进程玩去,对吧,这肯定是不行的吧,一般来讲你至少 应该是多线程实现吧。那既然是多线程,每一个请求来,是不是他单独要操作一个数据库啊,对吧,它是个单独操作,他可能会操作好几个表,但每一次操作你应该用什么样东西来对应他呢?你说我来给你个全局Session,对吧,这样的话,那你就得考虑每次取的时候大家要不要一样。那怎么才能更加合适去使用呢?这个时候我给你一个Session,这个Session本身级别就是线程级别的,你去用就是了。在线程内我保证安全就可以了。有这样一种方式来处理,好了几种不同的方式,以及它的应用范围。给大家说清楚。好,我们以后的编程啊,多线程那就是不可避免。所以我们每次用一个,大家只要你想公用的东西,公共的数据,共享的数据,你就必须用多线程并发的思想去想一想这东西如果是一个多线程,如果再用的话,你正在用,我又在用,它会产生什么样的结果?一定要这么去想,对吧。      我们通过这样简单的实验。但是我们发现。不管怎么样,你提出一个Session类,就可以把有些操作呢,把这些公共的操作给它全部放到Session里面来了。你这边呢,更多的就是说,每一个class Student:这个我们称为什么呢,我们暂且可以叫它,叫实体类。为什么呢?它实实在在的放了一个数据嘛。这个实体类呢,刚好对应着我们表,对吧,这个类对应着我们的表,那这里面的实例呢,对应着记录,所以它是一个实实在在的实体,这个叫实体的。那这样实体类在使用的过程中就可以这么来做。那如果以后的话,其实呢我们更应该还是要实现一个什么东西呢?session点save然后把s传进去,session.save(s),如果能这么解决的话,其实他就必须用到一些非常手段,而这种非常手段,就是我们前面给大家介绍的元编程。刚才我们都已经提到了,怎么去在session这边能够把class Student:这里面的信息是不是给他提取出来,这实际上就是利用反射的机制嘛,在做嘛。我们再用这种方式来把它取出来。我们通过一个类型运行时。你一个对象是不是有类型啊,通过你的类型,我把你的属性全部给你拿出来了,对吧,这就是反射嘛,对吧,运行时类型信息嘛。我们先来操作这种东西嘛,,关键是我们现在刚才给大家提到的这一块儿要不要改造,Session我们是提取出来了,对吧,虽然我们发现他有各种各样的问题,我们只是在模拟,我又不是完全实现。我们现在想解决另一个问题了,什么问题呢?class Student():这东西。这东西的话,我们看看有没有共性啊。
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()


class Student:
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        
        with session as cursor:
            session.execute(sql,self.name ,self.age)


# 连接数据库
# 

s = Student(5,'jerry',20)
# Session.save(s)
s.sace()
import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        if fieldname is None: # 如果fieldname传递进来的是一个None
            self.fieldname = name # 那么字段自身的名字就用,name
        else:
            self.fieldname = fieldname  # 否者就是用fieldname传递进来的字段本身的名字
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是ionent类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session(): # 这是线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能
        self.cursor =  None # Session实例属性self.cursor默认等于None


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        if self.cursor is None: # 如果self.cursor属性是None
            self.cursor = self.conn.cursor() # 给self.cursor属性一个游标,这种方式是线程不安全的
        cursor.execute(query,args) # query传递sql语句,*args传递属性,从外部传递sql语句,sql语句跟每个方法本身相关,self.name,self.age就是用到了get方法返回来的值
        self.conn.commit() #  提交


    # 上下文,跟自己类的实例没有关系,当别人with,Session这个类的c实例的时候会调用__enter__,走的时候会调用__exit__,做完工作走的时候可以实现提交事务
    def __enter__(self):
        self.cursor = self.conn.cursor() # 返回一个游标,给with语句的他Session.execute(sql,self.name,self.age)调用
        return self # 如果用with进来,返回Session本身

    def __exit__(self,exc_type,exc_val,exc_tb):

        if exc_type: # 有没有异常
            self.conn.rollback() # 有异常执行回滚
        else:
            self.conn.comit # 没有异常进行提交
        self.cursor.close() # 关闭连接


# 把表定义为类,把(id,name,age)字段定义为类属性,类属性所有实例公用
class Student: # Student的实例的dict的字典,还是空空荡荡,描述器的优先级高于Student,被描述器栏走了,不给里面写就写不进去了
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField('id','id','True',nullable=False,auto_increment=True) # 这里指定的名字,是用来做字典中的一个key
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = 'name'
        self.age = age 

    # 连接数据库
    def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
        Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

        # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
        with session:
            session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去

实现元编程
#  class Student():这东西。这东西的话,我们看看有没有共性啊,class Product:是不是也要有id等于某个东西,然后呢是不是也带有name等于某个东西,也有id吧,商品有名字吧,商品有价格,那总之大家是不是发现,虽然你们有不同。但是你们有些东西是相同了。
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))
    pass

class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)


    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):      # 假设我想实现一个元类型。我用这个元类呢,主要想解决什么问题呢?元类怎么玩的呢?是不是起个名字是吧class ModelMeta,起个名字,然后呢,我们是不是从这个type来一把就行了。
    def __new__(cls,what,bases,attrs:dict):            # 然后呢我们在里面是不是还给他写了一个叫new的东西吧,new东西,然后我们说后面就三个东西是吧叫*args,也行,叫what也行,你到底是谁,对吧。然后这块是不是写个bases,再往后我们写dict,这会我们不叫dict,我们叫attrs就是说,所有的属性是吧。但是你要知道他是个字典类型,你要实在记不住,这写attrs:dict是不是就可以了。
        print(what,bases,attrs)
        return super().__new__(cls,what,bases,attrs)      # 然后我们以后想在这儿做一些事情了。我们知道这what,bases,attrs:dict三样东西实际上是我们最关心的,是吧。然后呢我们看看如果是这样的话,我们下面要做什么事。

class Model(metaclass=ModelMeta):      # 我们讲这是个元类,元类不是你用来继承的,对吧,元类不是这么样让你玩的。那我们下面能不能构造出class Model这样的东西,然后metaclass等于ModelMeta。
    pass      # 然后Student继承他



class Student():      # 我们可不可以这样做?至少目前我这么写还没有任何影响,对不对,还没有任何影响。但是你知道这个student,如果你把这id ,name ,age 三个往这一定义。这个student从上往下跑,当跑到student 的时候会发生什么事情啊?他开始就把id ,name ,age这些东西,它构造他的时候,是不是就把他所谓的这些属性,我们在哪能得到啊,是不是在class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):这就可以碰到了,这样class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):      print(what,bases,attrs)是不是可以拿到,当然你是不是应该把这东西new方法再给人家送回去是吧。就照原样给他送回去return super().__new__(cls,what,bases,attrs)
有什么送什么的。不然的话,我们Student这就成None了是吧,我们知道class Model(metaclass=ModelMeta):这家伙是不是会class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):动他,这class Student(Model):家伙,是不是也会class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):动他。那这样的话,这个class Model(metaclass=ModelMeta):类在构造的时候是不是会用class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):这个new方法,这个class Student(Model):类的构造时候是不是也会用class ModelMeta(type):      def __new__(cls,what,bases,attrs:dict):这个new方法。这个类对象只要产生是不是都用这个元类这个模板来做啊,元类就是他们的模板嘛,他俩都会调用。但是呢,这个class Model(metaclass=ModelMeta):我不关心。这个class Student(Model):我们关心。那我们怎么关心呢?大家想一想你怎么才能区分出他俩啊。至少你现在能区分出来吧,你想用的东西都有什么呀?是IntField,StringField吧,不是刚说过嘛。你要用的东西,你再遍历过程中,你有办法标识出他们吧?有办法没有?我们判断一下吧,你所有的一个属性里面究竟有谁嘛。
    id = IntField('id','id',True,nullable=False,auto_increment=True)
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        
        with session as cursor:
            session.execute(sql,self.name ,self.age)

# class Product:      # 写一个类class Product:
#     id = IntField('id','id',True,nullable=False,auto_increment=True)      # 是不是也要有id等于某个东西,
#     name = StringField(64,'name',nullable=False)      # 然后呢是不是也带有name等于某个东西,
#     price = FloatFileld      # 也有id吧,商品有名字吧,商品有价格,那总之大家是不是发现,虽然你们有不同。但是你们有些东西是相同了。什么东西相同,来我们写一下。id是不是IntField,你发现我是不是就把class Student:下的IntField('id','id',True,nullable=False,auto_increment=True)这东西抄过来就完了呀。对吧,那这name是不是也差不多,但是长度是不是有可能有变化。比如商品名64个放不住是吧,写80个,是吧。然后这价格,价格比如说我还要实现一个什么FloatFileld。但是我们就不实现了,因为大大同小异。总之我们发现这里面是不是就一个字要去写啊,写什么呀,是不是写字段名啊,是不写这些东西,一个个往下写,去写就行了。写完之后,我们再看他写完之后是个什么样子,写完之后你要做一件事,做什么事呢?__init__是吧

#     def __init__(self,????):      # 写完之后你要做一件事,做什么事呢?__init__是吧。然后这里面是不是写了好多啊,,好几个参数,写完了以后。然后下面是不是,照着上面的东西,是不是写个self.id又等于了一个什么东西是吧,。一句句话往下写,对吧,就这样往下写。你不觉得当你写三个的时候,你就觉得这东西是不是重复太多了。这就是我们下面要解决的问题啊,因为对我们来说表太多了,每一种表定义都是这么个结果。别这些东西定完之后,然后每一个是不是都有自己的save方法。对吧,然后这只是其一,然后你还得定义是不是一连串的每一个表都得有对应的操作。每一个表不但有类似的字段定义。有类似的__init__方法的定义,还有类似的所有操作表的定义。你不觉得他们太重复了嘛。那我们现在看看这东西究竟应该怎么改造呢?也就发现这class Product:东西和这class Student:东西。惊人的相似啊。所以呢我们发现如果要这么做的话,自己得想办法实现一个东西。那我们先来看看,假设我想实现一个元类型。我用这个元类呢,主要想解决什么问题呢?元类怎么玩的呢?是不是起个名字是吧class ModelMeta,
#         self.id = ?
#         self.name = ?
#         self.price = ?

#     def save():
#         pass


# 连接数据库
# 

# s = Student(5,'jerry',20)      # 这东西对我们构建元类,没有任何帮助,现给他注释掉。然后呢我们发现构建元类过程中,前面几个class StringField(Field):,class IntField(Field):,class Field:是不是爱执行不执行,你跟我这元类没关系,你们都是来自于type是吧,我现在要关注的是元类这块东西,这个class ModelMeta(type):元类和它影响的这class Model(metaclass=ModelMeta):,class Student(Model):两个类,这两个类之间还有派生关系,是吧,我们来看。当class Model(metaclass=ModelMeta):他执行的时候,走到这。class Model(metaclass=ModelMeta):这个类,是不是要被创建出来,他得用自己的那个模板,就他自己的元类是不是就这个ModelMeta。这个class Student继承至Model,继承他无所谓,但是他也得知道自己的元类是谁,他的元类也是ModelMeta是吧。那我们发现这print(what,bases,attrs)东西是不是至少应该打印两行啊。我们把她走一遍
# Session.save(s)
# s.sace() # 都不用了


# 打印结果(看着字典是空的吗?{'__module__': '__main__', '__qualname__': 'Student', 'id': <__main__.IntField object at 0xffff8d2275c0>, 'name': <__main__.StringField object at 0xffff8d2275f8>, 'age': <__main__.IntField object at 0xffff8d227fd0>, '__init__': <function Student.__init__ at 0xffff8cb73f28>, 'save': <function Student.save at 0xffff8cb76048>}不是吧,是不是你定义 def __init__(self,id,name,age):和 def save(self,session:Session):这两方法早就给你塞到字典里面去了,是吧,这事就是那类定义的时候,就必须得把这东西给知道了。因为你已经写死了嘛,对吧,他必须知道这些信息之后,他才能使用元类记录。你可以认为元类就是他模板,你可以用这个模板是不是来创造一个真正的一个类对象出来。原来我们说是type的实例,现在就变成你这个ModelMeta元类的这个实例了,对吧。元类的实例 这是所谓的类对象概念。那这个下面这个# s = Student(5,'jerry',20)这里 s用一个模板,是不是来创建实例了,谁的模板,你写的是Student哪个内容,是不是就拿class Student(Model):这个模板来创建这个实例,这个实例指的是由类产生出他自己的那个实例对象,指的是这个东西。但是呢,类class Student(Model):本身又是对象,他得用元类来构造自己。我们发现一句像s = Student(5,'jerry',20)这样语句都没出现,他依然会调动自己的元类,调了2次,一次谁调的呢?一次就是Model调的,一次就是Student他调的。结果你如果在这里面要操作某些东西,你是不是待把它俩区分开啊,那你当然能区分开,他最简单的方式是不是通过名字区分啊,但是我们真的不关心这个东西。我们关心谁呢?我们关心的其实是IntField,StringField这些东西。因为我们发现这东西是不是大量重复,对吧。所以呢,这个东西我们应该怎么改造呢? 字段的是不是还是自己的呀?自己的东西就不要放到父类,基类里去了。是不是把公用的往里面挪啊,什么东西是公用的?比如像class Student(Model):      def __init__(self,id,name,age):      def save(self,session:Session):这些东西是不是就算有点公用的意思吧。为什么说有公用的意思呢,因为id = IntField,name = StringField,age = IntField这东西你必须定义啊,每张表是不是对应的都不同啊,你比如放基类里面你怎么放啊?id 和name还差不多,那密码呢谁解决啊,价格谁管啊?对吧,你价格总是不一样的呀,就是有的价格他要整形,有的价格还是不时要浮点型啊。就算你几张表都字段一模一样,他的String要求有的是64,有的128,你怎么办?有的id要求自增,有的id不要自增,这是不是太个性了,太个性东西就不适合放在哪里啊,就不适合放在基类中了,基类不管这事d额,基类就是要把什么共通的东西是不是往里面放。你比如说是save方法,是不是挺相似的,想办法看看基类能不能替你做一些事情是吧。这些东西id = IntField,name = StringField,age = IntField以后看看能不能放进去,对不对。至少目前来看放进去不太合适。因为每个里面写的语句可能不太一样啊。现在挪还不方便,但是我们有这种想法是吧,要不然要基类干什么,就为了多继承一次吗,对吧,总得把一些公共东西,放他那儿去嘛,是吧,但是目前来看,我如果想把id = IntField,name = StringField,age = IntField这些字段,想拿到的话,是不是可以class Model(metaclass=ModelMeta):跳过他。因为我在class ModelMeta(type):      def __new__(cls,what,bases, attrs:dict):这儿是不是可以拿到啊。对不对,我把它是不是也已经给你print(what,bases,attrs)打印出来了呀,那我们下面先不管其他,我们在这儿先做一些事儿,做什么事儿呢?我们能不能先把这东西给他迭代一下。)
Model () {'__module__': '__main__', '__qualname__': 'Model'}
Student (<class '__main__.Model'>,) {'__module__': '__main__', '__qualname__': 'Student', 'id': <__main__.IntField object at 0xffff8d2275c0>, 'name': <__main__.StringField object at 0xffff8d2275f8>, 'age': <__main__.IntField object at 0xffff8d227fd0>, '__init__': <function Student.__init__ at 0xffff8cb73f28>, 'save': <function Student.save at 0xffff8cb76048>}
# 我把它是不是也已经给你print(what,bases,attrs)打印出来了呀,那我们下面先不管其他,我们在这儿先做一些事儿,做什么事儿呢?我们能不能先把这东西给他迭代一下
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases, attrs:dict):    
        # print(what,bases,attrs)      # 那print(what,bases,attrs)这个东西我就不打了。bases对你来说问题也不大。这个这个what呢我们看它打印做什么,他是不是就是Student 这个名字啊,这名字可能还真有点用。那既然是个名字的话,那把名字留下来是吧。def __new__(cls,what,bases, attrs:dict):改成def __new__(cls,name,bases, attrs:dict): 这里return super().__new__(cls,name,bases,attrs)也把what改成name。这个名字可能还真有用,为什么有用呢?你想想。你想想。如果你的类名儿刚好就是表名的话。我是不是又可以省点事了。表名不分大小写啊,我不管你student一个大写还是怎样。所以这个名字对我们来说还是有用的啊,有这个名字可以解决很多问题。但是呢谁说Student类名就一定是表名,万一我想个性一把怎么办呢? 这是不是又带来问题了,那这怎么办呢?你想啊,这个名字要对应一张表的表名了。那这张表对应的就是这个class,加个属性能不能解决呢?我允许你改表名,但是我给你个属性吧,咱俩约定一个属性可以吧,这个属性就记住这个表名总可以了吧,比如说,我能不能叫的就最简单的我叫tablename总可以吧。

        for k,v in attrs.items():      # 迭代谁呢,attrs.items()
            print(k,v)      # 来把这些东西打印一下。那print(what,bases,attrs)这个东西我就不打了。
            
        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):
    pass

class Student(Model):
    __tablename__ = 'stu'       # 你想啊,这个名字要对应一张表的表名了。那这张表对应的就是这个class,加个属性能不能解决呢?我允许你改表名,但是我给你个属性吧,咱俩约定一个属性可以吧,这个属性就记住这个表名总可以了吧,比如说,我能不能叫的就最简单的我叫tablename总可以吧。可以不可以啊,可以,这不就解决问题了吗,我放在外面合适吗,你在外面是全局的,但是我这个表名是不是只跟这个,我们这个类是不是映射一张表啊,这个类映射一张表,你放到外面去干什么呀?那这个属性是不是只跟这个类应该相关啊,你跑到那外头干什么呀,那有人说要不要放在self 里面去,你觉得要不要放到self里面去啊?这不一一对应啊,你放self里面搞那么多干嘛,不是重复了,是吧,就重复了。所以有人说这个在这,在加一个def __init__(self,id,name,age):      self.tablename对吧,等于什么东西,那不要tablename等于这个东西可不可以,其实是可以的。不就重复吗,这不是重复了n多遍了,对不对,何必呢,那既然是这样子的话,那我们就放class Student(Model):      tablename=这儿,放到这儿,我们再给它起个漂亮点儿的名字class Student(Model):      __tablename__ = 'stu'对吧,不就行了,这可以吧。是吧,既然有这个东西,那我们就再来瞅一眼啊,运行一下
    id = IntField('id','id',True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        
        with session as cursor:
            session.execute(sql,self.name ,self.age)



# 连接数据库
# 

# s = Student(5,'jerry',20)
# Session.save(s)
# s.sace()

# 运行结果
__module__ __main__
__qualname__ Model
__module__ __main__
__qualname__ Student
__tablename__ stu      # 我们自个儿建的东西是不是在这,可以吧,这是不是又算进去一个,然后id是不是在这儿。
id <IntField id>      # 然后id是不是在这儿,请问你想怎么过滤呢?来我们第一个,我们这么过滤。
name <StringField name>
age <IntField age>
__init__ <function Student.__init__ at 0xffff91793f28>
save <function Student.save at 0xffff91796048>
# 请问你想怎么过滤呢?来我们第一个,我们这么过滤。
import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases,attrs:dict):
        # print(what,bases,attrs)
        if attrs.get('__tablename__',None) is None:      # #  如果谁?是不是就是字典对我们来说是吧。这字典里面如果没有谁。我们怎么样,比如说你如果连tablename你都没定义,你是不是就没告诉我到底跟哪张表映射是吧。你没告诉我是映射,我是不是下面就想办法要给你填个值进去是吧,这就是我们要解决的问题。你比如说点get谁呢?我们就是让他去找一个东西,找谁呢?是不是这个__tablename__,这我们知道get方法的话,如果取不到是不是默认就应该None。你如果怕出错,你是不是可以 if attrs.get('__tablename__',None)这么写啊,如果这东西是None的话,什么意思,是不是就没写啊,
            attrs['__tablename__'] = name.lower()      # # 没写怎么办?attrs然后下面是不是这个__tablename__有人说你这__tablename__是不是可以写成常量,可以,但是只在这用,所以就不用写了。对吧,然后等于谁?是不是等于name传上去就完了嘛。这是相当于如果你自己不定义,对不起,我只能拿这个东西,谁呀,是不是就这东西,那人说这名字有大小写的问题,你要看不顺眼,这是个字符串,你要看实在不顺眼,那你就直接掉lower方法就行了,对吧,attrs['__tablename__'] = name.lower()这不就解决问了。是吧,第一个表名是不是解决了。你要不管我自己管,对吧。而且__tablename__这属性跟别人不太冲突啊,没事大胆用,你要怕冲突,你中间再加一杠__table_name__是吧,不冲突的啊。不冲突。好,我们通过这种方式是不是就可以解决这个问题了。好,那我们下来看for k,v in attrs.items():这东西,我们要这个干嘛呀?我们要这东西,你发现我们下面打印出来的看到的是不是还是一系列东西啊,对吧,那我们现在把这个把这个__tablename__ = 'stu'先给他禁掉,禁掉以后,我们看看下面这个执行以后的结果是不是符合我们的要求,对不对。运行在看,怎么__tablename__ model是model呀。对吧model给他加就加上呗,你又不用他,你以后创建实例是不是student啊,你有创建model的实例嘛,是不是不创建啊。所以无所谓Model他执行一遍,那直接一边就执行一边,我们从哪看啊,我们是不是看我们自己下面这些东西啊,跟他没关系啊,跟他没关系。所以Model这个,哎显示就显示了无所谓,不理他。我们来看下面__qualname__ Student      id <IntField id>      name <StringField name>      age <IntField age>      __init__ <function Student.__init__ at 0xffff91ab3f28>      save <function Student.save at 0xffff91ab6048>      __tablename__ student这些东西的话,你看student是不是被我们已经取到了,对不对。然后我们现在想玩的是呢,我们想玩的是这些id 和age和name这些东西。这怎么拿出来啊?if isinstance(v)是什么?是Field吧,如果是Field的话。这个时候我们在来打印一下,这就清静了,是吧

        for k,v in attrs.items():
            if isinstance(v,Field):      # 这怎么拿出来啊?if isinstance(v)是什么?是Field吧,如果是Field的话。这个时候我们在来打印一下,这就清静了,是吧,
                print(k,v)

        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):
    pass

class Student(Model):
    # __tablename__ = 'stu'
    id = IntField('id','id',True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        
        with session as cursor:
            session.execute(sql,self.name ,self.age)



# 连接数据库
# 

# s = Student(5,'jerry',20)
# Session.save(s)
# s.sace()

# 打印结果(干净吧,这才是我们想要的,所以这个时候model你还关心吗?根本就不用关心了。好了,你都能在元类里面把这东西,这你想要的这些字段都拿到了。你剩下来的事情还什么不能做啊,基本上想干什么干嘛,就看你自己的逻辑想怎么处理了。有这样一个东西我们就能解决很多问题,但是呢,我们为了以后用的方便,我们这边先解决一个什么问题呢?这样的问题,你想sql = "instrt into student (name,age) values (%s,%s)"你看name,age) values (%s,%s)。你现在这个地方,你还要写成这样的语句,是吧。那你有几个字段你是不知道啊,你有几个字段,那你在赋值的时候能解决什么样问题呢?那现在我们先解决第一个问题吧,字段问题能不能搞定啊。有几个字段是不是可以搞定啊,那当然有人说你在这儿,这class ModelMeta(type):什么时候处理的,这是不是在类定义的时候就开始做这事儿啊。你看我们是不是现在# s = Student(5,'jerry',20)      s.sace()一行有效代码还没写呢,对吧,这全是定义啊,那我们现在拿到了,只能说提前预备着呗。对不对,那我这块儿想做一个东西啊,我们先看。我们现在想写个东西叫mapping我给他一个字典,我现在做什么事呢?)
id <IntField id>
name <StringField name>
age <IntField age>
# 我们现在想写个东西叫mapping我给他一个字典,我现在做什么事呢?

import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases,attrs:dict):
        # print(what,bases,attrs)
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower()

        mapping = {}      # 写个东西叫mapping我给他一个字典
        for k,v in attrs.items():
            if not isinstance(v,Field):
                mapping[k] = v      # mapping这个字典呢,处理完了,我们下面要做的事情。
        
        attrs['__mapping__'] = mapping      #   attrs['__mapping__']等于mapping,你说写这个干嘛呀,为什么要这样写啊,写干什么呀。当然你发现,你以后真想用它的时候,你都塞到字典里面去了,你到时候是不是直接访问mapping属性啊,这__mapping__是不是相当于是个属性啊,我们塞进去了,你以后只要访问mapping属性,你是不是直接可以把谁拿到啊?是不是这所有的k 和v 你都可以拿到。对吧。你还需要再遍历一遍吗。我们这样把它拿到的目的是什么呢?拿到的目的就说,在你了类定义的时候,我都已经给你把你自己的字段,就是字段类型的那些属性是不是都给你专门放到一个字典中啊。这样你以后是不是自己就没有必要再翻一遍,再翻一遍,再翻一遍了。这是不是叫一种加速的办法?否则你看我们是不是需要遍历所有之后,我才能把字段类型是Field他的,是不是才能给你弄一遍啊,现在我特意给你建了__mapping__这样一个属性。这个属性对应的这个字典内部存的是谁呀,存的不就是id <IntField id>      name <StringField name>      age <IntField age>这东西吗?以后你在遍历这个里面,说我要找ib的,那这ib对象,是不是立马被你拿到了,id 对象是谁啊?虽然你是个描述器,但好歹你的id对应的就是一个对象啊。对吧,一个实例嘛,什么实例啊,你这id是不是对应个IntField实例啊,这个IntField实例你拿来操作不行吗,比如说,这个实例上对吧,这个实例不就是id = IntField('id','id',True,nullable=Field,auto_increment=True)他吗。对吧,那他不是调用方法,不就是self.id = id他呢。要么赋值,要么取值的吗,不就是这么玩的吗,对吧。所以你有这个实例对象是不是这样就可以做很多事。大家要知道你这虽然是个类属性,定义的时候做的,但是id = IntField('id','id',True,nullable=Field,auto_increment=True)这个实例要不要创建出来。这不但得把它创建实例,而且还待把它初始化了,这是一个真正存在的实力对象啊。这是个实例啊,虽然你这个东西是通过ModelMeta是吧,通过它的Meta去构造,但是IntField('id','id',True,nullable=Field,auto_increment=True)      StringField(64,'name',nullable=Field)这东西老早都得先生成的。这每一个是不是都待生成好啊,你才能付给id这个属性吧。对吧,也就是说IntField('id','id',True,nullable=Field,auto_increment=True)这些是一个个活生生的实例啊。对吧,他给你用的。所以这些实例他是不是有相应的内存地址啊,我们mapping[k] = v是不是相当于把他的引用就跟他的什么,跟他这个k属性名字是不是就关联好了。而且我if isinstance(v,Field):是不是过滤好了,我下面attrs['__mapping__'] = mapping再拿的时候还需要再过滤吗?我就不需要过滤了。哎,我们用这种方式就是为了解决这样一个问题啊。也就是说以后再来看。打印一下,大家看这啥都没有了嘛,因为你不打印了嘛,对吧,好,这个时候的话我们可以来做这样一个事情。

        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):
    pass

class Student(Model):
    # __tablename__ = 'stu'
    id = IntField('id','id',True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

    def save(self,session:Session):
        # 连接数据库
        # cursor execute(sql)
        sql = "instrt into student (name,age) values (%s,%s)"
        
        with session as cursor:
            session.execute(sql,self.name ,self.age)



# 连接数据库
# 

s = Student(5,'jerry',20)      # 因为你不打印了嘛,对吧,好,这个时候的话我们可以来做这样一个事情。把他注释删掉
# Session.save(s)
print(s.__dict__)      # Student是不是已经被我们改造好了,然后我们把这个s 的dict给他打印一下,我们看这里面有什么。
print(Student.__dict__)      # 然后我们打印一下Student的__dict__,来看一下分别都是什么


# 打印结果
{'id': 5, 'name': 'jerry', 'age': 20}      # 大家看你自己存是不是还存自己的,这个是不是我们那个set方法帮我们塞进去的,对不对啊,描述器的set方法嘛。那下面这些呢。
{'__module__': '__main__', 'id': <__main__.IntField object at 0xffff9eda6630>, 'name': <__main__.StringField object at 0xffff9eda6fd0>, 'age': <__main__.IntField object at 0xffff9eda0048>, '__init__': <function Student.__init__ at 0xffff9e6f6048>, 'save': <function Student.save at 0xffff9e6f60d0>, '__tablename__': 'student', '__mapping__': {'id': <__main__.IntField object at 0xffff9eda6630>, 'name': <__main__.StringField object at 0xffff9eda6fd0>, 'age': <__main__.IntField object at 0xffff9eda0048>}, '__doc__': None}      # 这是人家本来就是你定义的时候,他就他得保证这么塞是吧。关键是我们看看有没有我们塞进去__tablename__有吧,首先呢我们看这个__mapping__呢,是不是也塞进来了,而mapping 你看我们是不是 {'id': <__main__.IntField object at 0xffff9eda6630>, 'name': <__main__.StringField object at 0xffff9eda6fd0>, 'age': <__main__.IntField object at 0xffff9eda0048>}, '__doc__': None}把它打印出来了,这里面是什么东西啊,他为什么能打成这样子啊,这不就我自己定义的那个def __str__(self):      return "<{} {}>".format(self.__class__.__name__, self.name)这不是我自己定义的嘛。对吧,我们看,这'id': <__main__.IntField object at 0xffff9eda6630>      'name': <__main__.StringField object at 0xffff9eda6fd0>是不是对应着一个个实例啊,那这些实例以后就可以为你所用了。它的值是不是关联到他自己student实例,这个实例资源里去了,在哪儿呢?不就我上面打印这行{'id': 5, 'name': 'jerry', 'age': 20}嘛。这是他自己的实例嘛,对吧,各管各的啊,mapping呢,就是为了你后面用的方便。否则的话,你还得自己到这个属性字典里面是不是从头遍历一遍啊,这就是我们要给大家解决的问题。通过这样一个东西的话,我们就可以建立一些非常方便的事情。通过这样一个改造的话,我们就可以拿这样的东西。但是还有一件小东西啊,我们看,Model类的实现
import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        if fieldname is None: # 如果fieldname传递进来的是一个None
            self.fieldname = name # 那么字段自身的名字就用,name
        else:
            self.fieldname = fieldname  # 否者就是用fieldname传递进来的字段本身的名字
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            print('len(value)',len(value))
            print('self.length',self.length)
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session(): # 这是线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能
        self.cursor =  None # Session实例属性self.cursor默认等于None


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        if self.cursor is None: # 如果self.cursor属性是None
            self.cursor = self.conn.cursor() # 给self.cursor属性一个游标,这种方式是线程不安全的
        cursor.execute(query,args) # query传递sql语句,*args传递属性,从外部传递sql语句,sql语句跟每个方法本身相关,self.name,self.age就是用到了get方法返回来的值
        self.conn.commit() #  提交


    # 上下文,跟自己类的实例没有关系,当别人with,Session这个类的c实例的时候会调用__enter__,走的时候会调用__exit__,做完工作走的时候可以实现提交事务
    def __enter__(self):
        self.cursor = self.conn.cursor() # 返回一个游标,给with语句的他Session.execute(sql,self.name,self.age)调用
        return self # 如果用with进来,返回Session本身

    def __exit__(self,exc_type,exc_val,exc_tb):

        if exc_type: # 有没有异常
            self.conn.rollback() # 有异常执行回滚
        else:
            self.conn.commit# 没有异常进行提交
        self.cursor.close() # 关闭连接

# 实现元类(元编程),元类不是用来继承的
class ModelMete(type): # 继承至type类,继承自ModelMete的子类都会调用__new__方法
    def __new__(cls, name:str,bases,attrs:dict):
        print('1',name,'
','2',bases,'
','3',attrs)
        # 打印结果,结果有两个类调用,一次是Model类调用打印出来,一次Student类调用打印出来,操作的时候需要区分开
        # 1 Model 
        # 2 () 
        # 3 {'__module__': '__main__', '__qualname__': 'Model'}
        # 1 Student 
        # 2 (<class '__main__.Model'>,) 
        # 3 {'__module__': '__main__', '__qualname__': 'Student', 'id': <IntField id>, 'name': <StringField 64>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7f9ac20e4620>, 'save': <function Student.save at 0x7f9ac20e46a8>}
            # 此字典不是空的,Student类定义的方法早就塞到字典中了,Student类定义的时候里面的方法就必需要知道,他必需知道这些属性后才能使用元类,他的母版通过这些母版来创建一个真正的类对象出来,原来是type的实例,现在就是ModelMete元类的实例

        # 解决表名的问题
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower() # 如果__tablename__属性为None,就把name值赋值给他,lower()方法转换字符串中所有大写字符为小写解决类名大小写问题,

        mapping = {} # 给他一个字典
        for k,v in attrs.items(): # 遍历这个字典
            # print(k,'==',v) 
            if isinstance(v,Field): # 如果值中有Field的
                print(k,v) # 都打印出来,这是我们想要的
                mapping[k] = v # 塞到字典里
        attrs['__mapping__'] = mapping # 以后用的时候都塞到字典里去了,以后直接访问__mapping__属性所有的k和v都可以拿到

        return super().__new__(cls, name,bases,attrs) # 送回去,有什么送什么,不然继承至他的Student就成None了,super()调用父类下的__new__属性

class Model(metaclass=ModelMete): # 这里类继承至ModelMete,构造时会用ModelMete的__new__方法
    pass


class Student(Model): # 类本身是对象,需要用元类来构造自己,当执行到Student,就把构造他的这些属性,我们可以在元类型ModelMete中得到,这里类继承至Model,基类还是ModelMete,构造时会用ModelMete的__new__方法
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField('id','id','True',nullable=False,auto_increment=True) # 这里指定的名字,是用来做字典中的一个key
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # __tablename__ = 'stu' # 这个属性只跟这个类相关,这个类映射一张表,这个属性记住这个表名,
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = name
        self.age = age 

    # 连接数据库
    def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
        Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

        # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
        with session:
            session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去


s = Student(5,'jerry',20)
print('s.__dict__ =',s.__dict__)
print('Student.__dict__ =',Student.__dict__)
# 打印结果
# s.__dict__ = {'id': 5, 'name': 'jerry', 'age': 20} # 自己存还是存自己的描述器的__set__方法帮我们塞进去的
# Student.__dict__ = {'__module__': '__main__', 'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7fa153397048>, 'save': <function Student.save at 0x7fa1533970d0>, '__tablename__': 'student', '__mapping__': {'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>}, '__doc__': None} # 创建时Student类的id,name,age属性就要保证这样塞,我们用元类塞进去的'__tablename__': 'student'在,'__mapping__'也在里面看到的id': <IntField id>名字格式是由描述器的__str__方法定义的名字对应着一个个实例


Model类的实现

Sthdent这样的类,如果多键立几个,可以发现千篇一律,都是一个样子,
每一个这样的类,带定义一个名称对应不同的表,都需要先定义好类属性,再定义__init__初始化,而这些值正好是定义好的类的属性。
操作也是都一样,CRUD

设计
定义一个类Model,增加一个__table__类属性用来保存不同的表名

# 我们来构建一个model,我们把这些东西给他拿进来。这个是另一个思维方式,我先没有构建元类。我先呢,构建一个Model,我把什么东西放进了,我把save方法给他放进来了,这是不是save,就相当于从什么类别里面提前出来了,是不是从每一个实体类里面给它提取出来,然后放到自己的基类里面去了。为什么可以提取出来我们下面要做大量的工作了,我们做什么样的大量工作呢?我们看,我首先是不是得遍历啊,我一旦把它遍历之后呢,就带来了一些问题啊,什么问题呢?你这个k,v 我是拿到了,你想怎么处理啊,你得告诉我。k,v我处理的方式依然一样,我继续判断你是不是Field的。如果你是Field呢,我把你都记录下来。记录下来之后呢,然后我就想办法,要把save方法给你构造出来,怎么构造呢?insert into什么字段,然后括号里面写什么表,什么字段,然后值等于多少,你要给我写出来,所以说我们下面要把这个方法给你改造出来。是不是save这方法想办法,往上移。这个方法是不是挪到class Model(metaclass=ModelMeta):这来

import pymysql

class Field:
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        if fieldname is None:
            self.fieldname = name
        else:
            self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases,attrs:dict):
        # print(what,bases,attrs)
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower()

        mapping = {}
        for k,v in attrs.items():
            if isinstance(v,Field):
                mapping[k] = v
        
        attrs['__mapping__'] = mapping

        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):      # 好,基类如果能实现,那基类要做的事情,那就是说我得统一化了。比如说我认为你的save方法都是来解决insert语句的。对吧,你这里面就不能混杂,你混杂起来我实在很难处理啊。因为每一种insert,updata还有select,尤其select是吧写法更加五花八门了,那怎么办呢?不同的save的方法做不同的事情嘛,多建save的方法就完了嘛。那我们来看这个save 方法,我们怎么改造,大家发现 (name,age) 这东西是不是可以改造改造。。

    def save(self,session:Session=None): # insert info      # 是不是save这方法想办法,往上移。这个方法是不是挪到class Model(metaclass=ModelMeta):这来
        # 连接数据库
        # cursor execute(sql)
        names = []      # 我们看这块应该怎么写,那我们起个名字叫names等于这样一个东西。
        values = []      # 下面这块不是还应该有个values 啊。是吧,那我们来看。这第一个表名点是不是self.__tablename__,第二个names,第三个values。可以吗?names这东西出来肯定会会有问题的,怎么写呢?"".join可遍历谁啊,把name写进来,因为我们都用列表,他names = []      values = []一定会保障他上下是不是一一对齐的,不会错位吧,不会断续,不会错位。那values 这一块呢。这块发现有大的问题。 如果这块不用参数化的话,一定会出大问题。对吧,为什么会出大问题呢?是因为这一块的话你要这么写出大问题的原因在于什么类型加单引号,什么类型不加单引号,字符串是不是要加单引号啊,int要0吗,你是不是都得考虑啊,这个时候你发现解决起来非常繁琐。怎么办?参数化呗,参数化是不是很好是吧。他只要写什么?百分之s就行了。是不是在底下session.execute(sql,self.name ,self.age)给他传进来就行了,看见没有,用这种方式传进来就行了,而且人家传的时候是不是已经很明确告诉你,你给我元组也行,你给我列表也行。那是不是传的时候,这不就是values 嘛,session.execute(sql,values )是吧。那values怎么写?",".join(['%s']*len(names)),不就怎么写,你们缺几个补几个就是嘛,你又要可迭代,我是不是把它套到['%s']可迭代对象里,然后给他是3个,我就给他*len(names)乘一个3不就完了嘛。乘3以后大家知道这是一个大列表里面,三个字符串重复嘛,不就这意思吗,ok ,添进去完了嘛,然后['%s']*len(names)这里面是不是又被",".join遍历出来,遍历出来是不是以逗号分割,是不是%s,%s,%s就成他了嘛。等通过这种方式,你说sql = "instrt into student (name,age) values (%s,%s,%s)"这字符串我是不是拼接完了。拼接完了下来就是拼接完了这字符串怎么传参?这不session.execute(sql,self.name ,self.age)这么传参就完了吧。你sql = "instrt into student (name,age) values (%s,%s,%s)"这块是不是前面一直到这儿全拼完了,你是不是给了我好几个%s 那好几个%s我拿谁来替换啊,是不是要依次替换啊,必须有顺序。所以我们在这names = []      values = []是不是用列表啊。保证他们一一对齐啊。既然保证一一对齐,那我session.execute(sql,values )这个地方的干什么呀?我拿values = []这个是不是放到这之后,他第一个元素是不是跟这个%s去做啊。第二个元素是不是跟第二个%s啊,第三个元素是不是跟第三个%s对齐啊。人家session.execute(sql,values )这块是不要求是元组列表,还有字典都可以嘛,那我现在用个列表可不可以嘛,那这不就凑齐了嘛。凑齐这些东西就能跑了,就能用了。那你发现class Model(metaclass=ModelMeta):     def save(self,session:Session):       这个方法跟你是谁的有关系吗?写成这样你是student的,还是谁的有关系吗,是不是就没关系啊,那我们class Student(Model):     def save(self,session:Session): 这个还要不要出现,不要了。是不是这回我们终于把他合成功了,是吧,原来我们__tablename__是不是解决起来比较繁琐是吧。然后这for k,v in type(self).__dict__.items():一块是不是解决起来也有点问题啊。但是呢,我们通过这样一个改造,一个构造,我们发现原来这东西是可以解决我们的问题的,对吧。但是呢我好像没往里面塞东西,是吧,所以把他写完了,但是还有点问题是吧。对吧,我们都没给他塞进去,是吧,然后这个地方是不是就开始names.append谁呀?name是谁啊,那个是谁啊?
        for k,v in type(self).__dict__.items():      # 是不是这块应该写什么呀,是不是self啊,那你可以写成什么type(self)是吧,这样取他的这个类嘛,取他的这个类以后,这个类里面是不是有个字典啊,__dict__字典,这个字典是不是就可以items()呀。
            if isinstance(v,Field):      # 那我们这是不是又变成了什么?k,v在这个字典中的问题了,那还是一样嘛。如果说如果isinstance谁?v他是谁呀?,Field。如果你是这东西是不是才跟我拼字符串才有价值啊,那sql = "instrt into {} ({}) values ({})" 这东西是不是就往后放了。对吧,with session as cursor:这东西我们就先不让他执行了,是吧。先凉一边去。好,那我们来看。有这么个东西来处理之后,这个语句相当于是我们最后要用的东西,对吧。那我们现在能拿到谁?我们遍历的过程中,这些这"instrt into {}名字怎么拿呢?就是通过tablename可以拿啊,但是你想有这class ModelMeta(type):东西的帮助你tablename还能拿到吗?这东西是不是在定义时执行啊,这class Model(metaclass=ModelMeta):东西是不是实例执行吗,这实例执行这class ModelMeta(type):东西是不是早都已经给你填好了,对吧,这应该怎么做?是不是我们把self.tablename拿进来就行了,那"instrt into {} ({})这块({})怎么做呢?这是不是好几个字段的组合嘛,当然我们不用实现那么复杂,我们现在就是你写几个字段我拿几个字段。当然像我们这种如果是id组件,而是自增的话,你可以不写id。所以可以有些省略了啊。但是呢,我们要求在这块呢实现,就是实现的时候把id也加上。这样的话我们遍历起来也简单,凑一个字段也简单。我们怎么简单,怎么来,未来sql = "instrt into {} ({}) values ({})" 后面values ({})" 0是不是也是三个。那这块儿应该怎么写呢?是我想办法应该把这些东西要保存起来。因为"instrt into {} ({})这块({})这个地方是不是要给他一次性替换进去是吧。我们看这块应该怎么写,那我们起个名字叫names等于这样一个东西。

                names.append(k)      # 我们都没给他塞进去,是吧,然后这个地方是不是就开始names.append谁呀?name是谁啊,k
                values.append(self.__dict__[k])      # 然后values呢,首先这v是什么东西呀。看isinstance(v,Field)这个v是什么类型啊。那我们要v里面什么东西呢。v现在是个Field对吧,但是我们要是什么呀?他是一个Field类型的实例,是吧,这是没有问题的。那具体什么,就是子类IntField      StringField的区别而已嘛。但他也是父类的实例嘛,就是没问题的,是吧。好,我们通过这样的方法,当然我们得把values.append(self.__dict__[k])这个值给你塞进去了,塞进去这就拿到了。好,我们现在是不是相当于是把值拿到了是吧,这名字names.append(k)是不是都拿到,名字是不是就",".join(names)塞到这儿,是不是就ok 了。然后呢",".join(['%s']*len(names))      self.__tablename__,这个东西我们是不是有元类可以保证啊,所以没有问题的啊,这个呢",".join(names),我们就通过现在这种遍历方式,就拿到了id,name,age所有的字段给他sql = "instrt into {} ({})添到({})这儿来,我们这个括号就是来解决这个问题的。
        # sql = "instrt into student (name,age) values (%s,%s)"
        sql = "instrt into {} ({}) values ({})".format(self.__tablename__)            # 来,我们把这句给他改造改造,我们看到他怎么用,我们才知道这东西是不是可以写成这样。对吧,这个可以这么改嘛,对吧。那你这种东西的话,你说我用session.execute(sql,self.name ,self.age)这种方式传,这也没问题啊。这块 (%s,%s)其实说实在也能改,对不对。但是我们要支持参数化查询的话,那你可以不改,就是以后这么做,对吧,但是呢,我们这样做的话就发现稍微麻烦一点。好,那我们说这块儿应该怎么处理呢?那比如说我现在这一块的话,如果调save这个方法,看save说明我是什么方法,这是不是self实例在用的方法。如果实例在用的方法能跟我们class ModelMeta(type):这一块的方法调用方式一样吗?是不是不太一样啊,但不太一样,是不是发现解决的问题的办法是一样啊,怎么做?如果说我们先解决这样一个问题的话,是不是这块应该写什么呀,是不是self啊,那你可以写成什么type(self)是吧,这样取他的这个类嘛,取他的这个类以后,这个类里面是不是有个字典啊,__dict__字典,这个字典是不是就可以items()呀。那我们这是不是又变成了什么?k,v在这个字典中的问题了,那还是一样嘛。如果说如果isinstance谁?v他是谁呀?,Field。
        print(sql)      # 打印结果instrt into student (id,name,age) values (%s,%s,%s)
        print(values)      # 打印结果[5, 'jerry', 20]      能不能搞好,大家看[5, 'jerry', 20]这东西是不是未来session.execute(sql,values)就会values放这儿了,这东西instrt into student (id,name,age) values (%s,%s,%s)不就session.execute(sql,values)是放在sql这儿的吗,这下执行就完了嘛,都做完了。所以这个model 就是来解决什么问题?,解决我们移动save的问题,移动save的问题。但是它并没有解决。实际上我们现在考虑的像 id = IntField('id','id',True,nullable=Field,auto_increment=True)      name = StringField(64,'name',nullable=Field)      age = IntField('age')这些问题啊。这些问题,我们怎么处理呢?都class ModelMeta(type):      def __new__(cls,name,bases,attrs:dict):塞到这儿来了啊,各干各的事,这个model 应该对大家来说,现在看这应该没问题吧,这个没问题的。      当然还有一些小问题,比如说像id这地方有一些问题啊,这些问题的话解决留给框架解决吧。因为这样写的话,我们就写不完了,对吧,真要写一个orm框架的话,可不是说两百行代码能搞定的,对吧。这家伙真要写完的话,可五千行代码都搞不定了,对吧。所以我们只是通过它来学习SQLAlchemy,它究竟背后是怎么做的。否则这东西学不会的啊。通过这样的改造,我们就可以把它实现了。那元类的改造,我编写了一个元类,对吧,叫ModelMet,他作为元类,我们就要考虑这个元类,能替我们做什么事儿,对吧,我们现在发现这个原类其实可以给我们把那个new函数拿到。new方法拿到之后,我们就可以对所有的实体类,比如Student这样的实体类,我们就可以在他这个类在构造的过程中,我们就可以对他进行一个拦截进行拦截。主要是为什么呢,其实就是为了他字典里面添些东西,让他用的更加方便啊,我们添一些东西。

        
        # with session as cursor:
        #     session.execute(sql,self.name ,self.age)

class Student(Model):
    # __tablename__ = 'stu'
    id = IntField('id','id',True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

# 连接数据库
# 

s = Student(5,'jerry',20)
# Session.save(s)
print(s.__dict__)
print(Student.__dict__)
import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        if fieldname is None: # 如果fieldname传递进来的是一个None
            self.fieldname = name # 那么字段自身的名字就用,name
        else:
            self.fieldname = fieldname  # 否者就是用fieldname传递进来的字段本身的名字
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            print('len(value)',len(value))
            print('self.length',self.length)
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session(): # 这是线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能
        self.cursor =  None # Session实例属性self.cursor默认等于None


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        if self.cursor is None: # 如果self.cursor属性是None
            self.cursor = self.conn.cursor() # 给self.cursor属性一个游标,这种方式是线程不安全的
        cursor.execute(query,args) # query传递sql语句,*args传递属性,从外部传递sql语句,sql语句跟每个方法本身相关,self.name,self.age就是用到了get方法返回来的值
        self.conn.commit() #  提交


    # 上下文,跟自己类的实例没有关系,当别人with,Session这个类的c实例的时候会调用__enter__,走的时候会调用__exit__,做完工作走的时候可以实现提交事务
    def __enter__(self):
        self.cursor = self.conn.cursor() # 返回一个游标,给with语句的他Session.execute(sql,self.name,self.age)调用
        return self # 如果用with进来,返回Session本身

    def __exit__(self,exc_type,exc_val,exc_tb):

        if exc_type: # 有没有异常
            self.conn.rollback() # 有异常执行回滚
        else:
            self.conn.comit # 没有异常进行提交
        self.cursor.close() # 关闭连接

# 实现元类(元编程),元类不是用来继承的
class ModelMete(type): # 继承至type类,继承自ModelMete的子类都会调用__new__方法
    def __new__(cls, name:str,bases,attrs:dict):
        print('1',name,'
','2',bases,'
','3',attrs)
        # 打印结果,结果有两个类调用,一次是Model类调用打印出来,一次Student类调用打印出来,操作的时候需要区分开
        # 1 Model 
        # 2 () 
        # 3 {'__module__': '__main__', '__qualname__': 'Model'}
        # 1 Student 
        # 2 (<class '__main__.Model'>,) 
        # 3 {'__module__': '__main__', '__qualname__': 'Student', 'id': <IntField id>, 'name': <StringField 64>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7f9ac20e4620>, 'save': <function Student.save at 0x7f9ac20e46a8>}
            # 此字典不是空的,Student类定义的方法早就塞到字典中了,Student类定义的时候里面的方法就必需要知道,他必需知道这些属性后才能使用元类,他的母版通过这些母版来创建一个真正的类对象出来,原来是type的实例,现在就是ModelMete元类的实例

        # 解决表名的问题
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower() # 如果__tablename__属性为None,就把name值赋值给他,lower()方法转换字符串中所有大写字符为小写解决类名大小写问题,

        mapping = {} # 给他一个字典
        for k,v in attrs.items(): # 遍历这个字典
            # print(k,'==',v) 
            if isinstance(v,Field): # 如果值中有Field的
                print(k,v) # 都打印出来,这是我们想要的
                mapping[k] = v # 塞到字典里
        attrs['__mapping__'] = mapping # 以后用的时候都塞到字典里去了,以后直接访问__mapping__属性所有的k和v都可以拿到

        return super().__new__(cls, name,bases,attrs) # 送回去,有什么送什么,不然继承至他的Student就成None了,super()调用父类下的__new__属性

class Model(metaclass=ModelMete): # 这里类继承至ModelMete,构造时会用ModelMete的__new__方法
     # 连接数据库
    def save(self,Session:Session=None):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        names = []
        values = []
        for k,v in type(self).__dict__.items(): #  type(self)取他的类,类里面有字典__dict__,items() 方法以列表返回可遍历的(键, 值) 元组数组
            if isinstance(v,Field): # 如果你是这些东西,才跟我拼字符串才有价值
                names.append(k) # 列表填充的是字符id,name,age
                values.append(self.__dict__[k]) # 将取得的值赋值给values列表
                print('self.__dict__[k]',self.__dict__[k]) # 实例的字典
                # 打印结果
                # self.__dict__[k] 5
                # self.__dict__[k] jerry
                # self.__dict__[k] 20

                # print('self = ',self,'
','k = ',k,'
','v = ',v,'
')
                # # 打印结果 ,同一个self,
                # # self = <__main__.Student object at 0x7f629eb41588> k = id v = <IntField id>
                # # self = <__main__.Student object at 0x7f629eb41588> k = name v = <StringField name>
                # # self = <__main__.Student object at 0x7f629eb41588> k = age v = <IntField age>

                sql = "insert into Student (id,name,age) values (%s,%s,%s)" # 参数化查询,sql语句,对Student表插入数据,值为self.name,self.age
                sql = "insert into {} ({}) values ({})".format(
                    self.__tablename__, # 传递表名
                    ",".join(names),  # join():连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串
                    ",".join(['%s']*len(names)) #join遍历出来,然后以逗号分割,一个大列表里面三个字符串重复,需要一个可迭代对象['%s']套到一个可迭代对象中,在乘以三 这里有一个大问题,什么类型加单引号,字符串要加单引号吧,什么类型不加单引号,int类型不需要吧
                ) # 改造方法
            
        # Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

        # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
        # with session:
        # session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去


class Student(Model): # 类本身是对象,需要用元类来构造自己,当执行到Student,就把构造他的这些属性,我们可以在元类型ModelMete中得到,这里类继承至Model,基类还是ModelMete,构造时会用ModelMete的__new__方法
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField('id','id','True',nullable=False,auto_increment=True) # 这里指定的名字,是用来做字典中的一个key
    name = StringField(64,'name',nullable=False)
    age = IntField('age')

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # __tablename__ = 'stu' # 这个属性只跟这个类相关,这个类映射一张表,这个属性记住这个表名,
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = name
        self.age = age 

    # # 连接数据库
    # def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
    #     # 拿到cursor 掉execute(sql)方法里面送sql语句
    #     sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
    #     Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

    #     # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
    #     with session:
    #         session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去


s = Student(5,'jerry',20)
s.save()
# print('s.__dict__ =',s.__dict__)
# print('Student.__dict__ =',Student.__dict__)
# 打印结果
# s.__dict__ = {'id': 5, 'name': 'jerry', 'age': 20} # 自己存还是存自己的描述器的__set__方法帮我们塞进去的
# Student.__dict__ = {'__module__': '__main__', 'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7fa153397048>, 'save': <function Student.save at 0x7fa1533970d0>, '__tablename__': 'student', '__mapping__': {'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>}, '__doc__': None} # 创建时Student类的id,name,age属性就要保证这样塞,我们用元类塞进去的'__tablename__': 'student'在,'__mapping__'也在里面看到的id': <IntField id>名字格式是由描述器的__str__方法定义的名字对应着一个个实例


使用元类改造Model()

编写一个元类ModelMeta
以他作为元类的类,都可以获得一些类属性。
如果没有定义__table__,就自动加上这个属性,值为类名。
可以遍历类属性,找出定义的字段类,建立一张映射表mapping。
找出主键字段primarykey。

  那元类的改造,我编写了一个元类,对吧,叫ModelMet,他作为元类,我们就要考虑这个元类,能替我们做什么事儿,对吧,我们现在发现这个原类其实可以给我们把那个new函数拿到。new方法拿到之后,我们就可以对所有的实体类,比如Student这样的实体类,我们就可以在他这个类在构造的过程中,我们就可以对他进行一个拦截进行拦截。主要是为什么呢,其实就是为了他字典里面添些东西,让他用的更加方便啊,我们添一些东西。比如说像我们有这样一个需求,tablename你要是没做怎么办?对吧,我说我们是为了提供这样一些比较好的一些友好性,对吧,我们希望能给用户提供,比如tablename刚才我们一起解决掉了,对吧。那比如说我能不能以后直接拿到你,我就可以找到你里面的所有的k和,就是他那个,我们讲字段与他的那个Field实例之间是不是一个对应关系啊,这个时候我们说要借助一个字典,省的你以后再遍历了。这个时候我们生成一个字典叫mapping,对吧。那我能不能解决主键的问题。有的时候我们为了方便,你看有一张表,我待知道你的主键是谁吧。当然类似的东西还很多,你可以继续扩展。你比如说我想知道你的表中的外键是谁,是不是也可以提前给他塞进来啊,总之你可以把一些你认为有用的东西全部给他。通过这种在元类中,通过这种捕获,然后遍历的过程中就给他拿出来。拿出来之后呢,给他形成一个属性放在那儿。那比如说现在我们有个需求了,我们说,哎我能不能把这个主键字段给它找到。大家看一看主键字段你能不能搞定啊,现在在元类中。
# 大家看一看主键字段你能不能搞定啊,现在在元类中。
import pymysql

class Field:
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases,attrs:dict):
        # print(what,bases,attrs)
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower()

        mapping = {}
        primarykey = []      # # 我现在写一个东西叫primarykey等于None。遍历的过程中,我能不能找到主键啊,首先你必须是field吧,我才问你有没有pk这个属性嘛,对吧。我们定义东西叫pk嘛,对吧,那这个地方应该怎么写呢?是不是你该传的还是来继续传啊,对吧,然后你是不是要判断一下。
        for k,v in attrs.items():
            if isinstance(v,Field):
                mapping[k] = v
                if v.name is None:
                    v.name = k
                if v.column is None:      # 如果v.cloumn是None,什么意思呢?
                    v.column = v.name      # v.cloumn等于谁,是不是相当于我们在做class Field:      if fieldname is None:      self.fieldname = name      else:      self.fieldname = fieldname这句吧。那这句是什么意思呢?也就说这个以后其实是可以不提供了。因为这个东西的构造完全可以class Field      def __init__(self,name,通过这个name来给他解决,对不对。也就是说class Field:      if fieldname is None:      self.fieldname = name      else:      self.fieldname = fieldname这个参数其实都可以不传。那不传的话,那我们就说你在定义的时候,如果没有class Field:      if fieldname is None:      self.fieldname = name      else:      self.fieldname = fieldname这东西,如果你在定义的时候,什么时候在定义的时候啊,是不是class Student(Model):      id = IntField('id','id',True,nullable=Field,auto_increment=True)在这里定义的时候,你当你不给我第二个'id'这个参数的时候,相当于是fieldname是不是没有啊。你不fieldname没有,你想这是不是在class Student(Model):这定义的时候      id = IntField('id','id',True,nullable=Field,auto_increment=True),这句话就已经可以出来了是吧,已经出来了。那也就是说在这个时候,如果我要判断你这个v.cloumn要没有的话,我是不是要给你个名字啊,给个什么名字啊,v.cloumn = v.name就是他嘛。那等于v.name,那v.name要是没给呢。那v.name要是没给又是什么意思啊,那相当于是不是成IntField()这样了,是吧,连name都可以不给,name不给的话,那我们能用谁,是不是就用id,name,age,这些字段,用它了。那这个时候在元类这边是不是一把也构造了。好,那这个地方说v点name等于谁了,这不就是k吗。v.name = k,这样你这两个都可以省了,以后都可以省了。那我们发现最后这个构造就剩 age = IntField()这么一点,简单了。所以通过这样一个元类,可以帮助我们实现很多操作,这样的话我们在使用的时候,其实你真正要做的事情,就是class Student(Model):构建这东西了。这就是你要做的事情。就是这么一点了。这个所以这种事,你发现这其实是框架做的事情。那我们做什么,我们就写student,写实体类,这是我们要做事的。所以呢,如果是这样的话,我们这一块儿呢,我们class Field:      if fieldname is None:      self.fieldname = name      else:      self.fieldname = fieldname这一块甚至都不用特意的去写这样的东西了啊,我们就这么写self.fieldname = fieldname就行了,你愿意给就给,不愿意给就不给。就写成这样就可以了。这些操作让元类给你做。因为当这个元类在遍历,在处理class ModelMeta(type)这些东西的时候,请问class Field:      def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):这个东西的实例存不存在呀,Field实例存不存在啊。当我在这个class ModelMeta(type):里面,这个def __new__(cls,name,bases,attrs:dict):方法在执行的时候,请问Field实例存不存在。class Field:      def __init__(self,name,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):这是Field实例的属性吧,肯定存在嘛。因为你操作的是fieldname这些东西嘛,为什么存在?,是因为你在定义的时候这些class Student(Model)      id = IntField('id','id',True,nullable=Field,auto_increment=True)这些实例就已经存在了。我当然可以操作他,所以你在遍历v.name和v.column,人家的实例都存在,你当然可以去取这些值嘛,是吧。你把这些属性取出来。这属性到底怎么样?那你这地方,我们实际上也可以做一点,这地方你要写好一点,就是v点name是不是None v.name is None。你如果是None,因为人家有可能自己填了嘛。对吧,等于k,那如果v.name他等于None,他v.name就是就是k 如果v.name他不等于空,不等于空你就不要动了。人家就是想起个名字吧,对吧,如果column人家也重新定义了,那你也不要动了。因为用户想自定义了,是不是这个意思啊,对吧,那也就是说如果是class Field:       def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):这样子的话name=None是吧,,就这个意思当然你改了field相应的下面这块IntField,StringField是不是都得改。我们通过这样一个方式的话,会发现,如果class Student(Model):      id = IntField('id','id',True,nullable=Field,auto_increment=True)我'id','id',这个叫id 大家发现第三项是什么来着?第三天我们说他是不是pk的问题哈,pk 默认是什么?默认是不是pk=False。所以第三个这个所谓的True你能这么做吗?不行吧,对不对,哎,所以class Student(Model):这块你要id = IntField(True,nullable=Field,auto_increment=True)这么一写,是不是就对应上就出问题了。所以我们这块是不是得写个pk等于True,这样id = IntField(pk=True,nullable=Field,auto_increment=True)我们用关键字传参就可以了。也就是说你想定义成什么样,是不是就由你定义了,好,写成这个样子,用户用起来已经是非常非常的方便了啊,
                if v.pk:      # 是不是你该传的还是来继续传啊,对吧,然后你是不是要判断一下。如果是v点pk,说明它是true是吧,那什么意思啊,是不是他就是啊,就是我们是不是要这么做啊。primarykey等于谁。
                    primarykey.append(v)       # 就是我们是不是要这么做啊。primarykey等于谁?你把v给它也行,对吧,我们给一个主键对象也可以嘛,反正instrt into student (id,name,age) values (%s,%s,%s)这一列是不是就是这个v啊,所以定义的这个字段是不是就是个v啊,你给他就可以了,这么写对吗?思考啊要思考。因为我们现在写项目。呃,主键,只能一列吗?两列可不可以做主键啊,那你这个写法是什么意思呢?是不是后面的覆盖前面的,所以我们发现这样写是不是有点小问题啊,是吧。所以呢primarykey这一块应该怎么办呢?primarykey给它等于一个空列表。这块primarykey.append(v)这是不是把几个都放进去,这样的话写个v是不是为了方便,因为我写个v是告诉你什么啊,是告诉你我这个地方以后是一个什么的实例你想用它里面的一些属性,你随便拿啊,你写个true,false有什么意义啊,一点儿意义都没有,是吧。因为我用v是不是还可以把v点name拿出来,是吧,可以拿很多东西,比如说这个主键有没有缺省0值啊,啊,虽然主键可以自增,但是你主键要不自增呢,你要不填我能不能给缺省值,但是给缺省值有一个0问题。当你有主键,你还不让他自增,你还是个int,你自己添,你还不填,他给你添一个缺省值就行了。当他第二次填缺省值的时候,是不是已经有个缺省值在那儿了,一定会报主键冲突的,对吧,比如像类似的问题,总之他会有一些,我们在说field这个类上,它实际上定义了很多属性,这些属性我们应该怎么解决的问题啊。所以大家要思考这个问题啊。那我们通过这样的方式,是不是就可以把多个主键或者一个主键是不是加进来。那既然组件都被你遍历出来,当然写这个东西是告诉你还有其他很多你关心的东西是不是都可以把它从里面给遍历出来之后,把它悄悄的是吧,给他塞进去,塞哪儿去呢。
# 把它悄悄的是吧,给他塞进去,塞哪儿去呢,是不是这个attrs是吧primarykey,等于谁呢,是不是就把primarykey这个东西给他就行了,是吧,那你如果你自己现在还需要说,哎,我还有其他关心的一些东西,你往里面填就行了嘛。对吧,所以我们给大家写几个特别的地方,你看通过__tablename__这个我是告诉你,原来我们可以这样来构造表名,对吧,我们可以自己添加属性。通过class Model(metaclass=ModelMeta):      for k,v in type(self).__dict__.items():这个呢我们是告诉你,原来描述器我们真的要操作的话,还可以这么操作,对吧,而且class ModelMeta(type):      def __new__(cls,name,bases,attrs:dict):      for k,v in attrs.items():这个东西的执行时机和class Model(metaclass=ModelMeta):      def save(self,session:Session=None):      for k,v in type(self).__dict__.items():这个东西执行时机是完全不同的,所以两个代码很相似,但是执行的时间是完全不同的。这两个你要搞清楚啊,你不要觉得代码很相似啊,就完了,class ModelMeta(type):      def __new__(cls,name,bases,attrs:dict):      for k,v in attrs.items():这个什么时候执行啊,是你的类在构建的时候,他自己在做了。那class Model(metaclass=ModelMeta):      def save(self,session:Session=None):      for k,v in type(self).__dict__.items():这个什么时候执行啊,实体类的实例,而不是model的,model其实我都不让你实体化的,model只是用来做基类的,做公共的一些资源,是不是在这儿定义啊,对吧。所以说呢,当你model的子类的实例,他来调用save方法的时候是不是for k,v in type(self).__dict__.items():做这事儿,对吧,就是两个时机啊,完全不同的时机。好,那class ModelMeta(type):      def __new__(cls,name,bases,attrs:dict):      for k,v in attrs.items():这个地方的话,我们是不是相当于类构建的时候,我通过你构建的信息我就可以做那么多事儿。好,构建信息能做这么多事,其实我们还可以做很多其他的事,比如说我都已经拿到了你所有的字段了,你觉得__init__方法,你只用class Student(Model):      __init__方法,我给你构建不出来吗,def __init__(self,id,name,age):这id,name,age不就是那些名字门吗,对吧,self.id这不就是它哪些的self点这玩意吗,完全可以给他构造出来,基本上就没有什么方法构造不出来了。就是说你有个性化的地方,你要改改。你比如说你像save这里面,你这sql = "instrt into {} ({}) values ({})".format语句太个性化了。每一个你比如说每一个都不太一样,对吧,但是呢,对你来说,当你insert一张表的时候,你要么insert into什么表,什么字段,然后values什么值,对吧,你要的这么insert,对吧,你要么就说,哎我换一种方式0,但是不管怎么样,这些表大同小异,你一抽象,其实都是这个模板,对于update语句也大同小异,也是个模板。只不过我们只能抽象到这层了。如果我们再能给他努一把力的话,实际上连这样的事情,这些最基本的操作,如果你都是这样子,你都不用写了。我们实际上都是,像这些东西全部都构造完了。其实class Model(metaclass=ModelMeta):这东西已经是什么?已经都是class Student(Model):实体类的模板了,那实体类干什么,实体内就做id = IntField('id','id',True,nullable=Field,auto_increment=True)这个性化就行了。像def __init__(self,id,name,age):这些都是非个性化,因为大家都是一样的,对吧,一起创造就可以了。
        
        attrs['__mapping__'] = mapping

        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):
    def save(self,session:Session=None):
        # 连接数据库
        # cursor execute(sql)
        names = []
        values = []
        for k,v in type(self).__dict__.items():
            if isinstance(v,Field):
                names.append(k)
                values.append(self.__dict__[k])
        # sql = "instrt into student (id,name,age) values (%s,%s,%s)"
        sql = "instrt into {} ({}) values ({})".format(
            self.__tablename__,
            ",".join(names),
            ",".join(['%s']*len(names))
        )
        print(sql)
        print(values)

        # with session as cursor:
        #     session.execute(sql,self.name ,self.age)

class Student(Model):
    # __tablename__ = 'stu'
    id = IntField(pk=True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

# 连接数据库
# 

s = Student(5,'jerry',20)
s.save()
# Session.save(s)
# print(s.__dict__)
# print(Student.__dict__)


import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        self.fieldname = fieldname  
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            print('len(value)',len(value))
            print('self.length',self.length)
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session(): # 这是线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能
        self.cursor =  None # Session实例属性self.cursor默认等于None


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        if self.cursor is None: # 如果self.cursor属性是None
            self.cursor = self.conn.cursor() # 给self.cursor属性一个游标,这种方式是线程不安全的
        self.conn.commit() #  提交


    # 上下文,跟自己类的实例没有关系,当别人with,Session这个类的c实例的时候会调用__enter__,走的时候会调用__exit__,做完工作走的时候可以实现提交事务
    def __enter__(self):
        self.cursor = self.conn.cursor() # 返回一个游标,给with语句的他Session.execute(sql,self.name,self.age)调用
        return self # 如果用with进来,返回Session本身

    def __exit__(self,exc_type,exc_val,exc_tb):

        if exc_type: # 有没有异常
            self.conn.rollback() # 有异常执行回滚
        else:
            self.conn.comit # 没有异常进行提交
        self.cursor.close() # 关闭连接

# 实现元类(元编程),元类不是用来继承的,ModelMete类的执行时机和Model类的执行时机是完全不同的,ModelMete类是在类构建时执行,Model类的子类的实例,来调用方法的时候,比如save方法时执行。
class ModelMete(type): #元类可以拿到__new__函数,拿到以后就可以对所有的实体类在构造的过程中进行拦截对他字典里填一些东西,让他用的更加方便比如__tablename__,在比如拿到你可以找到里面的所有k和v字段和Field实例之间对应的关系要借助一个字典省的以后遍历类属性我们生成了一个字典叫做mapping,还要找出主键是谁,总之可以把一些你认为有用的东西全部通过在元类通过这种捕获遍历的过程中把他拿过来给他形成一个属性放在哪。 类构建的时候执行,继承至type类,继承自ModelMete的子类都会调用__new__方法
    def __new__(cls, name:str,bases,attrs:dict):
        print('1',name,'
','2',bases,'
','3',attrs)
        # 打印结果,结果有两个类调用,一次是Model类调用打印出来,一次Student类调用打印出来,操作的时候需要区分开
        # 1 Model 
        # 2 () 
        # 3 {'__module__': '__main__', '__qualname__': 'Model'}
        # 1 Student 
        # 2 (<class '__main__.Model'>,) 
        # 3 {'__module__': '__main__', '__qualname__': 'Student', 'id': <IntField id>, 'name': <StringField 64>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7f9ac20e4620>, 'save': <function Student.save at 0x7f9ac20e46a8>}
            # 此字典不是空的,Student类定义的方法早就塞到字典中了,Student类定义的时候里面的方法就必需要知道,他必需知道这些属性后才能使用元类,他的母版通过这些母版来创建一个真正的类对象出来,原来是type的实例,现在就是ModelMete元类的实例

        # 解决表名的问题
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower() # 如果__tablename__属性为None,就把name值赋值给他,lower()方法转换字符串中所有大写字符为小写解决类名大小写问题,

        mapping = {} # 给他一个字典,找到里面的所有k和v字段和Field实例之间对应的关系要借助一个字典省的以后遍历类属性我们生成了一个字典叫做mapping
        primarykey = [] # 生成列表,防止原来的值被覆盖掉,想要找到主键
        for k,v in attrs.items(): # 遍历这个字典,写这个东西是告诉你,还有其他你关系的东西,都可以把他从里面遍历出来以后,可以把他塞进attrs字典里
            # print(k,'==',v) 
            if isinstance(v,Field): # ,主键是否可以遍历出来,首先要是Field才问你有没有pk这个属性。如果值中有Field的
                print(k,v) # 都打印出来,这是我们想要的
                mapping[k] = v # 塞到字典里v
                if v.name is None: # 如果v.name也没给呢
                    v.name  = k # 给v.name赋值为k
                if v.column is None: # 相当于Field类判断if fieldname is None属性是否为None,fieldname的构造完全可以通过Field类的提供的name来解决,当Student类的id属性不提供字段名就行了,也可以不写,默认值是None
                    v.column = v.name # 判断如果fieldname没有给名字,就给一个v.name,如果v.name也没给呢
                if v.pk: # 如果遍历出来pk主键属性,条件为真
                    primarykey.append(v) # 写个v是告诉你这个地方以后是一个什么的实例,想用里面的一些属性随便拿,比如可以拿到v.name,给primarykey把多个主键或一个主键对象加进来
        attrs['__mapping__'] = mapping # 以后用的时候都塞到字典里去了,以后直接访问__mapping__属性所有的k和v都可以拿到
        attrs['primarykey'] = primarykey # 把遍历出来的pk主键对象,塞进attrs字典里
        return super().__new__(cls, name,bases,attrs) # 送回去,有什么送什么,不然继承至他的Student就成None了,super()调用父类下的__new__属性

class Model(metaclass=ModelMete): pass # 代码块挪走但是这个基类要有。当ModelMete的子类的实例,来调用save方法的时候运行,这里类继承至ModelMete,构造时会用ModelMete的__new__方法

# 实体类,Student实体类要做个性化的就行了,__init__方法非个性化,应为大家都是一样的
class Student(Model): # 类本身是对象,需要用元类来构造自己,当执行到Student,就把构造他的这些属性,我们可以在元类型ModelMete中得到,这里类继承至Model,基类还是ModelMete,构造时会用ModelMete的__new__方法
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField(pk=True,nullable=False,auto_increment=True) # 如果不给name,fieldname,pk需要使用关键字传参
    name = StringField(64,'name',nullable=False)
    age = IntField()

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # __tablename__ = 'stu' # 这个属性只跟这个类相关,这个类映射一张表,这个属性记住这个表名,
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = name
        self.age = age 

    # # 连接数据库
    # def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
    #     # 拿到cursor 掉execute(sql)方法里面送sql语句
    #     sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
    #     Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

    #     # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
    #     with session:
    #         session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去
class Engine:
    def __init__(self,*args,**kwargs):
        self.conn = pymysql.connect(*args,**kwargs)
  # 连接数据库
    def save(self,instance:Student):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        names = []
        values = []
        for k,v in instance.__mapping__.items(): #  type(self)取他的类,类里面有字典__dict__,items() 方法以列表返回可遍历的(键, 值) 元组数组
            if isinstance(v,Field): # 如果你是这些东西,才跟我拼字符串才有价值
                names.append(k) # 列表填充的是字符id,name,age
                values.append(instance.__dict__[k]) # 将取得的值赋值给values列表
                print('self.__dict__[k]',instance.__dict__[k]) # 实例的字典
                # 打印结果
                # self.__dict__[k] 5
                # self.__dict__[k] jerry
                # self.__dict__[k] 20

                # print('self = ',self,'
','k = ',k,'
','v = ',v,'
')
                # # 打印结果 ,同一个self,
                # # self = <__main__.Student object at 0x7f629eb41588> k = id v = <IntField id>
                # # self = <__main__.Student object at 0x7f629eb41588> k = name v = <StringField name>
                # # self = <__main__.Student object at 0x7f629eb41588> k = age v = <IntField age>

                sql = "insert into Student (id,name,age) values (%s,%s,%s)" # 参数化查询,sql语句,对Student表插入数据,值为self.name,self.age
                sql = "insert into {} ({}) values ({})".format(
                    instance.__tablename__, # 传递表名
                    ",".join(names),  # join():连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串
                    ",".join(['%s']*len(names)) #join遍历出来,然后以逗号分割,一个大列表里面三个字符串重复,需要一个可迭代对象['%s']套到一个可迭代对象中,在乘以三 这里有一个大问题,什么类型加单引号,字符串要加单引号吧,什么类型不加单引号,int类型不需要吧
                ) # 改造方法
            
        # Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

        # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
        # with session:
        # session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去



s = Student(5,'jerry',20)
enging = Enginge('192.168.161.203','root','root','test')
enging.save(s)
# print('s.__dict__ =',s.__dict__)
# print('Student.__dict__ =',Student.__dict__)
# 打印结果
# s.__dict__ = {'id': 5, 'name': 'jerry', 'age': 20} # 自己存还是存自己的描述器的__set__方法帮我们塞进去的
# Student.__dict__ = {'__module__': '__main__', 'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7fa153397048>, 'save': <function Student.save at 0x7fa1533970d0>, '__tablename__': 'student', '__mapping__': {'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>}, '__doc__': None} # 创建时Student类的id,name,age属性就要保证这样塞,我们用元类塞进去的'__tablename__': 'student'在,'__mapping__'也在里面看到的id': <IntField id>名字格式是由描述器的__str__方法定义的名字对应着一个个实例



引擎类
  实体类没有提供数据库连接,当然也不应该提供,实例类就应该只完成表和类的映射。

  提供一个数据库的包装类
  1,负责数据库连接
  2,负责CRUD操作,取代实体类的CRUD方法,create(新建)、read(读取)、update(更新)和delete(删除),简称 CRUD。

  下面继续改造我们这个框架,我们虽然改造了不少,但是呢还是会有一些小的问题,这些小问题呢,我们以后想办法再慢慢解决他。我们先把主体框架全部搭好,,那我们要说引擎类了。那引擎为什么东西呢?我们说实体类中我们不需要提供数据库的连接,实体类就做映射去,他其他事都不要关心。对吧,他就是说跟我这个表的数据相关的属性你写好,跟我表相关数据操作你做好。但是我们发现最后我们通过基类的构造,我们连实体类中连什么save方法都不要了。因为发现这东西也通用啊,我们发现原来这里面有这么多东西可以通用。对吧,所以说才出来框架,因为太多东西重复了就需要框架来帮助我们全部自动化实现,那就是更合适了。那实体类不需要提供数据库连接,你也不应该提供。因为实体类只管数据,只关心数据跟其他的你就不要再纠缠在一起,说写了一块儿,不要。我们从封装角度来看也应该这么做。谁的事儿谁做,不是他的事,别给他做,对吧。连接这事儿我们说连接这东西是不是做全局共享啊,对吧。所以呢,既然是这么一种思考,那实例这个类也就是说实体类就应该只完成表和类之间关系的映射,这就是一个ORM对象关系映射。我们现在想这么做,我们准备提供一个数据库的包装。那这个数据库包装类干什么事呢?他的作用是负责数据库连接。负责crud的操作来取代实体类self.id。啊,这也可以取代啊,那我们来看怎么来做。

  我们现在独立出来一个引擎.我们说增删改查是不是也想让他解决啊,数据库连接增删改查都想扔给他。你看这怎么写,我们把连接交给他来处理,对吧。那你想这个连接是不是就目前我们的实现来讲,我们的设计是一个用这一个链接就够了,是吧。那也就是说到时候你这个engine,应该一个就够了,也就是说它是一个实例就够了。这一个实例就够了,就叫单例。但是我们现在没有做任何单例的处理,这得靠你自己去实现。也就是说你如果非得自己再实现一个engine实例出来,也就是说两个engine对吧,可不可以呢?就目前来看也不会出什么大问题。但我们认为就目前以我们使用规模来讲。一个连接就够你用了。那我们这一块的话就是说engine 你如果要做单例保护的话,那你就要处理一些事情了。也就是说如果你现在要对一个init,是不是实体化最后就要调init嘛,你就要在init里面做手脚了。当然你也可以通过今天的元类编程,也可以实现。比如说engine,你给他做一个元类,它从那个元类继承。只要你现在构建他,他可以在里面去给他记个数。也就是说engine现在已经有一个了,是不是这就计数啊,但是这个就稍微麻烦一点。我们发现不需要这么做,你在engine里面是不是可以放一个所谓类属性啊,计个数也可以吧,对吧,但是还是有风险,为什么有风险做单例引用这种计数方式有风险,你别忘了我有可能是多线程的方式啊,我同时现在有两个线程起来了。我发现engine都没有创建,这是不是同时发现的,同时发现是不是就跟我们事务并发其实挺相似的。这个时候在某一个时期大家一读,哎呦原来计数现在是零,两个读到是不是计数都是零啊,现在创建了一个出来,另一个是不是也创建一个出来,这个时候就不一定是1了,对吧。就是说你真正创建的对象就不是1了,这就不能保证单例了。对吧,那怎么办呢?加锁是一种方式,对不对,加锁是一种方式。我们就顺便提一下单位应该怎么解决。你比如说class Student(Model):这个东西我要单例怎么解决呢?
 # 现在呢,我们是想实现一个engine ,那个engine里面呢我们也是想把class Model(metaclass=ModelMeta):下的这东西给他挪走,对吧,那这个engine呢大家看一个是管连接,一个是管这个save方法。那如果engine把它拿走之后,我们想那你总得让我实体类总得有东西调用吧。实体类调用,实体类里面没写,那谁写了,那肯定是在model里面想解决嘛。但是model里面要是不解决谁解决啊,那谁解决,没人管了,所以这个时候我们就要看了,这个时候就有地方不一样了,看这class Engine:      def save(self,instance:Student),当然我这个按道理不应该写student,我是为了让你看清楚,这个地方是不是其他类型都可以啊。我写这个就是让你看得清楚,这我们是准备这么解决这个问题的,这样是不是就离我们当初设想的目标就太近了,我们当初目标就说这个save方法你就不要管了。当时想放在session上,现在呢,我觉得session还不行,我再给你来个engine来解决这样一个问题,对吧。那么看这个地方save方法,save方法你把instance传进来。这不就是你那一个个student的实例嘛。 把这个实例拿进来之后,你看for K,V in instance.__mapping__.items()我这块干嘛,我还需要再遍历一遍你所有的属性吗?不需要了吧。我只需要把你的mapping是不是拿来啊。对吧,我以前还得遍历你的类属性,我还得写个什么instance,__class __点儿,是不是dict到这里面是不是遍历啊。现在不需要把,现在我只需要在你的实例的mapping这个属性上做一些事儿就行了,把mapping拿过来,把items拿过来。这里面拿的是什么呀?k是谁呀?k是不是就是他那个名字,然后v呢?不就是那值吗,当时放的。mapping把v拿到了,我们是不是下面可以做其他事情啊,把v拿到以后,你看他怎么做的,names.append('{}'.format(k))把这个k拿出来,然后把values是不是还是通过instance这个方法去取出来,取出来之后是不是我们字符串能不能筹出来,可以筹出来吧。哎,那发现原来这东西是不是可以不放在实体类其他的基类里面也可以做啊。对吧,你传什么实例,实例里面他已经有一个mapping这样特殊构造过的属性,就可以拿到每一个实例的这个他的字段所对应的属性的话,那我还需要把这方法加在你那边嘛。所以他这个时候就把方法迁移了,他把方法迁移到ending这边来了。由ending这边来解决所有的crud的处理啊。所以呢,通过这种方法也就可以了。

import pymysql

class Field:
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        self.name = name 
        self.fieldname = fieldname
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index
        pass

    def validate(self,value):
        raise   NotImplementedError
    
    def __get__(self,instance,owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self,instance,value):
        self.validate(value)
        instance.__dict__[self.name] = value

    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__, self.name)
    
    __repe__ = __str__

class IntField(Field):
    def __init__(self,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field,auto_increment=Field):
        self.auto_increment = auto_increment
        super().__init__(name,fieldname,pk,unique,default,nullable,index)
    def validate(self,value): 
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,int):
                raise TypeError("{} should be int".format(self.name))


class StringField(Field):
    def __init__(self,length=32,name=None,fieldname=None,pk=Field,unique=Field,default=None,nullable=True,index=Field):
        self.length = length
        super().__init__(name,fieldname,pk,unique,default,nullable,index)

    def validate(self,value):
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            if len(value) > self.length:
                raise ValueError("{} is too long. value={}".format(self.name,value))


class Session():    # 这时线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):
        self.conn = conn
        self.cursor = None

    # def close:
    #     self.conn.close()
    def execute(self,query,*arge):
        if self.cursor is None:
            self.cursor =self.conn.cursor()
        self.cursor.execute(query,arge)

    def __enter__(self):
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        if exc_type:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()

class ModelMeta(type):
    def __new__(cls,name,bases,attrs:dict):
        # print(what,bases,attrs)
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower()

        mapping = {}
        primarykey = []     
        for k,v in attrs.items():
            if isinstance(v,Field):
                mapping[k] = v
                if v.name is None:
                    v.name = k
                if v.fieldname is None:    
                    v.fieldname = v.name    
                    primarykey.append(v)     
        
        attrs['__mapping__'] = mapping

        return super().__new__(cls,name,bases,attrs)

class Model(metaclass=ModelMeta):pass      # 当然拿走了,但是你的这个基类要有,写一个pass

class Student(Model):
    # conut = 0   # 对吧,你只要实例创建一次,我就会给你加1。也有很简单的解决办法,我自己先创建个实例给你呗。我这边类定义完以后,我立马给你一个实例。你想在创建是不是就不用加锁了,对不对,因为你所有的后面创建是不是读这个count啊,只要我给你创建了一个实例,是不是就已经变成1了,你再改对不起改不了。这时候你们随便争,反正已经是1了,你们随便争你爱怎么读这么读,是不是都是1啊,你哪怕就是一千个线程同时读还是一啊,改变不了这个事实吧。所以这是不是一个最简单的处理办法。也就是说我勤奋点,我怕你多建,我先给你建一个不就ok了吗,下面你们再争抢就不会出现超过1的情况了,因为你怎么读都是1了,对不对。这种也是一种很简单的解决办法。用这种方式也可以啊。当然你非得让他们现在是突然一下起好多线程让他谁先看到,谁抢到是谁的,这就出问题了。因为大家一看都是0,都想创建,一下创建一堆出来对吧,创建一堆出来,尤其是怕说,哎,你不是用统计数字吗,我现在来看一下。看这个实例到底有几个,你一数好家伙,不止一个,是吧。你如果判断个数的话,你会发现不止一个。如果你用count的话,大家是不是你写完我写,是不是这个1可能被很多人创建实例以后,都修改成1啊,他感觉上是一个实例,实际上已经不是一个实例了。所以单例呢就是有这种争抢现象时候你要看怎么解决,这是单实例,我们顺便说一下啊,这是一种方式。那如果是单例,那既然最后就要用这一个,既然要用这个,往往都是全局使用这一个,那你何不先给我提供一个呢,然后你通过一个简单的count等于1不就全部解决完了,谁读都是1了,这是最简单的解决方案,我觉得也是很有效解决方案。
    # __tablename__ = 'stu'
    id = IntField(pk=True,nullable=Field,auto_increment=True)
    name = StringField(64,'name',nullable=Field)
    age = IntField('age')

    def __init__(self,id,name,age):
        self.id = id
        self.name = name
        self.age = age

class Engine:      # 这里写一个类,Engine

    def __init__(self,*args,**kwargs):      # 数据库连接,对吧,engine里面完成,这时候在构造,时候来解决这个问题,那构造的时候我们这块应该怎么写啊,是不是你有很多参数,对不起,有多少写多少呗,*arg,**kwargs。然后呢,这块儿是不是我们要借助另一个东西啊,数据库连接是不是pymysql这边是不是可以解决我们这个连接的问题。
        self.conn = pymysql.connect(*args,**kwargs)      # connect是不是这个方法是可以解决我们问题,这个方法里面有什么送什么呗*arg,**kwargs,对吧,让他自己去解决这个问题,我们不关心了。那这个东西是什么呢?是不是也可以放到他的self点conn是不是就等于他了。对吧,这样的话我们就把连接就已经建好了,我们通过这样的方式就把连接给他传进去了,对吧。那我们有这样的连接,就可以解决我们的问题。这大概就是一个engine这边的一个改造。engine这边一个改造的话,那我们这边真的要用的时候,是不是engine这边应该做个实例啊。

    def save(self,instance:Session):      # 然后放到这,放在这儿以后呢,我们发现这一块儿现在要遍历谁呀,这地方应该用的是一个实例吧instance,当然我们为了方便先写个student,对吧,你要知道这东西不不仅仅是student。
        # 连接数据库
        # cursor execute(sql)
        names = []
        values = []

        for k,v in instance.__mapping__.items():      #  这块遍历谁呀。是不是instance他的字典里面有些东西,字典的东西是不是就它的属性,__mapping__就行了。在mapping里面有所有的k和v,v本身还是这个东西,k是什么,继续弄。
            if isinstance(v,Field):      # 继续弄,然后v 是不是还是从这个k里面去拿self这个,这能self嘛,这是不是就变成instance。self.__tablename__ 肯定不是吧,instance.__tablename__ 是不是都跟instance相关啊。跟instance相关,name和values这些东西是不是改造,还是从instance里面去拿,拿了之后是不是jion凑的方法都没变啊。你把表名是不是从instance.__tablename__拿,那这里面还有东西要改吗?没了,写完了,对吧,这么来做。那原来的s.save()还用吗?
                names.append(k)
                values.append(instance.__dict__[k])
        # sql = "instrt into student (id,name,age) values (%s,%s,%s)"
        sql = "instrt into {} ({}) values ({})".format(
            instance.__tablename__,
            ",".join(names),
            ",".join(['%s']*len(names))
        )
        print(sql)
        print(values)

        # with session as cursor:
        #     session.execute(sql,self.name ,self.age)

# 连接数据库
# 


s = Student(5,'jerry',20)
# s.save()      # 那原来的s.save()还用吗?Model都没了,还说你实体类上呢,是吧,没了以后,然后我们怎么办,是不是要做一个engine啊,那这个engine应该怎么做呢?engine是不是里面还得去与数据库连接啊,我们数据库连接刚才是写到谁里面了,是不是写class Session():      def __init__(self,conn:pymysql.connections.Connection):这了。但是conn:pymysql.connections.Connection这东西是不是还是从外界传进来的,好,那好办了,那我们自己来写就是了,数据库连接,对吧,engine里面完成。

# 连接数据库
engine = Engine('127.0.0.1','root','fspass','mysql')      # engine这边一个改造的话,那我们这边真的要用的时候,是不是engine这边应该做个实例啊engine等于Engine然后把什么放进来,是不是就是我们那些什么,127.0.0.1啊,那些东西,是吧。我们Engine这块是不是他替我们去维护这个连接啊,维护这个连接。最后我们想用,对吧,session这块,当然我们最后把session这块儿。你如果要用session,session这个连接是不是没人传进来啊,你得想办法给他传进去啊,那session这一块要用的时候你去创建就行了。
engine.save(s)
# Session.save(s)
# print(s.__dict__)
# print(Student.__dict__)


# 打印结果,是不是可以拿到,剩下就是解决怎么去连接的问题了,怎么去连接,然后去执行的问题,创建出来,然后给session传一个conn进去,然后就可以with session as cursor:通过with语法就可以直接做了。剩下就是session.execute(sql,self.name ,self.age)怎么连接的问题。那对于一个基本的,尤其像我们这个后面马上要讲的东西啊,我们后面马上要讲的东西。
instrt into student (id,name,age) values (%s,%s,%s)
[5, 'jerry', 20]
  总结
        这是一个ORM框架的雏形,从这个例子就可以明白ORM框架的内部原理。

        学习一个ORM框架
        1、看Model类如何描述表,属性和字段如何映射
        2、增删改查方法调用如何转化为SQL语句并执行

import pymysql

# 字段类的实现
#   字段名称column(字段本身字段的名称)
#   类型type(字段本身什么类型比如int,映射到python这里就无所谓了)
#   是否主键pk 
#   是否唯一键unique
#   是否索引index
#   是否可为空nullable 
#   默认值default(是否有默认值)
#   是否自增
#       这些封装成一个类就可以了,在这个类实例化的时候把这些东西传给他,每一个字段都不一样用一个类的实例的属性来保存
# 字段类要提供对数据的校验功能,列如声明字段是int类型,应该要判断数据是不是整形,比如传递一个字符串,在数据库中定义是5个字符长度的,传递的是6个字符的长度那就不能在传递进去了必需拦截住,这里就必需解决这样的问题进行校验
# 字段现在定义为类属性,而这个类属性又适合使用类来描述,这就是描述器了

class Field: # 定义一个类,解决字段定义的问题,每一个数据类型他的校验方法有可能不一样,对不一样的地方做继承做一些简单修改
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment(是否自增,写在这里不合适,可以写到子类里,比如int字段有自增的需要)

        # 赋值实例属性
        self.name = name 
        self.fieldname = fieldname  
        self.pk = pk
        self.unique = unique
        self.default = default
        self.nullable = nullable
        self.index = index


    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样
        raise NotImplementedError()  # 具体类型,具体对待,父类不实现,基类告诉你我是个框架,是个架子,子类才知道自己具体的情况是什么,有子类具体来解决实现数据校验问题

# 构建描述器
    def __get__(self,instance,owner):  # self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        if instance is None: # 如果instance拿到Student当前实例是None
            return self # 返回self拿到的的实例
        return instance.__dict__[self.name]  # 返回自己的名字

    def __set__(self,instance,value):
        self.validate(value) # 存数据前可以对数据进行校验,对value进行校验,是谁的实例,就进入做相应的校验,校验如果能通过一定没有异常,向下走进入 instance.__dict__进行赋值
        instance.__dict__[self.name] = value # instance拿到Student当前实例中的dict(字典)没人用了,实例记住自己的数据,最好的办法就是实例在自己的字典上玩,把字典用上需要给个key, name = StringField(64,'name',nullable=False)给的name就是name,借用名字不一样作为key,防止覆盖,然后再Student上面一个个不同的实例上面存它self.name = 'name'写进来的值value
# 中的self.name,self.name是Field类的属性作为字典中的键,进行赋值= value
    def __str__(self):
        return "<{} {}>".format(self.__class__.__name__,self.name) # 看一下当前实例的类的名字

    __repr__ = __str__

class IntField(Field): # int类型的类,继承至Field类
    def __init__(self,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False,auto_increment=False): # 子类个性化的东西,覆盖基类的init属性,auto_increment=False是否自增个性化增加的
        # 给实例传递信息
        #   name(给一个字段名字)
        #   fieldname(字段本身的名字)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        #   auto_increment=False(是否自增,默认False,不自增,但是int字段有自增的需要,方便以后使用)
        self.auto_increment = auto_increment # IntField类个性保存的东西,auto_increment赋值给实例属性
        super().__init__(name,fieldname,pk,unique,default,nullable,index) # 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        if value is None: # 简单的校验,value数据进来是不是空(None)
            if self.pk: # 如果是主键,如果value进来的数据是空,恰巧是self.pk主键,主键不能为None
                raise TypeError("{} is pk,but {}".format(self.name,value)) # raise抛出异常
            if not self.nullable: # 如果value is None,但是数据库不允许为空nullable
                raise TypeError("{} required.".format(self.name)) # raise抛异常
        else:
            if not isinstance(value,int): # 到这里给的value不是None,而给的是实实在在的值,就要进行类型判断了isinstance判断value是否是int类型
                raise TypeError("{} should be int".format(self.name)) # 如果不是int类型,抛异常

class StringField(Field): # String类型的类,继承至Field类
    def __init__(self,length=32,name=None,fieldname=None,pk=False,unique=False,default=None,nullable=True,index=False):
        # 给实例传递信息
        #   length=32(字符长度,默认32字节)
        #   name(给一个字段名字,不是关键字,必需给一个名字)
        #   fieldname(字段本身的名字,默认None)
        #   pk=False(是不是主键,默认False)
        #   unique=False(是不是唯一键约束,默认False)
        #   default=None(有没有缺省值,给个名不合适,默认None)
        #   nullable=True(是否为空,默认True,可为空)
        #   index=False(是否为索引,默认False,不是索引)
        self.length = length # IntField类个性保存的东西,length赋值给实例属性,告诉他字符长度,可以长度判断
        super().__init__(name,fieldname,pk,unique,default,nullable,index )# 继承父类的属性,这些属于基类保存的东西

    def validate(self,value): # 数据校验功能,每种类型的数据校验功能可能不一样,对基类的数据校验覆盖掉做一些修改
        # 验证方式与IntField类一致
        if value is None:
            if self.pk:
                raise TypeError("{} is pk,but {}".format(self.name,value))
            if not self.nullable:
                raise TypeError("{} required.".format(self.name))
        else:
            if not isinstance(value,str):
                raise TypeError("{} should be str".format(self.name))
            # print('len(value)',len(value))
            # print('self.length',self.length)
            if len(value) > self.length: # 上面判断完就可以保证类型是什么,判断一下value长度是否大于self.length
                raise ValueError("{} is too long. value={}".format(self.name,value)) # 大于self.length,值出错了,抛异常

class Session(): # 这是线程不安全的
    def __init__(self,conn:pymysql.connections.Connection):  # 传入一个连接
        self.conn = conn  # 将连接赋值给self.conn实例属性,如果conn是从外部传入的就不要管关了,如果建立连接是在你这里做的,那就应该提供关连接的功能
        self.cursor =  None # Session实例属性self.cursor默认等于None


    # 连接数据库
    def execute(self,query,*args ):  #这里的作用是给一个sql语句他就执行 ,只负责执行该提交提交, 自个已经有了这里的conn就不用传了 ,query传递sql语句,*args传递属性
        if self.cursor is None: # 如果self.cursor属性是None
            self.cursor = self.conn.cursor() # 给self.cursor属性一个游标,这种方式是线程不安全的
        self.conn.commit() #  提交


    # 上下文,跟自己类的实例没有关系,当别人with,Session这个类的c实例的时候会调用__enter__,走的时候会调用__exit__,做完工作走的时候可以实现提交事务
    def __enter__(self):
        self.cursor = self.conn.cursor() # 返回一个游标,给with语句的他Session.execute(sql,self.name,self.age)调用
        return self # 如果用with进来,返回Session本身

    def __exit__(self,exc_type,exc_val,exc_tb):

        if exc_type: # 有没有异常
            self.conn.rollback() # 有异常执行回滚
        else:
            self.conn.comit # 没有异常进行提交
        self.cursor.close() # 关闭连接

# 实现元类(元编程),元类不是用来继承的,ModelMete类的执行时机和Model类的执行时机是完全不同的,ModelMete类是在类构建时执行,Model类的子类的实例,来调用方法的时候,比如save方法时执行。
class ModelMete(type): #元类可以拿到__new__函数,拿到以后就可以对所有的实体类在构造的过程中进行拦截对他字典里填一些东西,让他用的更加方便比如__tablename__,在比如拿到你可以找到里面的所有k和v字段和Field实例之间对应的关系要借助一个字典省的以后遍历类属性我们生成了一个字典叫做mapping,还要找出主键是谁,总之可以把一些你认为有用的东西全部通过在元类通过这种捕获遍历的过程中把他拿过来给他形成一个属性放在哪。 类构建的时候执行,继承至type类,继承自ModelMete的子类都会调用__new__方法
    def __new__(cls, name:str,bases,attrs:dict):
        # print('1',name,'
','2',bases,'
','3',attrs)
        # 打印结果,结果有两个类调用,一次是Model类调用打印出来,一次Student类调用打印出来,操作的时候需要区分开
        # 1 Model 
        # 2 () 
        # 3 {'__module__': '__main__', '__qualname__': 'Model'}
        # 1 Student 
        # 2 (<class '__main__.Model'>,) 
        # 3 {'__module__': '__main__', '__qualname__': 'Student', 'id': <IntField id>, 'name': <StringField 64>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7f9ac20e4620>, 'save': <function Student.save at 0x7f9ac20e46a8>}
            # 此字典不是空的,Student类定义的方法早就塞到字典中了,Student类定义的时候里面的方法就必需要知道,他必需知道这些属性后才能使用元类,他的母版通过这些母版来创建一个真正的类对象出来,原来是type的实例,现在就是ModelMete元类的实例

        # 解决表名的问题
        if attrs.get('__tablename__',None) is None:
            attrs['__tablename__'] = name.lower() # 如果__tablename__属性为None,就把name值赋值给他,lower()方法转换字符串中所有大写字符为小写解决类名大小写问题,

        mapping = {} # 给他一个字典,找到里面的所有k和v字段和Field实例之间对应的关系要借助一个字典省的以后遍历类属性我们生成了一个字典叫做mapping
        primarykey = [] # 生成列表,防止原来的值被覆盖掉,想要找到主键
        for k,v in attrs.items(): # 遍历这个字典,写这个东西是告诉你,还有其他你关系的东西,都可以把他从里面遍历出来以后,可以把他塞进attrs字典里
            # print(k,'==',v) 
            if isinstance(v,Field): # ,主键是否可以遍历出来,首先要是Field才问你有没有pk这个属性。如果值中有Field的
                # print(k,v) # 都打印出来,这是我们想要的
                mapping[k] = v # 塞到字典里v
                if v.name is None: # 如果v.name也没给呢
                    v.name  = k # 给v.name赋值为k
                if v.fieldname is None: # 相当于Field类判断if fieldname is None属性是否为None,fieldname的构造完全可以通过Field类的提供的name来解决,当Student类的id属性不提供字段名就行了,也可以不写,默认值是None
                    v.fieldname = v.name # 判断如果fieldname没有给名字,就给一个v.name,如果v.name也没给呢
                if v.pk: # 如果遍历出来pk主键属性,条件为真
                    primarykey.append(v) # 写个v是告诉你这个地方以后是一个什么的实例,想用里面的一些属性随便拿,比如可以拿到v.name,给primarykey把多个主键或一个主键对象加进来
        attrs['__mapping__'] = mapping # 以后用的时候都塞到字典里去了,以后直接访问__mapping__属性所有的k和v都可以拿到
        attrs['primarykey'] = primarykey # 把遍历出来的pk主键对象,塞进attrs字典里
        return super().__new__(cls, name,bases,attrs) # 送回去,有什么送什么,不然继承至他的Student就成None了,super()调用父类下的__new__属性

class Model(metaclass=ModelMete): pass # 代码块挪走但是这个基类要有。当ModelMete的子类的实例,来调用save方法的时候运行,这里类继承至ModelMete,构造时会用ModelMete的__new__方法

# 实体类,Student实体类要做个性化的就行了,__init__方法非个性化,应为大家都是一样的
class Student(Model): # 类本身是对象,需要用元类来构造自己,当执行到Student,就把构造他的这些属性,我们可以在元类型ModelMete中得到,这里类继承至Model,基类还是ModelMete,构造时会用ModelMete的__new__方法
    # 用描述器 来描述字段的各种特征 比如 pk(是否为主键),约束唯一(是否唯一约束),null(是否可为空),fk(是否为外键)
    # 这里所做的就是在详细的设计表
    id = IntField(pk=True,nullable=False,auto_increment=True) # 如果不给name,fieldname,pk需要使用关键字传参
    name = StringField(64,'name',nullable=False)
    age = IntField()

# 实例属性,每个属性与字段之间建立映射关系,字段除了有某些数据外,还有其他的某种特征的表达
    def __init__(self,id,name,age):
        # __tablename__ = 'stu' # 这个属性只跟这个类相关,这个类映射一张表,这个属性记住这个表名,
        # 当属性定义为描述器的时候,只要对这个描述器的属性赋值,他就是调用__set__方法,__set__方法能拿到(self,instance,value)self拿到的是Field 的实例,也就是说实例本身是谁就是谁,instance拿到Student当前实例,owner拿到的是Student
        self.id = id
        self.name = name
        self.age = age 

    # # 连接数据库
    # def save(self,Session:Session):  # 这里要解决每一个业务方法的问题,用于传递给 Session.execute()sql语句进行执行
    #     # 拿到cursor 掉execute(sql)方法里面送sql语句
    #     sql = "insert into Student (name,age) values (%s,%s)" # sql语句,对Student表插入数据,值为self.name,self.age
    #     Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

    #     # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
    #     with session:
    #         session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去

# 引擎类,
class Engine:
    # 连接数据库,在Engine里完成
    def __init__(self,*args,**kwargs): # 构造的时候在解决数据库连接,有很多参数,*args,**kwargs有多少写多少呗
        self.conn = pymysql.connect(*args,**kwargs) # pymysql解决连接的问题,把连接赋值给 self.conn

    def save(self,instance:Student):  # 这里应该使用一个实例instance
        # 拿到cursor 掉execute(sql)方法里面送sql语句
        names = []
        values = []
        for k,v in instance.__mapping__.items(): #  在instance类的__mapping__里面有他所有的k和v
            if isinstance(v,Field): # 如果你是这些东西,才跟我拼字符串才有价值
                names.append(k) # 列表填充的是字符id,name,age
                values.append(instance.__dict__[k]) # 将取得的值赋值给values列表
                # print('instance.__dict__[k]',instance.__dict__[k]) # 实例的字典
                # 打印结果
                # self.__dict__[k] 5
                # self.__dict__[k] jerry
                # self.__dict__[k] 20

                # print('self = ',self,'
','k = ',k,'
','v = ',v,'
')
                # # 打印结果 ,同一个self,
                # # self = <__main__.Student object at 0x7f629eb41588> k = id v = <IntField id>
                # # self = <__main__.Student object at 0x7f629eb41588> k = name v = <StringField name>
                # # self = <__main__.Student object at 0x7f629eb41588> k = age v = <IntField age>

        # sql = "insert into Student (id,name,age) values (%s,%s,%s)" # 参数化查询,sql语句,对Student表插入数据,值为self.name,self.age
        sql = "insert into {} ({}) values ({})".format(
            instance.__tablename__, # 传递表名
            ",".join(names),  # join():连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串
            ",".join(['%s']*len(names)) #join遍历出来,然后以逗号分割,一个大列表里面三个字符串重复,需要一个可迭代对象['%s']套到一个可迭代对象中,在乘以三 这里有一个大问题,什么类型加单引号,字符串要加单引号吧,什么类型不加单引号,int类型不需要吧
        ) # 改造方法

        print(sql)
        print(values)
        # 打印结果
        # insert into student (id,name,age) values (%s,%s,%s)
        # [5, 'jerry', 20]
            
        # Session.execute(sql,self.name,self.age) # Session类调用save方法把sql语句传递进去

        # 在这个save函数中with,session类,能做什么,sql是个字符串没有什么好说的, Session.execute(sql,self.name,self.age)这一句可以放到with语句中
        # with session:
        # session.execute(sql,(self.name,self.age)) # cursor需要一个元组或字典,Session类调用save方法把sql语句传递进去



s = Student(5,'jerry',20)

# 连接数据库
engine = Engine('192.168.161.203','root','root','test') #Enginge需要一个实例, Enginge要跟数据库连接
engine.save(s)
# print('s.__dict__ =',s.__dict__)
# print('Student.__dict__ =',Student.__dict__)
# 打印结果
# s.__dict__ = {'id': 5, 'name': 'jerry', 'age': 20} # 自己存还是存自己的描述器的__set__方法帮我们塞进去的
# Student.__dict__ = {'__module__': '__main__', 'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>, '__init__': <function Student.__init__ at 0x7fa153397048>, 'save': <function Student.save at 0x7fa1533970d0>, '__tablename__': 'student', '__mapping__': {'id': <IntField id>, 'name': <StringField name>, 'age': <IntField age>}, '__doc__': None} # 创建时Student类的id,name,age属性就要保证这样塞,我们用元类塞进去的'__tablename__': 'student'在,'__mapping__'也在里面看到的id': <IntField id>名字格式是由描述器的__str__方法定义的名字对应着一个个实例


SQLAlchemy框架

SQLAlchemy是一个ORM框架
大量使用元编程

  那这东西有什么用呢?就先说这是ORM工具啊,它是个ORM的一个框架。这框架就是刚才我们写的那个东西,但是人家写的比我们复杂多了。所以呢,这个东西呢我们就要去看一看了。那他是一个ORM框架,也是现在python这边最流行的orm框架,也就是说,我们真的要与数据库之间进行操作的话,我们不可能直接的去写什么sql语句来这么做。虽然可以这么做,但是一般情况下都得先做对象关系映射,然后才能操作数据库,这其实是一个工业标准,大家都这么做。不管你现在以什么语言开发,我们通过pymysql这种方式直接去操作数据库中的表,那这种操作呢,就属于最早最早最原始的方式。大家这么写的,对吧,做做练习是可以的。但是一旦真的要生产环境中,咱们要使用代码怎么办呢,都必须用orm这种工具或者框架来替我们进行对象的封装,然后通过对象来操作数据库。那这时候就有问题来了,我们写那个都把自己写的累了。你想如果我们要进行多表之间的关联之后,然后进行增删改查。对吧,就更复杂了是吧,你还得给我解决,你比如说我要级联删除,你给我解决对吧,我不会写。我虽然写个框架,但是这么复杂的东西我就不写了,是吧,我就找你要。他得给你解决像类似于这种问题,对吧,他这写的框架应该比我们的还要灵活。我们在学的过程中,我们把大部分功能就已经实现过了。所以大家在学习的时候应该比直接讲这个东西,你要觉得要轻松许多了。它为做这个框架,他大量的使用元编程。因为我们发现刚才我们在做这个model 类的时候,我们发现用元编程是比较省劲儿的,是吧,我发现通过元编程就可以解决我们很多的问题。但是呢,除了像这种地方,你明确知道用元编程能解决,你想解决的问题。否则的话我们还是那句建议。能通过继承能解决的问题,你就别用元编程了,好吧。那怎么做呢?很简单那个pip install sqlalchemy,你把东西往上边写就行了,他就把他装上来了。

安装

pip3 install sqlalchemy
版本检查

开发中选定一个版本后需要写文档告诉所有人,当前使用的什么样的库,使用的几点几点版本,不然各别版本之间他们是有些性能或者个别方面的一些差异,这些差异一旦存在,根本不能保证大家所使用的代码能够正确的在这个程序上运行,需要预定各各库的版本

# python模块查看版本

import sqlalchemy
print(sqlalchemy.__version__)


# 命令行查看版本

[root@localhost ~]# pip3 show sqlalchemy
Name: SQLAlchemy
Version: 1.3.18
Summary: Database Abstraction Library
Home-page: http://www.sqlalchemy.org
Author: Mike Bayer
Author-email: mike_mp@zzzcomputing.com
License: MIT
Location: /usr/local/lib64/python3.6/site-packages
Requires: 

开发

SQLAlchemy 内部使用了连接池

  如果查考文档:https://docs.sqlalchemy.org/en/13/orm/tutorial.html

  第一件事儿请你给我创建一个连接。创建连接的方式变了呀,这已经变成了engine = create_engine('sqlite:///:memory:', echo=True)这种方式,对吧,这种方式我们可没写过呀。sqlite:///:memory:这什么意思啊?这怎么还///三个杠啊?那就是说留一个是不是做根了,他这个意思一样,那他为什么要用根呢,总之SQLAlchemy说过这是一个本地使用的非网络使用的啊,本地使用,一个本地的一个很小的关系0型数据库,微型的关系型数据库,他就跟实际上告诉你什么呢?他这个是在本地路径,它跟网络无关,是吧,我们写网络应该怎么写,跟根有关系吗?没关系吧.我们写网络协议怎么写的,是不是下面就没这个根了,192.168.多少多少东西,是不是这,冒号3306是不是这么写啊,就这么写。所以说这一块他是跟本地路径相关的啊。你看他一上来,看完版本,紧接着说你应该创建一个引擎。我们刚才写了一个引擎,第一步干嘛啊,是不是连接时候归他管啊,对吧,至少把连接事关归他管吧。那连接事归他管,来我们瞅瞅看这个create_engine()他这里面究竟有什么东西啊。哎,这一块好像有我们想要的东西吧。他说如果你create_engine这个引擎的话,你应该创建一个引擎的一个实例,创建一个实例就够了。然后标准的调用的方法应该是一个url对吧,所以这个时候我们要用url的方式,然后呢engine = create_engine("mysql://scott:tiger@hostname/dbname",这个mysql的连接方式原来是这么写的呀,是吧,哎,原来是这么写的,然后这边encoding='latin1', echo=True)给我加一些东西啊。那这个是mysql的方法,但是呢这是我们要用的吗?所以引擎的配置这些东西呢,我们再来看看啊,还有。那就要多看看啊,这一块儿Engine Configuration是它引擎的相关配置。大家有空的话去看一下这个图,他什么意思呢?他说呢你通过他创建之后呢,他会给你做一个方言,为什么呢?。这张图我们要看得懂啊,看他为什么这么做呢?我们来看啊,首先不管怎么说,我未来就连在这儿Engine来,我必须得通过engine,对吧。我写代码通过engine给我创建一个connect()连接,但是最终是调这个方法,调这个方法以后,这个连接实例就创建了,连接实例创建了。你看这里面Pool,Dialect有东西,这东西Pool能是一个池里面能放一个东西吗?一般来讲,是不是池里面放一批啊,对吧,也就是说一批连接在池中维护着。我看来我们的实现是不是太原始了,太简陋了。人家这边还实现个连接池,但是我们其实已经提到了连接池的概念了,对吧。我们说一个引擎要全局,就这么一个的话,连接可能各个子系统你还得用个连接吧,这个时候是不是要多个连接。那如果这个时候的话,你要不要做一个连接池出来,所以我们还得实现一个连接池类。然后连接池类是不是被放在这个engine里面去。你想办法把它给放进来,对吧,其实也很简单,self点什么东西不就完了嘛,就放进来了啊。有了连接池行吗?这管不同连接,他为什么还要下面加一个Dialect这个东西呢,这个东西方言。什么意思啊。你说上海话啊,对不对,他说陕西话啊,对不对,你说不定你还来个什么,各个地方什么话了,对不对,各种话是吧。各种话不一定能听得懂是吧,不一定能听得懂。那方言就是处理,就是各种方言,就是说不同的东西,不同什么东西?不同的数据库有不同的方言甚至同一个数据库也有不同的方言。比如说你支持的sql版本不同,对不对,比如说大家都是支持sql的,但是分两类,一类叫tsql对吧,还有一种叫什么,叫plsql比如oracle用的叫plsql这就是不同的方言。然后呢,它会创建出个DBAPI也就是说,操纵还是通过api, api什么意思啊,也就是你这边暴露出来的接口嘛,然后调用就是了嘛,对吧。通过api来调用数据库的这种各种功能嘛。增删改查嘛,之类的,对吧。当然除了增添改查,比如还要控制语句,难道你就不能有一些控制语句传过什么。比如说我现在能不能通过我的编程来做一个grace授权呢,当然可以啊,难道还得非得跑到数据库跟前,然后敲啊,或者你远程连到数据库才能做这事儿啊,只要有权限,我现在编程时候通过executor,然后后面写那种语句应该是都可以做到的,这才是我们想要的东西,对吧。所以呢他这里面通过这样一个东西来给我们解决整个引擎应该做的事情。你看这个东西engine = create_engine('postgresql://scott:tiger@localhost:5432/mydatabase')已经接近于我们做过使用mysql的方式了啊。他是通过方言这种问题解决,你看dialect+driver://username:password@host:port/database它说dialect这个东西请你用方言加driver。比如说你叫mysql对吧,然后后面加/username:password@host:port这种方式,这是一个通用方式。最后呢,写/database就这种方式,他说我支持用url的方式来实现。他的内容实在是太多了,所以他的实现是极其复杂的,对吧,你看人家一个Engine光画图,我们都觉得挺复杂的了,是吧,都挺复杂。好,这是就是设计上面的一些区别啊,要专心做这个事情,就要把它做好。这mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]就是我们想要的东西,这就是他的格式。当然呢,我们这是找的过程。我的意思是说这个文档找起来很烦,你要多点几个,找到之后呢,那这东西就是我们以后用就行了。还有包括你比如说mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>这种方式对吧,unix_socket怎么用,然后像mysqldmysqldbb我们说这个东西不更新了,我们就不用了啊。好这是他的引擎以及他这些简单的例子,其实我们发现,其实就就什么呢?就mysql+mysqldb:这些东西是不是mysqldb换了一下就完了,对吧,你记住了以后说找一个例子之后,把这mysqldb换成pymysql然后剩下方式是不是跟mysql的一个经典连接是不是一样。所以就没必要再去找找找找找啊。有空的话,你可以练一下SQLite,SQLite挺好玩的,啊,windows要请你这么写,对吧,他就给你各种各样的例子,因为就怕你不会用。好,这是我们看的第一个里面要处理的问题。那通过这样的一个解决呢。我们看看能不能解决我们想做的问题。
创建连接

数据库连接的事情,交给引擎

# 好,这个就是我们经常大家要使用的一些,这是mysqldb对吧,mysqldb这个库不更新的库。因为你在二点几有可能看到所以把它列出来了,这东西的写法就是这样写的。所以你看这两个的区别mysql+mysqldb      mysql+pymysql0其实就在这儿了。但是呢,这个你如果用pymysql的话,它允许一些选项?<options>以通过打问号的方式,然后后面加选项的方式把它传进来。但这种方式我们用的很少,这个选项是跟数据库连接相关选项啊,你不要乱塞选项进去,数据库连接你知道它有什么选项你再填。
import sqlalchemy

# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

# # pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的,你比如有些他是后台操作,那这个时候你要给他的就权限就比较大了,增删改查都应该做,但是有些仅仅是前端用户上来查,临时要给这个用户查数据的,你就给他一个小权限。你在gramp的时候不要all了,grant select就行了。你写all的话是不是什么权限都有。哎,所以这个时候你授权的时候要注意,。那这个地方纯粹都是你自己用户管理,数据库用户管理的时候你们怎么来管理的问题了,这就不是编程要解决的问题了。通过这种方式,我们就可以连到数据库。那这几个方式的话,我把这几个都不要了,我要engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test",echo=True)后面这个,对吧。这个是什么意思呢?echo=True什么意思,我们一会儿就能看的很清楚啊,用这个东西它表示引擎是否会打印执行语句。也就是说我们在后面做所有的测试,做所有测试,他都会生成sql语句。因为我们用框架就是为了不写,比如刚才我们自己写的框架是不是.save
就行了,你不用写语句,但这个语句要不要打印呢?如果你写个echo等于true的话,它自动生成语句也会给你打印出来。这个便于我们调试。那你把这个写成一个全局的开关量,到时候配置文件里面写的是True你就打印配置文件写的是false,你就关掉就是了。对吧,就这么做就行了。我们用这种方式就可以来解决连接的问题,连接解决完了,下面就是映射的问题了。刚才我们通过文档发现它的引擎里面实际上维护的一个连接池,对吧,他为了解决,多种数据库,为了解决不同数据库之间版本的问题。它增加了比如像方言处理这样的一些功能,对吧,那这个是写的很好的。那么创建连接用我们这些字符串就可以解决的。然后呢,我们下面就是要创建表和对象关系,或者说和类之间如何建立映射关系的。这是我们要解决的问题。首先呢,他说你必须要创建个基类。
engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test",echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试

Declare a Mapping映射关系

表和类之间创建映射关系

创建基类
  我们下面就是要创建表和对象关系,或者说和类之间如何建立映射关系的。这是我们要解决的问题。首先呢,他说你必须要创建个基类。这东西经常会忘掉的,对吧,我们为什么创建基类,你想一想我们刚才是怎么做的,student有没有基类呀,对不对,所以呢,我们这块要怎么写呢?照他写,因为官方文档就是第一手资料嘛。
# 这么写,是不是实体类的基类就已经创建好了,对吧,把它导进来就行了,这东西要不要记呢,不要,因为这东西后面就是一次行的。base是不是创建好了,下面所有的实体类继承至它了。所以前面这东西不用背,是吧,抄一遍就完了,忘了去哪儿抄,是不是官方,对吧。官方https://docs.sqlalchemy.org/en/13/第一个让你点的那个链接进来是不是就可以了。然后,下面你是不是未来想解决问题,是不是class,然后一个Student是吧,Student以后,然后后面就干什么?是不是就是(Base)。。是不是就怎么来了。哎,就应该这么开始了。我们下面就可以这么开始了,这个地方是干什么,是不是就是实体类的创建啊。那这个地方我们要用,是不是就看怎么用了,还记得我们自己写的时候,是怎么用的,首先id对吧。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base

# 引擎,管理连接池
engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    pass

# 那这个地方我们要用,是不是就看怎么用了,还记得我们自己写的时候,是怎么用的,首先id对吧。id是不是想给他写个什么样的东西啊,是吧。那这个时候我们就得从什么上来了,我们就得从from sqlalchemy他应该给我们提供一些东西,我们看看能import的什么东西,有没有Column?有,有没有Integer?有,有没有String?也有。对吧,可以有这么多东西能借着用,那我们看看这个Integer什么东西啊。Integer这东西呢,能让你解决一些类型的问题,对吧,点进去看这一大段,呦,就没啦。那么没了的原因肯定是他跟TypeEngine这个类型是不有关了,点进去,我看来看Visitable点进去。我们看这块有什么东西,所以他这个类封装是非常非常复杂的啊。所以这一块呢,我们来看到Visitable里面class Visitable(util.with_metaclass(VisitableType, object)):又在这创建是吧,这东西还是生成这东西,哎呦with_metaclass是metaclass我们刚才是怎么做的,当然他之间还有其他几层关系啊,我们做的时候是不是写的比较简单,然后一个实体类,上面一个model,model是不是它从某个元类上来,是吧。他是不是看这样子,也是从某个元类上来,只不过是用with_metaclass他自己的工具里面找他的元类。然后来做util.with_metaclass(VisitableType, object)这个事情了,是吧。所以呢,这叫metaclass,对吧,来做这样的事情。所以这是一个封装的是非常非常多的啊。那我们下面应该怎么做呢?比如说这一块,我看能不能解决我们的问题。我们看官方文档他怎么教你的。官方这么说,请你呢如果使用之前,您告诉我 __tablename__ = 'users'你谁啊,你跟哪张表映射啊,对不对,但是官方这边没有像咱们做的比较好的,官方是这样子,你要不给我__tablename__ ,我就直接给你抛异常。他说这东西你必须给,因为你给的这东西我是不是能省的给你省啊,你不给我这东西,我怎么给你省啊,我没法给你省啊,你连表都不告诉我,对不对。好,那我们来给他写一下student,这是不是照官方来啊,然后官方下面用什么东西。。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String

# 引擎,管理连接池
engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'      # 我们看官方文档他怎么教你的。官方这么说,请你呢如果使用之前,您告诉我 __tablename__ = 'users'你谁啊,你跟哪张表映射啊,对不对,但是官方这边没有像咱们做的比较好的,官方是这样子,你要不给我__tablename__ ,我就直接给你抛异常。他说这东西你必须给,因为你给的这东西我是不是能省的给你省啊,你不给我这东西,我怎么给你省啊,我没法给你省啊,你连表都不告诉我,对不对。好,那我们来给他写一下student,这是不是照官方来啊,然后官方下面用什么东西啊?

# 这是不是照官方来啊,然后官方下面用什么东西,官方说用Column来做,Column解决什么问题啊,Column就是我们前面写的field嘛。对不对,只不过呢,这个类型他说从外面当参数给我送进来,我们当时是不是来把这个类型就变成了不同的子类了。比如intfield这是不同的设计方法。这两种方法都可以解决问题。一种是统一设计,然后通过里面来判断,通过参数不同来判断,对吧。那你要这么做的话,那我们也来试一下。Column是解决列对应的问题。然后我这个id呢肯定是integer啊,对不对,然后呢我这地方应该是跟主键相关啊。主键相关来瞅瞅看,哎primary_key,还真有是吧,跟我们那个写的名字差不多,那这应该是不是True啊。那在按道理这个地方你其实可以其他都不写的,比如说有没有null啊,有吧nullable,你有那么多东西,来来点Column瞅瞅你到底有什么东西def __init__(self, *args, **kwargs):,看他说我有这么多东西,最怕这东西了是吧,咋办呢,还好他地方:param name:看是不是有他,所以我们要解释一下这是干什么了。他说请你给我一个名字呀,也就是说这名字是不是在数据库这边的一种表现啊,对这个列的一个表现的名字是吧,对应这个表现的名字,这个名字可以给我一个。然后第二个什么, :param type\_:请你给我一下这个类型,你发现刚才官方的这个例子,官方的例子跟名字有关系吗?他直接把名字都不要了,他直接让我们用type。你看到没。你看官方的例子,这个 Column('data', String(50))说明,你看你应该这么用,什么意思啊。也就是说假设我们这边是个名字的话,那应该怎么写啊name = Column按道理你应该写个叫name,然后这边再写String吧,对吧,name = Column('name',String)应该这么写,但他说,哎,你也可以id = Column(Integer,primary_key=True,)这么写,好自由啊。我们说前面你前面你能像我们写的时候,你能给缺省值吗,你给个缺省值你试试看,你给缺省值后面这个name = Column('name',String)还能写吗?是不是啊,那个不能这么写,所以他是怎么做的,来,你看他是怎么玩的。他怎么玩的,不就靠*args这东西吗?他到时候来数数啊,来看第一个是什么,第一个你想他第一个如果是字符串的话,他一定不是他后面什么Integer啊,什么string类型吧,那第一个是字符串,我就把这第一个字符串做name啊,这不就判断出来了吧。如果你第一个不是字符串,那我就认为你应该是谁。比如说你应该是type吧。如果你现在不是type,那后面当然就有一些他这边告诉你了我后面支持的东西有什么,你是不是_schema.ForeignKey, 你是不是Constraint约束,你是不是ColumnDefault这些东西,我支持这些东西,然后这些东西是不是算args应该解决的问题啊。再往下都是kwargs对应的帮助,看这些:param key:,:param index:,:param info:,:param nullable:,:param onupdate:然后:param primary_key:,primary_key我刚才怎么传的,这是不是我们讲这是用这个kwargs是不是传进去了,哎,他就用这种方式来传的啊,只不过他通过一些简单的技巧,让我们是不是写起来更加简便了。但是什么都可以省,有一样东西不能省,类型,你什么都可以省,但是你一定要告诉我类型。那name = Column('name',String)这个'name'加不加看你自己了,我们是name = Column(String)能省则省,我们就这样能省则省,你比如说name,name我要求你nuallable必须是False,我也不管你默认值到底是什么,我不管,我反正是,现在是认为你这块我就要求你必须这样,所以你这块儿其实默认值默认应该是True的。所以说我们这块就是让他等于False,好这样一个东西做完了,对吧。那id = Column(Integer,primary_key=True,)这一块的话大家知道按道理这一块nuallable应该是False吧,就这样id = Column(Integer,primary_key=True,,nuallable=False)其实你可以不加,因为你看我们自己在处理的时候,校验的时候是不是先看pk啊,如果是pk的话,这块其实就已经做了判断了。所以nuallable这块你可以不管,你不写也没问题,他自己其实把这块,他写的很完善,他这块已经处理完了。当你使用你这块定义的时候,当你用它来反过来去生成数据库中的表的时候,他自然给你把这个主键加上了,你主键加上去他一定不可以为空,所以这是他加的东西,那你要是这样子的话,那你请你告诉我autoincrement这东西怎么办。那我们是不是要给他写上autoincrement=True对吧,对吧,这是不是跟我们刚才写的都差不多,其实我们在学着人家写的,我们在模仿人家是吧。只不过我们发现模仿他太难了,我们简单化了是吧。但是你现在发现是不是学习起来特别简单了,这东西怎么实现的,这东西后面肯定是描述器啊。所以你现在想他们描述器里面写什么呢?它的描述器不就是把这东西拿过去之后,当你操作他的时候,他的get和set的方法一样要工作吧,他用的也就是这个方法他还能用什么呀,然后我们再加一个吧,比如说age,age等于谁呀?那这个地方是不是写个Integer就够了,对吧,那我们说年龄无所谓可有可无,是不是就不关心了,要不要自增啊,这东西跟自增没关系嘛,对不对,那是主键吗?不是,要做外键吗?没有见过几个用年龄做外键的,那什么都不用做,完了,这就定义完了,还写什么,什么都不要写了,完了,这就是最简单的映射。但是他的要求是 __tablename__ = 'student'这一定要写。他们要求这写对不对,写的还没有我们好,但这个东西给你写是有好处,就说防止用户忘记这东西或者忘记自己真正对应什么表,就是我明确让你给我,这东西你必须明确告诉我,就是你还是要有点事儿要做啊。所以我们发现真要自己在他这下面要定义一个所谓的类型来讲啊,定一个所谓的类型来讲的话,你要做的事情就是说把Column这个列这个类能出来。然后把它所谓的Integer,String数据类型给他找出来,那我们看看还有没有其他数据。比如说我想看看有没有日期啊,Date有吧,有没有枚举呀,Enum有吧,对吧,你常见类型他都支持。这就是一个最简单的例子,还没完,官方说这事还没完,对吧。他说我给你做个漂漂亮亮的吧。那我们也做个漂漂亮亮呗,对吧?他是怎么写的?他说这个地方的话,我给你加一个,是不是把这些字段是不是都给你写出来了,那我们这块也要加一个啊,这样一个,因为我们最后要用容器观察的时候,这块你如果不这么做的话,我们看不清楚。因为他都给你显示的某某对象,地址完了,对吧,我们看不清楚里面到底有什么东西。所以repr我们把这个方法给它实现一下return,是不是一个字符串,字符串点format你想要谁,对不对。这个时候的话,你完全可以做一些事情,对不对。你比如说这个时候我要你显示的是谁呢?显示的就是我自己本身,那你要本身的话,我们就self呗,对吧,杠杠class是吧,点__name__,什么意思啊,我想为后来显示什么东西啊,我这么写,我第一项就是你一定要告诉我,现在到底是哪个实体类,然后我后面是不是显示它字段啊,哎你挑着显示几个就行了。你比如说self点,你这个时候是不是要显示字段里面的内容啊,那你完全就可以什么id,self.name,self.age把这三样写了,然后你这块看一下怎么写。你比如说这一块呢,我就写个id等于什么?然后name等于什么?对吧。然后最后age等于什么?这个东西其实就是为了最后打印的时候自己看着方便的啊,那既然写完这个return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age),下面应该怎么做啊?def __str__ = __repr__:对吧,哎算了,我就把他直接给__str__ = __repr__:这么写完了,对吧,前面def也去掉,懒了是吧,你在函数里面调用,和在这边直接给他赋值,是不是一样的,有这种方法,我们是不是后面创建实体了以后看就方便呀,那这么一做差不多了,就我们发现跟官方例子都差不多,对吧,好,这就是最简单的对象关系映射,这东西要不要背呢,不需要背,到官方文档上自己抄去吧,记不住啊,因为from sqlalchemy.ext.declarative import declarative_base这东西我写一遍,就封装起来,我就不看了,我都记不清。哎,原来有这么多层的,我自己也记不清啊,经常忘掉,下面这些倒是天天用,因为你写一个实体是不是写一个啊,写一个实体类写一遍。
    id = Column(Integer,primary_key=True,nuallable=False,autoincrement=True)
    name = Column(String,nuallable=False)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__:

# 创建基类
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

# 完整代码
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base

# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

# # pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test",echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他


创建实体类

Student表

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,string,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举

# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test",echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(string,nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __srt__ = __repr__


实例化
s = Student(name='tom')
print(s.name)
# tom
s.age = 20
print(s.age)
# 20

# 注意:未提交不会在数据库中显示

创建表(创建表不会创建库,数据库要事先存在)
  你用这种方式就可以生成一张表。只要我能连接成功,我就可以用Base.metadata.create_all(engine)这样的语句。是不是来做一下这事。首先是你现在是不是想用引擎啊,我们这引擎是不是在上面这儿,来把这些定义的事儿往上放,把引擎这些东西是不是跟代码创建相关的往下放,实体基类,实体类这些是不是都是纯粹定义的东西啊,是吧,往上放一点。那么通过这样创建一个引擎,然后我们使用引擎干什么呀?create_all他什么意思啊,他的意思是说,通过你这个引擎给我提供个连接数据库的能力。然后呢,我想让你通过Base的metadata,这是啥意思啊,看不明白,他的意思就是说,你所有的内部维护的一个东西。也就是说你所有从Base我这继承的,在我的metadata这边,也就是说他在元类这边,在他的metadata这边,他有一张表,他记录着所有用我这个元类创建的所有实体类,它记录着呢,这个我们刚才通过自己元类的编写是不是做这件事儿,应该不算是难事吧。你拿到这些实体类之后干什么呀?他说拿到了实体类,我能遍历所有实体类干嘛,我来替你做件事儿。我把所有实体类class Student(Base):这种东西我就可以给你转变成create语句,为什么会转变成create 语句啊?你看。create语句怎么写的?表名有没有,有,这东西是不是填空啊,列名有没有啊,列的类型有没有?能不能为空,有没有,是不是主键,有没有啊,约束,有没有,这是不是你刚定义过的。然后这些东西是不是就可以生成了,对吧,但是你说ENGINE=InnDB这引擎哪来的,那来个默认引擎不就完了嘛,哎,这是指的数据库引擎啊,这个跟刚才写那个engine不一样啊,数据库引擎,然后DEFAULT CHARSET=utf8这东西怎么办啊,这东西你不用关心,这个东西,你要不写是不是从你的库上继承啊。我们通过这样一个方式就可以完成数据库中表的创建。那还有一个方法Base.metadata.drop_all(engine)那这方法还用介绍吗?这方法干什么用呢?点进去drop_all看看他说给我一个绑定,你要绑定谁一个engine就行了,给我绑定,然后你要把你删除的表,请你告诉我对不对,这个地方的话我们去看他是不是两个都有了,Base.metadata.drop_all(engine)这个相当于什么啊,你定义的class Student(Base):这些实体类是不是对应的所谓的表啊,那也就是说把这些表给我删除掉。那Base.metadata.create_all(engine)这个什么意思啊,这个是说把定义的表给他创建起来啊,这句Base.metadata.drop_all(engine)我们就不用了,我们把这张表看看能不能创建成功。来走一把
# 创建表
Base.metadata.create_all(engine)

---

# 完整代码
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空,String给一个长度,我们就说人名,你给个64是不是也合适啊。为什么这么写呢,点进去看一下,第一个参数是吧,第一个是length嘛,如果length不给会怎么样?length不给就不给呗,你不给他就是一个None,是个None,这数字是个None你后面怎么用啊,是不是肯定抛异常,所以就是强制要给的啊。
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'      # 一般来讲密码是不一样的,所以你密码应该。那如果是这样的话,你的密码千万不要像我这种重复,你们应该有个东西叫密码生成器吧,对吧,在工作中应该有人用过,对吧,一般像这种密码的话,虽然是公开的,但是也是用密码生成器生成的,一个很长的字符串,但是背不过的。然后大小写符号混写的一个密码。这个密码然后你要用密码把它管理起来,对吧。好。这些东西有了,下来是不是就是填空的问题了。
database = 'test'      # 数据库,需要提前存在

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)      # 那你在外面创建也行,对吧,在外面创建怎么创建呢?我们一般起名叫conn_str这东西我们一般叫连接字符串,连接字符串怎么办呢,把mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)这个东西拿到这里去,所以你要起名字就叫连接字符串,你别改名字了,就大家公认这东西叫连接字符串。
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,这样的话他所有的sql语句你都可以看到,便于调试。然后把这个conn_str连接字符串,给他填进来就可以了
Base.metadata.create_all(engine) # 把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给

# 打印一下,因为我们有echo=True了,他就会把,这东西给我们打印出来
2020-12-18 11:17:32,005 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'      # 前面这里做了很多事,第一个我要读一下你的sql_mode的模式,对吧,也就是说你现的这个mysql的这个工作模式,这个工作模式也有会话级和Global之说。
2020-12-18 11:17:32,005 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,007 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-18 11:17:32,007 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,008 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-18 11:17:32,008 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,009 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'      # 然后这边是不是要查你的Charset` = 'utf8mb4默认字符集呀,和他的Collation` = 'utf8mb4_bin校对方式吧。
2020-12-18 11:17:32,009 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,010 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1      # 他做一些解释'test plain returns' AS CHAR(60)这东西。
2020-12-18 11:17:32,010 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,011 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1      # 然后看一下unicode是什么。
2020-12-18 11:17:32,011 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,011 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1      # 然后看一下你这个所谓的collated 校验方式是什么。
2020-12-18 11:17:32,011 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,012 INFO sqlalchemy.engine.base.Engine DESCRIBE `student`      # 然后看一下有没有这个东西。
2020-12-18 11:17:32,013 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,072 INFO sqlalchemy.engine.base.Engine ROLLBACK      # 最后ROLLBACK
2020-12-18 11:17:32,072 INFO sqlalchemy.engine.base.Engine       # 然后这快是他,曾经用过的东西,给他打印出来了
CREATE TABLE student (
        id INTEGER NOT NULL AUTO_INCREMENT, 
        name VARCHAR(64) NOT NULL, 
        age INTEGER, 
        PRIMARY KEY (id)
)


2020-12-18 11:17:32,073 INFO sqlalchemy.engine.base.Engine {}
2020-12-18 11:17:32,088 INFO sqlalchemy.engine.base.Engine COMMIT      # 最后提交,把这生成之后给我们打印出来。所以说这些东西就是他整个操作过程。也就是说我们现在如果大家要使用,大家如果要使用sqlalchemy的话。我们在自己还不熟悉的情况下,你一定要把echo=True这个东西打开了。

创建会话session
  在一个会话中操作数据库,会话建立在连接上,连接被引擎管理。
# 创建一个session。他说你要准备好与这个数据库之间通话了,那你就给我要创建这个session对吧。所以呢这个session呢,他说当你第一次设置运行程序的时候,在这个级别我们这个创建完成这个语句之后。因为定义一个session类,对吧。然后呢,我们通过它呢,来作为一个工厂来给你创建session对象。什么意思呢?也就是说,这东西呢,得先创建一个类出来,然后用类的实例化的时候,然后创建一个个session。那from sqlalchemy.orm import sessionmaker这个东西你看他又放在orm里面去了。所以他这个名字空间呢,放的好像不是让你觉得很舒服啊。但是呢怎么理解呢?凡是这个orm相关的,跟对象关系映射的,那你就到这儿来做了。那有session干什么,有session下面就是要跟表发生某种关联了。也就是说我要开始操作表了,也就这意思。那我们下面也是把它这个给它传过来。

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker      #  点进去我看来看看这个sessionmaker这个类,class sessionmaker(_SessionClassMethods):这个类的话它有一个_SessionClassMethods从上面来继承的啊。它地下会告诉我们Session = sessionmaker(autoflush=False)这一块的话autoflush默认应该是等于False,也就是说不自动flush的,不自动flush就跟我们前面写的那个文件操作一样。也就是说你操作一大堆是吧,我不替你False。你自己手动False或者提交,然后呢我才能把这样的数据给你放到数据库中去。否则的话你所有操作只是一个临时的。那Session = sessionmaker()我们看这个sessionmaker,他说你应该怎么用呢?你应该这么用。你首先呢sessionmaker()这个东西呢调用一下,然后呢Session这一块给你产生一个Session类,有这个Session类之后,然后对这个Session类sess = Session(bind=connection)进行实例化,实例化Session(bind=connection)的时候,请你告诉我这个Session应该用哪个引擎啊,这个跟我们写的有点相似是吧。然后拿到Session(bind=connection)这个东西之后,你才能拿到一个真正的Session对象sess啊。这个Session(sess)对象以后下来我们才能够去使用,这块# application starts已经告诉你具体应该怎么去用了,对吧。

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)      # 这条语句不用的时候就把它给关掉,不然他给你创建表。
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()      # 我们来看一下应该怎么来做这个事情。首先呢拿到一个Session类,然后呢sessionmaker对吧,然后这块按道理可以写个方bind等于谁,对吧,可以这样写对吧,等于engine它是吧,然后呢我们这一块下面就很好说了。

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)      # session 等于 Session这个Session这是我们用现在他们指定这种方法,也可以,那我们通过这种方式也可以建立出我们想要的东西,那把bind=engine这个东西挪到这儿session = Session(bind=engine)行不行呢。也可以啊,两种方式都行。那这个地方的话,如果你要这么做的话,就是后绑定对吧。后来绑定,后来绑定的话你就可以更加灵活,你说我可以再换一个行不行?也可以啊,都可以。好,那我们通过这样几种方式把它解决掉。

session      # session点可以拿到cursor方法,也就是说,你通过这个session的话,你实际上可以拿到一个cursor的。我们看。所以这个session里面其实也有很多方法可以给我们用的。那session究竟能干什么呢?session能做很多事啊。session创建完之后就可以对表进行操作了。那这样的话我们用session能不能开始用呢?我们来看下面的例子,他说session搞这么多东西,现在就够了。我们下面就看下面应该怎么操作啊。前面我们看到他的例子说,这ed_user = User(name='ed', fullname='Ed Jones', nickname='edsnickname')每一个实体类呢,你都可以用User(name='ed', fullname='Ed Jones', nickname='edsnickname')这种方式来给他创建。我们看这个地方的创建他没有带id,你想我们的id如果是一个整形前,他是一个可以自增的一个字段的话。那按道理其实你如果真想插入一条语句的时候这个字段你是可以不管的。那也就是说如果说我们现在创建就类似于像他这样对吧,我先创建一个实体对象出来这个对象应该有很多数据,你把该填的数据填完,比如像自增id这种你可以不给。因为你给的话相当于我们那天一测的时候把一个数字给他了个100他就会变成100,然后它会自增变成101对吧,变成这种事。所以我们这一块的话应该怎么做呢?这一块的话我们id就不管了,就相当于这写insert语序的时候,insert into,然后后面一张表的时候就把那个id字段故意不写,写完之后怎么办呢?你在session里面需要做一个and这样一个方法,你and过之后呢,就相当于把你这个现在要填的数据已经加到了他现在的某一个里面去了,就它里面会有一个区域来存储,你都改变。也就说你现在做了一定的改变。然后呢你想把这种改变了,加入到我这session里面去,最后你要对session进行提交,也就是说把这种改变最后真的持久化到,当然你commit嘛,持久化到数据库当中去,他说在这个时间上面这两条语句执行完之后呢,我们说这个实例 is pending;也就是说,它是一种预备的状态是吧,他准备什么,准备你commit他了,你不可commit这东西根本就没有提交嘛,因为我们讲这一块的话,应该是交给session。我们刚才看session的时候,他的outcommit它是关闭的。也就是说他实际他是不提交,也需要你手动提交才行,那底下呢our_user = session.query(User).filter_by(name='ed').first()是什么?他说底下这种呢是session.query(User)叫查询,然后呢filter_by(name='ed')过滤,first()什么意思,拿第一条也就是查询完之后有一个结果集,然后你想拿第一条,回来这些数据应该怎么办呢?我们说提交的时候,你用到了这个session,对吧,commit,然后你是把一个对象,对吧,是不是加了一个session里面去啊,它处于一个pending,也就是说现在状态已经变了。最后一把他commit,commit把这个数据实体化进去了。这User(name='ed', fullname='Ed Jones', nickname='edsnickname')是个什么呀?这是一条记录啊,他对应的是一条记录啊。那你现在session.query(User)这种查询,查询往往查的不止一条吧。如果能查到,往往不止一个虽然你过滤了,那不止一条应该拿出来,应该是一堆的数据。他说session.query(User).filter_by(name='ed').first()请你从这一堆数据里面给我拿第一条。拿第一条的话你想应该映射过来的话,应该是那一个个实体类的话,应该刚好对应这个实体类中的某一个的一行数据就应该对第一个实例嘛,所以呢our_user = session.query(User).filter_by(name='ed').first()他这个地方应该是our_user一个实例,所以他打印出来就是这一个。因为你查一条回来,查一条回来,当然放一个实例里面。那如果你后面要来一堆的呢,那我们到时候看他到底能给我们放几个进去。那如果想这边如果能拿好几行记录回来,那这边肯定就不用叫our_user应该改名叫our_users,对吧,应该是好几个,那这里面应该是一个,每一个都应该是一个user的实例,然后他放在一个容器中了,如果是多个的话,对吧,这是这样的过程。然后你看这块有个add_all(),add_all()什么意思啊,add_all()的意思就是说,那来多来几个,多来几个,你看他怎么放呢,这个地方是一个可迭代对象吧,可迭代对象里面,然后放了好几个实例是吧,这就是相当于放了三行对吧,也就是说你准备给他insert into三行嘛,对吧三行记录,那就是三行记录各有不同,他就怎么来做的啊。然后呢他说这一块的话,你可以通过查session.dirty对不对,可以查session.new就看到他这种变化,也就是说在add_all的时候,他并不是commit啊。最后如果你需要的话怎么办?session.commit()竟然有session.commit(),一定有rollback。那这快,我们应该怎么去写呢?我们下面就不要用他这方式了。
  session对象线程不安全。所以不同线程使用不用的session对象。
  Session类和engine都是线程安全的,有一个就行了。
from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象
session = Student() # 实例化

session对象线程不安全,所以不同线程使用不用的session对象。
Session类和engine都是线程安全的,有一个就行了。


CRUD操作

create(新建,增)、read(读取,查)、update(更新,改)和delete(删除,删),简称 CRUD

# 竟然有session.commit(),一定有rollback。那这快,我们应该怎么去写呢?我们下面就不要用他这方式了
try可能出错对吧。所以说这边如果有任何问题的话。对吧,一样的,except Exception as e你可以把这个e给他print(e)打印一下。然后呢我们下面就是说一样,然后session,对吧,这个session 的话我们说这一块应该是session.rollback()回滚吧,然后finally要做什么事儿,对吧,但是我们这块pass好像没其他事可以做,对吧,来看try这块应该怎么写,比如说我们这块的话,应该是上面要建实体类吧,叫student。那student的话应该怎么写,student应该等于什么?等于Student()这()里面可以写,可以不写啊,你不要看它这个提示,这提示没有用的,我们这一块应该放的,应该是你的那些字段的值,谁等于谁,谁等于谁应该这么写。你要么不写,你就在这写student,对吧,student点儿这个时候是不是比如说,这顺序无所谓,对吧。比如说name等于tom对吧。然后呢student对吧,点age,它等于20,这是你要写东西了,这相当于建了一个实例。也就是这一个实力到时候对应你进去之后,应该对应一行。那有人说这个student 点id你怎么不写啊,我写个100可不可以呀,只要不冲突,按道理是可以的。虽然是自增字段,自增字段是什么意思啊,你不给我,我就替你在上面这个值的基础上加1给你放进来了,对吧,你给我值,只要不冲突,我就能塞进去,对吧,但是下回自增从101开始了,是这个意思。所以呢这种地方的话,就有两种写法,所以呢这种地方的话就有两种写法,你要么这种写法,要么怎么写,要么就直接写个name等于对吧,name等于jerry,对吧,student = Student('jerry')这样,那你这等于相当于是不是又在student.name = 'tom'覆盖他。对吧,所以说你看这两种方式你自己喜欢什么方式,对吧,我一般喜欢点属性的方式。因为这种点属性的方式,点出来。确实是可以点出来的是吧,可以自动提示。那比上面student = Student('jerry')这种写完就方便多了是吧,所以我一般不太喜欢上面这种写法,写完之后,他现在什么状态都不是,他只是一个实例,他现在还不能用,怎么用呢,section点add,把谁add呀,把你当前这个实例add进来session.add(student)。ok 这个时候这个实例呢,他这个时候有个状态,这个状态是pending。那也就是说这些状态他之间是可以切换的。目前这个pending也就是说我预备好了。我准备好了,但是呢因为你现在没有commit,对吧。那我们知道中间假设还有很多操作要做,对吧。那比如说你现在又建一个实例,这也是可以的。你比如说这时候再来一个student = Student()对吧?好了,这时候就变了。这时候student是被重新覆盖掉了是吧。这是一个新的实例,但是没有用,你还得必须把它add(student)塞进来了。但是既然是批量的,他说我给你提供了一个方法叫add_all()对吧,然后这个时候你就可以student = add_all([])这么写,那如果是这么写的话,那我是不是可以student1啊。 student = add_all([student,student1])这么写啊,这不就完了嘛,对吧。这样写是可以的啊,那所以说session.add(student)      student1 = Student()这一块你就没必要这么写了。那你如果说add_all的话,这种方式student = add_all([student])不可以呀,当然可以。这也可以,这都是一样的啊,这两种方案都一样,就是一个是批量的,一个是单个用的。对于单个来讲的话,你就不能用add_all这种可迭代的方式了,使用add。通过这种方法的话,我们就可以解决这样一个问题。少一样是吧,session.commit()是吧。我们commit一下就行了,也就是按照官方文档,我们现在已经写完了。我们运行一下试一下。

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    # student = Student()
    student = Student(name = 'jerry')      
    student.name = 'tom'      # 这种方式是不是相当于覆盖上面那种方式,一般看你自己喜欢哪种方式,我一般喜欢创建的时候括号一写点属性。这种点属性的方式,他点出来的,可以自动提示的,比上面那种写法方便多了。
    student.age = 20

    # session.add(student)
    # student1 = Student()

    # session.add_all([student])
    session.add(student)

    session.commit()

except Exception as e:
    print(e)
    session.rollback()



# 打印结果
2020-12-19 11:02:31,779 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 11:02:31,779 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,781 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 11:02:31,781 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,782 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 11:02:31,782 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,783 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 11:02:31,783 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,784 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 11:02:31,784 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,785 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 11:02:31,785 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,786 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 11:02:31,786 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:02:31,787 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)      # 你看这边BEGIN ,你不用关心,他说BEGIN就是开事务。然后他说的很清楚,我这隐式的开启了不用你关心。
2020-12-19 11:02:31,788 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)      # 前面一堆不用管,看这条语句你认得吧,是不是天生就给你用了参数化查询啊。
2020-12-19 11:02:31,788 INFO sqlalchemy.engine.base.Engine {'name': 'tom', 'age': 20}      # 那然后这边他是不是这样弄一个字典啊。
2020-12-19 11:02:31,788 INFO sqlalchemy.engine.base.Engine COMMIT      # 然后这边是不是COMMIT,对吧。所以他其实就这一个过程啊。
# 那如果说论我现在应该该怎么做呢?那我们可以试试这样做。那你给他一个可迭代对象。那如果说可迭代对象怎么写呢?比如说我们现在for i in range(5):给它连续的加上五条,那完全都可以这么写,对不对。student = Student(name = 'jerry')这块就没有必要这么写了,这样student = Student()写。然后呢student.name = 'tom'这个地方我们加上了一个str把这个i加上来,student.name = 'tom' + str(i)就可以了吧,对吧。然后呢student.age = 20这个地方就给他加一个i,student.age = 20 + i对吧。像我们就凑上来了,我们给他一个容器,那我们就要list,就等于他[]。就是lst = [],那这个容器应该怎么做呢?是不是每次lst.append(student)就这样加进来就行了,对吧,当然我们现在模拟批量的加入。那下面这个方法是不就把他session.add(student)给去掉了。那我们 session.add_all([student])这块要放开的话,应该就是把这个list 是直接给他就行了,session.add_all(lst)就这样。那我们看来通过这种方式的话,他下面能给我们写成什么样子呢?看好啊,之前这是一次BEGIN,一次COMMIT提交。如果是这么做,他是批量提交呢?。还是说只提交一次,关键是批量的增加而已。我们看能不能一次性给我搞定了,那他应该能做到这一点,对吧,不然的话也就太不智能了,是吧。来走一把。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    # student = Student()
    lst = []
    for i in range(5):
        student = Student()
        student.name = 'tom' + str(i)
        student.age = 20 + i
        lst.append(student)

    # session.add(student)
    # student1 = Student()

    session.add_all(lst)      # 也就是说这个时候,我们要知道加进来的时候他的是pending状态,这就是说这东西可能有用,这决定这我们以后怎么样去使用这样一个库。
    # session.add(student)

    session.commit()

except Exception as e:
    print(e)
    session.rollback()


# 打印结果
2020-12-19 11:18:52,584 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 11:18:52,584 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,586 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 11:18:52,586 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,587 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 11:18:52,587 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,588 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 11:18:52,588 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,589 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 11:18:52,589 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,590 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 11:18:52,590 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,590 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 11:18:52,591 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:18:52,592 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 11:18:52,592 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:18:52,592 INFO sqlalchemy.engine.base.Engine {'name': 'tom0', 'age': 20}
2020-12-19 11:18:52,593 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:18:52,593 INFO sqlalchemy.engine.base.Engine {'name': 'tom1', 'age': 21}
2020-12-19 11:18:52,594 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:18:52,594 INFO sqlalchemy.engine.base.Engine {'name': 'tom2', 'age': 22}
2020-12-19 11:18:52,594 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:18:52,594 INFO sqlalchemy.engine.base.Engine {'name': 'tom3', 'age': 23}
2020-12-19 11:18:52,595 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:18:52,595 INFO sqlalchemy.engine.base.Engine {'name': 'tom4', 'age': 24}
2020-12-19 11:18:52,596 INFO sqlalchemy.engine.base.Engine COMMIT      # 我们看BEGIN ,COMMIT 是不是中间来了好几下,嗯,对吧,然后最后一个COMMIT。通过这种方式的话,就可以把这些数据全部加进来。好,那这是我们增加的过程啊增加过程。所以也就是说这个时候我们要知道接他进来的时候。
# 我现在是不是没id啊。那大家能看的话,这块儿是不是相当于新建一个,然后我把这个东西是不是加进来了。然后我一提交对吧,我也提交。然后呢这一块的话,我又把他加进来了。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    student = Student()
    student.name = 'tom10'
    student.age = 30
    session.add(student)

    session.commit()

    session.add(student)
    session.commit()

except Exception as e:
    print(e)
    session.rollback()

# 打印结果
2020-12-19 11:32:49,969 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 11:32:49,969 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,971 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 11:32:49,971 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,972 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 11:32:49,972 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,973 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 11:32:49,973 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,974 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 11:32:49,974 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,975 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 11:32:49,975 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,975 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 11:32:49,975 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:32:49,976 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 11:32:49,977 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:32:49,977 INFO sqlalchemy.engine.base.Engine {'name': 'tom10', 'age': 30}
2020-12-19 11:32:49,978 INFO sqlalchemy.engine.base.Engine COMMIT      # 你看他有几句啊,这个地方就没有必要看数据库了。哎,我不是session.add(student)又加了一遍吗,然后再session.commit()嘛。跟状态有关啊,对不对?也就是说,你从session.commit()这句提交过之后,你不是因为session.add(student)把它加进来,他就能session.commit()替你提交了。对吧,他应该有点变化吧,对吧,总得有点变化吧。也就是说我提交过之后,他应该做了某种判断。他知道你这次add进来时候,你的状态没有变化。你的状态应该没有变化。你如果有变化的话,他能够感知到了。那如果能感知到的话,它底下能是一条语句嘛,至少应该是两条吧。不管是updata也好,insert也好,总之第一条就该student = Student()      student.name = 'tom10'      student.age = 30      session.add(student)这么多是吧,这肯定对应的是insert的。那你第二条session.add(student)应该是在这个基础之上应该做了一些变化,对吧,起码有个update嘛。但是没有,或者说你应该有个insert吧,也没有。所以说呢,那我们这块来改一下。比如说我把age给他们改到了41。我们看看能生成几条语句,对吧。insert有吧,updata是不是有啊。
# 那我们这块来改一下。比如说我把age给他们改到了41。我们看看能生成几条语句,对吧。insert有吧,updata是不是有啊。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    student = Student()
    student.name = 'tom10'
    student.age = 30
    session.add(student)
    # print(student.id) # 这个时间因为我没有定义,它是None,但是session.commit()这个已提交完,我紧接着print(student.id)打印的时候,实际上它提交完之后,紧接着就跟我们做一句什么,是不是查询,那做这一次查询之后呢,然后他说print(student.id)这个值我替你打印出来它是9,那这个9是怎么来的呢。你刚提交完你这个9怎么来的,我们知道呢,其实在数据库中呢,当你对主键进行,就是对某条记录进行插入的时候,他实际上说他影响的行数是你的返回值。但是他还可以通过一些其他的内部的一些变量可以拿到你刚才最后一次增加的那条数据所对应的主键值,他是可以拿到的。他是通过这种方式可以拿到,拿到之后他把这个值拿到,就可以直接批过来,然后给你把这个值查到。查完这条记录之后呢,你看到吧谁给查出来的? student.id,student.name是不是都查回来了。查过来之后,它对你做这个对象填充,做完之后呢就可以student这个对象里面的主键,至少主键是不是都有了,主键有了之后你下面所有修改它记录下来之后,最后在给你拼成 一个updata语句,你没修改的不理你了,你那个字段修改了,它就拿过来,给你拼进来,主键因为查询的时候我已经拿到了,怎么办,给你拼到后面去,作为一个where条件,然后这样的话,就可以进行更新了。就是说当你一个insert语句完了之后,只要你提交之后,下面的这个student就是从无主键的状态,变成有主键的状态,也就是说insert的时候student.id有没有值?没有吧,commit之后提交一旦成功,student.id有没有值?有,这就是一种变化,有了id一般可以updata了,没有id能不能updata?绝对不可以,它是不会做的。那做成功这句,它也要求你说这句有变化,我虽然吧id给你填充回来了,但是你要有变化,你没变化我也不做这事。这个地方是我们要注意的。 我们看查询


    session.commit()
    # print(student.id)


    student.age = 41
    session.add(student)
    session.commit()

except Exception as e:
    print(e)
    session.rollback()

# 打印结果
2020-12-19 11:43:45,357 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 11:43:45,358 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,359 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 11:43:45,359 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,360 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 11:43:45,360 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,361 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 11:43:45,361 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,362 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 11:43:45,362 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,363 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 11:43:45,363 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,364 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 11:43:45,364 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 11:43:45,365 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 11:43:45,366 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
2020-12-19 11:43:45,366 INFO sqlalchemy.engine.base.Engine {'name': 'tom10', 'age': 30}
2020-12-19 11:43:45,366 INFO sqlalchemy.engine.base.Engine COMMIT
2020-12-19 11:43:45,369 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 11:43:45,370 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name 
FROM student 
WHERE student.id = %(param_1)s
2020-12-19 11:43:45,370 INFO sqlalchemy.engine.base.Engine {'param_1': 8}
2020-12-19 11:43:45,371 INFO sqlalchemy.engine.base.Engine UPDATE student SET age=%(age)s WHERE student.id = %(student_id)s
2020-12-19 11:43:45,371 INFO sqlalchemy.engine.base.Engine {'age': 41, 'student_id': 8}
2020-12-19 11:43:45,372 INFO sqlalchemy.engine.base.Engine COMMIT      # insert有吧,updata是不是有啊。也就是说我们student.age = 41在这做一个操作之后,他应该是有了变化。如果没有这句话,我们刚才已经试过了,相当于student从上一次提交到现在是不是没有任何变化,所以他就没有给你生成第二条语句。当你说提交之后,这个student它本身是有的变化的。那我们来看这里面有个什么东西呢?你看有几条语句啊。三条吧。我们来看INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)这是不是增加了。在你做完这件事之后,他紧接着做了一件事儿,SELECT student.id AS student_id, student.name AS student_name FROM student WHERE student.id = %(param_1)s他替你查了一把,对吧,他{'param_1': 8}把8给返回回来了。然后说你现在要改41,但是你改谁呀,是不是我得知道他的id啊,对吧。所以说呢他背后做的什么事儿。他背后给你做的一次提交,这是没有问题的。它是一个insert语句。然后紧接着他去select一把,他把你刚才的提交的东西拿回来了。什么东西呀,其实就是拿你刚才提交的id啊,他把你刚才提交了id拿回来,id拿回来之后呢,他说这个东西刚才是谁谁没问题。现在你要修改,行啊,你修改的时候我就知道id是谁了,也就是说你仅仅insert,insert返回的,一般来讲insert返回的只能告诉你,你影响了几行那个数据,insert本身并不能直接返回,你刚才插入成功之后,他那个id是级几,insert本身并不能做这事儿啊。所以呢我们现在这个工具给我们做事情是什么呢?提交一个事务之后,立即给我们进行一个查询,把这个id拿回来了,拿回来之后你要修改就是这个id。这是我们讲的这个增加和修改之间的一些差异啊。。

# 批量增加 

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)

Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

try: # 可能出错
    # 加入一个实体对象,把该填的都填好
    lst=[] # 给他一个容器
    for i in range(5): #模拟批量添加,单个增加就不用fom循环了
        # student = Student(name = 'tom') # 一种写法,这里面放的应该是字段的名谁等于谁
        student = Student() # 另一种写法,如果上面的写法也执行,这句相当于覆盖
        student.name = 'tom' + str(i)# str(i)把字符串凑出来,防止冲突,name属性进行赋值,这个写法可以会有提示.name
        student.age = 20  + i # 名字也要不一样
        # student.id = 100 # 自增字段不给我值就在上面的值上加1,给他值只要不冲突就能塞进去,下会字段就从101开始了,所以这个字段一般情况下可以不写
        lst.append(student) # 每次把创建完的student添加到容器内,方便遍历,防止丢失

    # session.add(student) # 这这时student这个实例他现在有个状态这些状态之间是可以切换的目前是pending预备好了,student写完后他只是一个实例,他还不能用
    # # student1 = Student() # 在创建一个Student实例,这时候student实例就被重新覆盖掉了,这在student表示符中是一个新的实例

    # # session.add(student) # 新的实例还是需要添加进来
    # # session.add_all([student,student1]) # 这个意思是,你既然想创建多个,批量创建就是用add_all这个方法吧给一个可迭代对象
    # session.add_all([student]) # 也可以单个用,添加进来状态就等于pending预备
    session.add_all(lst) # 遍历可迭代对象,添加进来

    session.commit() # 提交
 
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
    session.rollback()  # 执行回滚
finally:
    pass


# --- 运行打印结果,这些结果为什么可以看到呢echo=True是因为这个
# 2020-08-04 04:59:04,452 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) # BEGIN 开启事务,隐式的开启了不用你关心
# 2020-08-04 04:59:04,453 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s) # 天生就做个了参数化查询%s
# 2020-08-04 04:59:04,454 INFO sqlalchemy.engine.base.Engine {'name': 'tom0', 'age': 20} # 返回一个字典
# 2020-08-04 04:59:04,455 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
# 2020-08-04 04:59:04,456 INFO sqlalchemy.engine.base.Engine {'name': 'tom1', 'age': 21}
# 2020-08-04 04:59:04,456 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
# 2020-08-04 04:59:04,457 INFO sqlalchemy.engine.base.Engine {'name': 'tom2', 'age': 22}
# 2020-08-04 04:59:04,458 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
# 2020-08-04 04:59:04,458 INFO sqlalchemy.engine.base.Engine {'name': 'tom3', 'age': 23}
# 2020-08-04 04:59:04,459 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
# 2020-08-04 04:59:04,460 INFO sqlalchemy.engine.base.Engine {'name': 'tom4', 'age': 24}
# 2020-08-04 04:59:04,462 INFO sqlalchemy.engine.base.Engine COMMIT # 执行提交,统一提交


# --- 数据库数据已添加
# MariaDB [test]> select * from student;
# MariaDB [test]> select * from student;
# +----+------+------+
# | id | name | age  |
# +----+------+------+
# |  1 | tom0 |   20 |
# |  2 | tom1 |   21 |
# |  3 | tom2 |   22 |
# |  4 | tom3 |   23 |
# |  5 | tom4 |   24 |
# +----+------+------+
# 6 rows in set (0.00 sec)
# id值是自增的

# ---

# 增加一条并修改值

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)

Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

try: # 可能出错
    student = Student() # 另一种写法,如果上面的写法也执行,这句相当于覆盖
    student.name = 'tom' # name属性进行赋值,这个写法可以会有提示.name
    student.age = 20 
    # student.id = 100 # 自增字段不给我值就在上面的值上加1,给他值只要不冲突就能塞进去,下会字段就从101开始了,所以这个字段一般情况下可以不写
    session.add(student) # 这这时student这个实例他现在有个状态这些状态之间是可以切换的目前是pending预备好了,student写完后他只是一个实例,他还不能用
    print(student.id) # 提交前可以打印一下id,第一次打印没有定义返回的事None
    # # student1 = Student() # 在创建一个Student实例,这时候student实例就被重新覆盖掉了,这在student表示符中是一个新的实例

    session.commit() # 提交,
    print(student.id )# 提交完紧接的做了一次查询,后可以打印一下id。

    student.age = 41 # 修改age值
    session.add(student) # 当age值发生改变,于之前添加的不一样,才会在将修改完的添加
    session.commit() # 在提交
 
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
    session.rollback()  # 执行回滚
finally:
    pass

# --- 运行执行结果,这些结果为什么可以看到呢echo=True是因为这个
# 2020-08-04 05:24:47,195 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-04 05:24:47,197 INFO sqlalchemy.engine.base.Engine INSERT INTO student (name, age) VALUES (%(name)s, %(age)s)
# 2020-08-04 05:24:47,197 INFO sqlalchemy.engine.base.Engine {'name': 'tom', 'age': 20}
# 2020-08-04 05:24:47,199 INFO sqlalchemy.engine.base.Engine COMMIT # 从这里往上的语句表示增加了,这里的语句越短越好,生产跟事务没关系,这里的需要把生产的完整事务做完
# 2020-08-04 05:24:47,210 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) # 上面的执行完毕后,从这里开始新的事务。
# 2020-08-04 05:24:47,212 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name 
# FROM student 
# WHERE student.id = %(param_1)s
# 2020-08-04 05:24:47,212 INFO sqlalchemy.engine.base.Engine {'param_1': 7} #刚才提交的id是什么拿回来,他提你查询了一把,并返回了一个7
# 2020-08-04 05:24:47,214 INFO sqlalchemy.engine.base.Engine UPDATE student SET age=%(age)s WHERE student.id = %(student_id)s
# 2020-08-04 05:24:47,215 INFO sqlalchemy.engine.base.Engine {'age': 41, 'student_id': 7} # 现在要改41,但是改谁的需要知道他的id,把你刚才提交的id拿回来了,在修改我就知道谁是谁了
# 2020-08-04 05:24:47,218 INFO sqlalchemy.engine.base.Engine COMMIT


简单的查
# 对于查询语句应该怎么写呢?session点query什么东西呢,一个查询语句,它不是直接写一个sql语句上去,它不是这么玩的,你这么写就错了,它的意思是什么?你要查哪个对象啊?我是用面向对象,因为我现在是在面向对象,所以你告诉我你要查哪个对象,那是什么对象啊,这个对象就是你未来要用东西,你应该告诉我你应该要查的是谁。我们session点进去看看,有这个方法对吧,来我们看到这个方法是怎么说的,因为这是Student的方法嘛。搜query,def query(self, *entities, **kwargs):他说,`_query.Query`给你一个query对象嘛。他说我给你一个新的一个query的对象。我们是不是看到有个query类啊,也就是说我会给你生成一个query类的一个实例给你,关联到谁呢?关联到跟这个Session什么相关的。也就是说用Session来查的。然后return self._query_cls(entities, self, **kwargs)你看_query_cls是不是有这个类,有这个类请你给我一个entities,这地方也没有写sql呀,或者是字符串呀,什么意思呀,这地方可以给我一批的数据是吧。当然它这块实际上两种方案都可以用的,一个是你可以放一个列表进来,这也没问题,他自己帮你迭代。再一个呢,也就是说你用逗号,像*entities这种我们说这是可变的吧,也就是说你用逗号分割可以写好几个,什么意思啊。比如说session.query(Student,Teacher)这个地方如果你有Teacher的话,可以这么写。 那怎么办呢?这个写到这什么意思啊,也就是说你未来,以前你是查什么语句,现在变成什么。因为是面向对象的方式我们现在用orm关系的话,因为你现在是不知道sql的,你是不会直接用sql语句的你现在只知道有谁,你现在目前看我们的定义,只有student,对吧,你现在在session上要用这个Student。怎么办?这个意思是说你查询哪个对象,但是你不能给他,你不能给他session.query(Student())这样写,这是不可以的。因为什么呢?这叫填充的类,什么叫填充的类,等我们写完你就知道了。点filter这个地方呢,我们能写的是谁呢?student注意也这个地方不能直接写id,因为他不知道是谁的id,Student里面是不是有类属性啊,对吧,Student.id我们要看几的,2总是有的吧,你能session.query(Student()).filter(Student.id = 2)这么写吗这叫赋值。我们写的这跟sql语句无关。是不是应该session.query(Student()).filter(Student.id == 2)这么写,这什么意思啊。这也就是说我现在给你条件。Student嘛,我给你个条件,你来给我查询一下。你查询的这个结果,我给谁呢?你不能随便给个(Student()实例啊,你给实例,玩意我查出一批,你给一个实例合适吗?那有人说我给一批实例,那太繁琐了,所以这个应该怎么写,session.query(Student).filter(Student.id == 2)应该写个类Student什么意思啊,也就是说这个是我以后的填充的模板,我有几个数据回来,我有几条数据回来,是不是这个模板来创建几个对应的实例啊。Student是个模板,用它来创建student实例的。但是因为这块Student.id那你用实例点id等于他,不是的,他现在这一块就是面向对象,你得告诉我,你是这个里面的什么样的字段,我们现在是问字段,但是因为我们现在不知道数据库。orm其实当时一个很重要的原因就是说对于编程者来讲,他对sql语句没那么厉害。就假定不会sql语句,只会面向对象,在一看,哦,我用Student,那其实里面就隐含说我应该查的是Student表对吧,然后呢,filter过滤我现在这个属性,其实它是在过滤这个字段吧,然后这个字段是不是等于什么东西,是吧。这里面当然是你写的各种条件。有了这东西之后,那我们下面一个怎么办?这东西是不是查出来,是不是一批数据啊。那这个东西查回来的东西叫什么呢?这其实叫queryobj,查回来的东西,那我们有应该怎么玩呢。我们看这个query实际上查完之后呢,这东西实际上是一个可迭代对象。那我们来看看他有没有提供这个可迭代对象呢。我应该找什么呀,看def __iter__(self),那既然,怎么办呢,for x in queryobj:       print(x)。那session.rollback()这块的话,这条语句也没必要要了,是吧?查询的时候,实际上是我们不需要事务的,对吧,到底您实现什么隔离级别,那你就默认就用什么呗。比如我们现在实现的是什么默认级别啊。来我们走一遍
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    # student = Student()
    # student.name = 'tom10'
    # student.age = 30
    # session.add(student)
    # # print(student.id) # 这个时间因为我没有定义,它是None,但是session.commit()这个已提交完,我紧接着print(student.id)打印的时候,实际上它提交完之后,紧接着就跟我们做一句什么,是不是查询,那做这一次查询之后呢,然后他说print(student.id)这个值我替你打印出来它是9,那这个9是怎么来的呢。你刚提交完你这个9怎么来的,我们知道呢,其实在数据库中呢,当你对主键进行,就是对某条记录进行插入的时候,他实际上说他影响的行数是你的返回值。但是他还可以通过一些其他的内部的一些变量可以拿到你刚才最后一次增加的那条数据所对应的主键值,他是可以拿到的。他是通过这种方式可以拿到,拿到之后他把这个值拿到,就可以直接批过来,然后给你把这个值查到。查完这条记录之后呢,你看到吧谁给查出来的? student.id,student.name是不是都查回来了。查过来之后,它对你做这个对象填充,做完之后呢就可以student这个对象里面的主键,至少主键是不是都有了,主键有了之后你下面所有修改它记录下来之后,最后在给你拼成 一个updata语句,你没修改的不理你了,你那个字段修改了,它就拿过来,给你拼进来,主键因为查询的时候我已经拿到了,怎么办,给你拼到后面去,作为一个where条件,然后这样的话,就可以进行更新了。就是说当你一个insert语句完了之后,只要你提交之后,下面的这个student就是从无主键的状态,变成有主键的状态,也就是说insert的时候student.id有没有值?没有吧,commit之后提交一旦成功,student.id有没有值?有,这就是一种变化,有了id一般可以updata了,没有id能不能updata?绝对不可以,它是不会做的。那做成功这句,它也要求你说这句有变化,我虽然吧id给你填充回来了,但是你要有变化,你没变化我也不做这事。这个地方是我们要注意的。 我们看查询

    # session.commit()
    # # print(student.id)

    # student.age = 41
    # session.add(student)
    # session.commit()

    queryobj = session.query(Student).filter(Student.id == 2)
    for x in queryobj:
        print(x)

except Exception as e:
    print(e)

finally:
    pass


# 打印结果
2020-12-19 12:59:58,664 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 12:59:58,665 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,666 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 12:59:58,666 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,667 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 12:59:58,667 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,668 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 12:59:58,669 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,671 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 12:59:58,671 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,672 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 12:59:58,672 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,673 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 12:59:58,673 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 12:59:58,674 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 12:59:58,675 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age 
FROM student 
WHERE student.id = %(id_1)s
2020-12-19 12:59:58,675 INFO sqlalchemy.engine.base.Engine {'id_1': 2}
<Student: id:2 name:tom0 age:20>      #  我们来看,他写的时候是不是就SELECT写这么一条语句啊,按你条件写之后,是不是就可以达到我们的要求,是不是<Student: id:2 name:tom0 age:20>返回一条啊。虽然你迭代了,对不起,是不是一下就过了。那你如果写了一个,比如说写个Student.id == 200怎么样,那该查还得查呀,查不过来,我queryobj这里面虽然可迭代,但是没有元素啊。没有元素怎么没有元素就不进for去呗。所以说这就是我们要解决的问题。那通过这样一个方式,我们就完成了简单的查询。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)

Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

try: # 可能出错
    queryobj = session.query(Student).filter(Student.id == 2) # 我应该查的是Student表,过滤属性,相当于过滤字段。Student这是是以后填充的模板,我查询返回几条数据回来就用Student这个模板创建几个对应的实例 。filter(Student.id == 2),filter筛选条件,(Student.id == 2),这张表类下id属性,的这里不能直接写id,因为他不知道是谁的id,==判断等于什么,这里不能给sql语句,需要使用面向对象的方式进行查询,告诉query我要查询哪个对象可以是多个,也可以是一个可迭代对象。(Student)表示查询这张表
    for x in queryobj: # 返回的是一个可迭代对象,遍历出来
        print(x)
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
finally:
    pass

# --- 运行结果
# WHERE student.id = %(id_1)s
# 2020-08-04 07:14:07,837 INFO sqlalchemy.engine.base.Engine {'id_1': 2}
# <Student id:2 name:tom0 age:20>


  先查回来,修改后,在提交更改。
# 我们谁顺道也讲了怎么去改。要改之前呢,他必须替你把他的主键拿出来。因为改的时候,改谁,他必须得知道。他必须主键拿回来,没有主键是不行的。所以说呢我们马上要试一下这个改,但是查我们现在再看一遍啊。我们现在来看这个改。把session.rollback()这个写上。然后这样,那人说你这样没有ID,肯定有问题吧。对吧,因为现在是没有id的嘛,你现在是没有id 的话,那你这样改,你想你以为你能糊弄住我,肯定不行,对吧。因为这个东西没有id的话,语句都拼不出来。对吧,如果你想这么做,相当于是找等于41,玩意等于41的太多了怎么办,对吧,肯定不可能这么做。但是呢我们看看我student点id等于2什么意思,相当于我是想把,我的目的是想把id等于2的年龄,是不是给改成42。然后如果成功的话,这数据库里面的值刷一遍,这里面id等于2也就tom对应的年龄就不等于20了。这是我们想要看到的东西。如果出现失败,回滚,走一下。
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Date,Enum
from sqlalchemy.orm import sessionmaker

# 实体类的基类
Base = declarative_base()

# 实体类
class Student(Base):
    __tablename__ = 'student'

    id = Column(Integer,primary_key=True,nullable=False,autoincrement=True)
    name = Column(String(64),nullable=False)
    age = Column(Integer)

    def __repr__(self):
        return "<{}: id:{} name:{} age:{}>".format(self.__class__.__name__,self.id,self.name,self.age)

    __str__ = __repr__

# 引擎,管理连接池
host = '127.0.0.1'
port = 3306
user = 'root'
password = 'fspass' 
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)

engine = sqlalchemy.create_engine(conn_str,echo=True)

# Base.metadata.create_all(engine)
# Base.metadata.drop_all(engine)

# 这首先拿到一个Session类,这是拿到一个class
Session = sessionmaker()

# 这里是拿到一个实例,就是instance
session = Session(bind=engine)

try:
    student = Student()
    # student.name = 'tom10'
    # student.age = 30
    # session.add(student)
    # # print(student.id) # 这个时间因为我没有定义,它是None,但是session.commit()这个已提交完,我紧接着print(student.id)打印的时候,实际上它提交完之后,紧接着就跟我们做一句什么,是不是查询,那做这一次查询之后呢,然后他说print(student.id)这个值我替你打印出来它是9,那这个9是怎么来的呢。你刚提交完你这个9怎么来的,我们知道呢,其实在数据库中呢,当你对主键进行,就是对某条记录进行插入的时候,他实际上说他影响的行数是你的返回值。但是他还可以通过一些其他的内部的一些变量可以拿到你刚才最后一次增加的那条数据所对应的主键值,他是可以拿到的。他是通过这种方式可以拿到,拿到之后他把这个值拿到,就可以直接批过来,然后给你把这个值查到。查完这条记录之后呢,你看到吧谁给查出来的? student.id,student.name是不是都查回来了。查过来之后,它对你做这个对象填充,做完之后呢就可以student这个对象里面的主键,至少主键是不是都有了,主键有了之后你下面所有修改它记录下来之后,最后在给你拼成 一个updata语句,你没修改的不理你了,你那个字段修改了,它就拿过来,给你拼进来,主键因为查询的时候我已经拿到了,怎么办,给你拼到后面去,作为一个where条件,然后这样的话,就可以进行更新了。就是说当你一个insert语句完了之后,只要你提交之后,下面的这个student就是从无主键的状态,变成有主键的状态,也就是说insert的时候student.id有没有值?没有吧,commit之后提交一旦成功,student.id有没有值?有,这就是一种变化,有了id一般可以updata了,没有id能不能updata?绝对不可以,它是不会做的。那做成功这句,它也要求你说这句有变化,我虽然吧id给你填充回来了,但是你要有变化,你没变化我也不做这事。这个地方是我们要注意的。 我们看查询

    # session.commit()
    # # print(student.id)
    student.id = 2
    student.age = 41
    session.add(student)
    session.commit()

    # queryobj = session.query(Student).filter(Student.id == 2)
    # for x in queryobj:
    #     print(x)

except Exception as e:
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    print(e)
    session.rollback()

finally:
    pass



# 执行一下(出错了吧,他说insert,他想给你insert去看见没有,哎,我明明id给你了呀。对吧,我id要给你,你现在给我来个insert的,你这是什么意思啊?我id前面没给你,帮我indert,我现在id你给你了,你还给我这么做,对不对,但是你要知道一点,这里面跟我们刚才这种写法是有差异的。我们是先增加呢,然后他替我们做的一次查询,查询完了之后,然后才替我们再做提交的时候,因为他有id了。那你这个时候来,你跟我现在有id不是一样的道理吗?不一样,因为状态不一样。这是我们马上要给大家介绍的状态的问题。所以你要这么写,他就认为去增加,然后他一定会给你报一个什么问题啊。主键冲突问题。因为2这个属性有了,你还写个insert into,你这明摆的主键冲突嘛,对吧,所以这种写法。这种写法就不是我们所想象的update了。你这个地方写进来,就相当于一个全新的一个student。那我们现在对比一下,刚才我们是先commit然后进行了修改,然后再commit他就能是updata。那现在到底差在什么地方?差就差在,查那么一下下,或者要么就差在提交那么一下子。总之在这个提交之后他student一下能帮我们做一些状态上的一些变化,这样我们才能做这事儿,对吧,反正就在这一块儿替我们做这事。总之这样修改是不可以的。刚才那个修改大家还记得吗。好,查不用多说对吧,改呢,我们发现直接这么去改是不可以的,我们要怎么改呢?必须先查回来再修改,再提交。怎么查,来,我们来看一下,这 queryobj = session.query(Student).filter(Student.id == 2)是我们查的语句吧,对不对,我们通过这种方式是可以把他查回来,但这个查完以后是不是变成可迭代对象,这不是我们想要的。所以我们想把它能不能给我们装到一个实例。那些当然这个方法有很多。我们这块给大家提供一个方法,get方法。get方法就是通过主键给我进行查询的,那怎么写呢?来,我们来试一下。)
2020-12-19 14:51:48,108 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
2020-12-19 14:51:48,108 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,109 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
2020-12-19 14:51:48,109 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,110 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
2020-12-19 14:51:48,110 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,111 INFO sqlalchemy.engine.base.Engine show collation where `Charset` = 'utf8mb4' and `Collation` = 'utf8mb4_bin'
2020-12-19 14:51:48,111 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,112 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS CHAR(60)) AS anon_1
2020-12-19 14:51:48,112 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,113 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS CHAR(60)) AS anon_1
2020-12-19 14:51:48,113 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,114 INFO sqlalchemy.engine.base.Engine SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_bin AS anon_1
2020-12-19 14:51:48,114 INFO sqlalchemy.engine.base.Engine {}
2020-12-19 14:51:48,115 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2020-12-19 14:51:48,116 INFO sqlalchemy.engine.base.Engine INSERT INTO student (id, name, age) VALUES (%(id)s, %(name)s, %(age)s)
2020-12-19 14:51:48,116 INFO sqlalchemy.engine.base.Engine {'id': 2, 'name': None, 'age': 41}
2020-12-19 14:51:48,116 INFO sqlalchemy.engine.base.Engine ROLLBACK
~~~~~~~~~~~~~~~~~~~~~~~~~~~
(pymysql.err.IntegrityError) (1048, "Column 'name' cannot be null")
[SQL: INSERT INTO student (id, name, age) VALUES (%(id)s, %(name)s, %(age)s)]
[parameters: {'id': 2, 'name': None, 'age': 41}]
(Background on this error at: http://sqlalche.me/e/13/gkpj)
[root@ecs-kc1-large-2-linux-20200825091713 ~]# 
# 我们这块给大家提供一个方法,get方法。get方法就是通过主键给我进行查询的,那怎么写呢?来,我们来试一下。依然通过student表来查,查到的数据(拿到的数据),依然必须用student这个东西,来进行填充,生成一个Student类的实例,get要求必须使用主键,主键肯定保证的是是唯一的吧,要么查到要么就没有(要么有要么没有)这个stduent要么是none,要么就不是none,不存在stduents的情况加个s表示多个。

```py
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '172.17.112.87'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

try: # 可能出错
        # 修改必须,先查回来,修改后,在提交更改。
        student = session.query(Student).get(2) # 拿回第二条记录, get()方法就是通过主键构建查询的,在我们这就是id
        # student.id = 2 # 我们既然已经使用get方法拿到主键,都能查回一条记录,我们查目的是为了改,那student.id = 2修改主键的操作就不要做了

        student.age = 44 # 现在student.age有某种变化了,而且我是从数据库中给你查回来的保证必须有数据,有数据然后进行修改,修改后把你注册到session.add()中说我的值有变化了,来你注册一下,session在提交的时候就会发现这种变化,并把它生成相应的sql语句然后提交到数据中去,如果出现任何异常session.rollback()回滚
        session.add(student) # 这个add不是对应这sql里的insert插入语句。  add()只要你现在你觉得你想把这个数据以后被我提交,你就应该在我session这边注册一下,然后session替你来做commit()提交,把你让我提交的拿过来,要提交谁在我这里注册一下
        session.commit()  # 首先使用get查回来了,而且能保证是有数据的,然后进行修改,修改完以后add注册到这个session中,表示我这有变化了,到你这注册一下,session在提交的时候就会,发现这种变化,并把他生成相应的sql语句,然后提交到数据库中去。
#     queryobj = session.query(Student).filter(Student.id == 2) # 我应该查的是Student表,过滤属性,相当于过滤字段。Student这是是以后填充的模板,我查询返回几条数据回来就用Student这个模板创建几个对应的实例 。filter(Student.id == 2),filter筛选条件,(Student.id == 2),这张表类下id属性,的这里不能直接写id,因为他不知道是谁的id,==判断等于什么,这里不能给sql语句,需要使用面向对象的方式进行查询,告诉query我要查询哪个对象可以是多个,也可以是一个可迭代对象。(Student)表示查询这张表
#     for x in queryobj: # 返回的是一个可迭代对象,遍历出来
#         print(x)
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
    session.rollback()
finally:
    pass

# --- 运行结果
# 2020-08-01 07:11:18,470 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) # 在一个事务内
# 2020-08-01 07:11:18,471 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age 
# FROM student 
# WHERE student.id = %(param_1)s
# 2020-08-01 07:11:18,471 INFO sqlalchemy.engine.base.Engine {'param_1': 2} # 首先按照主键给你SELECT一把
# 2020-08-01 07:11:18,473 INFO sqlalchemy.engine.base.Engine UPDATE student SET age=%(age)s WHERE student.id = %(student_id)s
# 2020-08-01 07:11:18,473 INFO sqlalchemy.engine.base.Engine {'age': 44, 'student_id': 2} # 然后给你UPDATE
# 2020-08-01 07:11:18,491 INFO sqlalchemy.engine.base.Engine COMMIT # 然后提交   # 大家看他是怎么写的,?首先按照主键给你SELECT一把啊,这是在一个事务内对吧,因为我们还没提交呢,然后紧接着他会做什么事儿呢?他说你下来我就能给你变成一个updata语句,看来他所谓的智能是必须你得达到某种状态之后,他才能给你做所谓智能化。否则的话他会把它当insert语句来处理。那这就是所有的orm,基本上你去看,你学会我们现在这个orm工具,你换一个orm工具,它基本上也是这么写的。所以里面都会提到一个东西叫状态啊,好,通过这样的方式就可以进行修改了。好,这是修改过程两种,我们可以看到两种,第一种先增后改,先增的话能增加完之后,如果你要改造,它立马为你做一个select语句,然后把id拿过来,然后你就可以改。然后呢如果我们要直接改怎么办?直接改是不可以的,你必须先查。查回来之后,然后在这个地方改,改完之后再提交,他就会给你变成updata语句。为什么这么做呢,为什么要用orm原因从这里面已经看到了,我所有的操作都是面向对象来操作的。以后我暴露给你的方法就会把这些东西全部给你封装成一个新的方法。比如说我以后就告诉你这个叫什么修改name或者修age这样的方法你再也看不到,这都叫原始的东西的,你看不见了,我就会用新的类,新的方法,重新封装。你到时候调这些方法就可以了。你就根本就碰不到这么原始的修改方式了。可以提供在封装,用户只需要给函数传参数就行了。
      


Instance “<Student at ex3e654ea>“ is not persisted 未持久的异常!

每一个实体,都有一个状态属性_sa_instance_state ,其类型是sqlalchemy.orm.state.InstanceState
可以使用sqlalchemy.inspect(entity)函数查看状态。
常见的状态值有transient, pending,persistent, deleted, detached,

状态 说明
transient 实体类尚未加入到session中,同时并没有保存到数据库中
pending transient的实体被add()到session中,状态切换到pending ,但它还没有flush到数据库中
persistent session中的实体对象对应着数据库中的真实记录.pending状态在提交成功后可以变成persistent状态,或者查询成功返回的实体也是persistent状态
deleted 实体被删除且已经flush但未commit完成。事务提交成功了,实体变成detached,事务失败,返回persistent状态
detached 删除成功的实体进入这个状态

新建一个实体,状态是transient临时的。
一旦add()后从transient变成pending状态。
成功commit()后从pending变成persistent状态。
成功查询返回的实体对象,也是persistent状态。

persistent状态的实体,修改依然是persistent状态。

persistent状态的实体,删除后, flush后但没有commit,就变成deteled状态,成功提交,变为detached状态
提交失败,还原到persistent状态.flush方法,主动把改变应用到数据库中去。

删除、修改操作,需要对应一个真实的记录,所以要求实体对象是persistent状态。修改前必须是persistent状态,所以说修改后(提交后)还必须是persistent状态,修改前后状态不会变化。 当你删除的时候,他会变成deteled,当你提交成功之后,他就从deteled变成detached,就是说从你数据库中分离了,这是没人要的东西了,彻底删除了,这是状态的变化。

# 查看状态,导入inspect函数
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '172.17.96.98'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):  # 创建一个函数用来查看状态
    ins = inspect(entity)
    print('状态信息','transient=',ins.transient,'
','pending=',ins.pending ,'
','persistent=',ins.persistent,'
','deleted=',ins.deleted,'
','detached=',ins.detached)

try: # 可能出错
        # 修改必须,先查回来,修改后,在提交更改。
        student = session.query(Student).get(3) # 拿回第二条记录, get()方法就是通过主键构建查询的,在我们这就是id

        session.delete(student)
        show(student) # 每一个步骤状态都不一样
        session.flush()
        show(student) # 每一个步骤状态都不一样
        # session.commit()  # 注意提交了就真删除了,状态为detached
#     queryobj = session.query(Student).filter(Student.id == 2) # 我应该查的是Student表,过滤属性,相当于过滤字段。Student这是是以后填充的模板,我查询返回几条数据回来就用Student这个模板创建几个对应的实例 。filter(Student.id == 2),filter筛选条件,(Student.id == 2),这张表类下id属性,的这里不能直接写id,因为他不知道是谁的id,==判断等于什么,这里不能给sql语句,需要使用面向对象的方式进行查询,告诉query我要查询哪个对象可以是多个,也可以是一个可迭代对象。(Student)表示查询这张表
#     for x in queryobj: # 返回的是一个可迭代对象,遍历出来
#         print(x)
        session.rollback()
        show(student) # 每一个步骤状态都不一样
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
    session.rollback()
finally:
    pass

# 2020-08-06 22:38:06,807 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-06 22:38:06,808 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age 
# FROM student 
# WHERE student.id = %(param_1)s
# 2020-08-06 22:38:06,809 INFO sqlalchemy.engine.base.Engine {'param_1': 3}
# 状态信息  transient= False pending= False persistent= True deleted= False detached= False
# 2020-08-06 22:38:06,834 INFO sqlalchemy.engine.base.Engine DELETE FROM student WHERE student.id = %(id)s
# 2020-08-06 22:38:06,834 INFO sqlalchemy.engine.base.Engine {'id': 3}
# 状态信息  transient= False pending= False persistent= False deleted= True detached= False
# 2020-08-06 22:38:06,835 INFO sqlalchemy.engine.base.Engine ROLLBACK
# 状态信息  transient= False pending= False persistent= True deleted= False detached= False




import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum # 如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '172.17.112.87'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

try: # 可能出错
        # 删除必须,先查回来,后删,在提交更改
        student = session.query(Student).get(2) # 拿回第二条记录, get()方法就是通过主键构建查询的,在我们这就是id

       
        session.delete(student) # delete()在改之前必须知道你这个id是谁,但是你知道不顶用,你必须从数据库中查一把回来,然后你的状态就达到了某种要求了,然后就可以进行修改,修改后就可以提交就会生成update语句,删除也是一样,你就应该在我session这边注册一下,然后session替你来做commit()提交,把你让我提交的拿过来,要提交谁在我这里注册一下
        session.commit()
#     queryobj = session.query(Student).filter(Student.id == 2) # 我应该查的是Student表,过滤属性,相当于过滤字段。Student这是是以后填充的模板,我查询返回几条数据回来就用Student这个模板创建几个对应的实例 。filter(Student.id == 2),filter筛选条件,(Student.id == 2),这张表类下id属性,的这里不能直接写id,因为他不知道是谁的id,==判断等于什么,这里不能给sql语句,需要使用面向对象的方式进行查询,告诉query我要查询哪个对象可以是多个,也可以是一个可迭代对象。(Student)表示查询这张表
#     for x in queryobj: # 返回的是一个可迭代对象,遍历出来
#         print(x)
except Exception as e:
    print('~~~~~~~~~~~') # 为了有异常看的清楚
    print(e) # 打印一下报错
    session.rollback()
finally:
    pass

# --- 运行结果
# 2020-08-01 07:11:18,470 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) # 在一个事务内
# 2020-08-01 07:11:18,471 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age 
# FROM student 
# WHERE student.id = %(param_1)s
# 2020-08-01 07:11:18,471 INFO sqlalchemy.engine.base.Engine {'param_1': 2} # 首先按照主键给你SELECT一把
# 2020-08-01 07:11:18,473 INFO sqlalchemy.engine.base.Engine UPDATE student SET age=%(age)s WHERE student.id = %(student_id)s
# 2020-08-01 07:11:18,473 INFO sqlalchemy.engine.base.Engine {'age': 44, 'student_id': 2} # 然后给你UPDATE
# 2020-08-01 07:11:18,491 INFO sqlalchemy.engine.base.Engine COMMIT # 然后提交



导入测试数据

导入到数据库
linux导入sql文件,sql文件在本地

[root@localhost ~]# mysql -uroot -p < test.sql


test.sql文件


/*
Create By Wayne

Source Server         : python
Source Server Version : 50545
Source Host           : 192.168.142.135:3306
Source Database       : test

Target Server Type    : MYSQL
Target Server Version : 50545
File Encoding         : 65001

Date: 2017-10-01 20:27:47

http://www.magedu.com
*/
DROP DATABASE IF EXISTS test;
CREATE DATABASE IF NOT EXISTS test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE test;

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for departments
-- ----------------------------
DROP TABLE IF EXISTS `departments`;
CREATE TABLE `departments` (
  `dept_no` char(4) NOT NULL,
  `dept_name` varchar(40) NOT NULL,
  PRIMARY KEY (`dept_no`),
  UNIQUE KEY `dept_name` (`dept_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of departments
-- ----------------------------
INSERT INTO `departments` VALUES ('d009', 'Customer Service');
INSERT INTO `departments` VALUES ('d005', 'Development');
INSERT INTO `departments` VALUES ('d002', 'Finance');
INSERT INTO `departments` VALUES ('d003', 'Human Resources');
INSERT INTO `departments` VALUES ('d001', 'Marketing');
INSERT INTO `departments` VALUES ('d004', 'Production');
INSERT INTO `departments` VALUES ('d006', 'Quality Management');
INSERT INTO `departments` VALUES ('d008', 'Research');
INSERT INTO `departments` VALUES ('d007', 'Sales');

-- ----------------------------
-- Table structure for dept_emp
-- ----------------------------
DROP TABLE IF EXISTS `dept_emp`;
CREATE TABLE `dept_emp` (
  `emp_no` int(11) NOT NULL,
  `dept_no` char(4) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date NOT NULL,
  PRIMARY KEY (`emp_no`,`dept_no`),
  KEY `dept_no` (`dept_no`),
  CONSTRAINT `dept_emp_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE,
  CONSTRAINT `dept_emp_ibfk_2` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of dept_emp
-- ----------------------------
INSERT INTO `dept_emp` VALUES ('10001', 'd005', '1986-06-26', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10002', 'd007', '1996-08-03', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10003', 'd004', '1995-12-03', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10004', 'd004', '1986-12-01', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10005', 'd003', '1989-09-12', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10006', 'd005', '1990-08-05', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10007', 'd008', '1989-02-10', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10008', 'd005', '1998-03-11', '2000-07-31');
INSERT INTO `dept_emp` VALUES ('10009', 'd006', '1985-02-18', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10010', 'd004', '1996-11-24', '2000-06-26');
INSERT INTO `dept_emp` VALUES ('10010', 'd006', '2000-06-26', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10011', 'd009', '1990-01-22', '1996-11-09');
INSERT INTO `dept_emp` VALUES ('10012', 'd005', '1992-12-18', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10013', 'd003', '1985-10-20', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10014', 'd005', '1993-12-29', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10015', 'd008', '1992-09-19', '1993-08-22');
INSERT INTO `dept_emp` VALUES ('10016', 'd007', '1998-02-11', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10017', 'd001', '1993-08-03', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10018', 'd004', '1992-07-29', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10018', 'd005', '1987-04-03', '1992-07-29');
INSERT INTO `dept_emp` VALUES ('10019', 'd008', '1999-04-30', '9999-01-01');
INSERT INTO `dept_emp` VALUES ('10020', 'd004', '1997-12-30', '9999-01-01');

-- ----------------------------
-- Table structure for employees
-- ----------------------------
DROP TABLE IF EXISTS `employees`;
CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of employees
-- ----------------------------
INSERT INTO `employees` VALUES ('10001', '1953-09-02', 'Georgi', 'Facello', 'M', '1986-06-26');
INSERT INTO `employees` VALUES ('10002', '1964-06-02', 'Bezalel', 'Simmel', 'F', '1985-11-21');
INSERT INTO `employees` VALUES ('10003', '1959-12-03', 'Parto', 'Bamford', 'M', '1986-08-28');
INSERT INTO `employees` VALUES ('10004', '1954-05-01', 'Chirstian', 'Koblick', 'M', '1986-12-01');
INSERT INTO `employees` VALUES ('10005', '1955-01-21', 'Kyoichi', 'Maliniak', 'M', '1989-09-12');
INSERT INTO `employees` VALUES ('10006', '1953-04-20', 'Anneke', 'Preusig', 'F', '1989-06-02');
INSERT INTO `employees` VALUES ('10007', '1957-05-23', 'Tzvetan', 'Zielinski', 'F', '1989-02-10');
INSERT INTO `employees` VALUES ('10008', '1958-02-19', 'Saniya', 'Kalloufi', 'M', '1994-09-15');
INSERT INTO `employees` VALUES ('10009', '1952-04-19', 'Sumant', 'Peac', 'F', '1985-02-18');
INSERT INTO `employees` VALUES ('10010', '1963-06-01', 'Duangkaew', 'Piveteau', 'F', '1989-08-24');
INSERT INTO `employees` VALUES ('10011', '1953-11-07', 'Mary', 'Sluis', 'F', '1990-01-22');
INSERT INTO `employees` VALUES ('10012', '1960-10-04', 'Patricio', 'Bridgland', 'M', '1992-12-18');
INSERT INTO `employees` VALUES ('10013', '1963-06-07', 'Eberhardt', 'Terkki', 'M', '1985-10-20');
INSERT INTO `employees` VALUES ('10014', '1956-02-12', 'Berni', 'Genin', 'M', '1987-03-11');
INSERT INTO `employees` VALUES ('10015', '1959-08-19', 'Guoxiang', 'Nooteboom', 'M', '1987-07-02');
INSERT INTO `employees` VALUES ('10016', '1961-05-02', 'Kazuhito', 'Cappelletti', 'M', '1995-01-27');
INSERT INTO `employees` VALUES ('10017', '1958-07-06', 'Cristinel', 'Bouloucos', 'F', '1993-08-03');
INSERT INTO `employees` VALUES ('10018', '1954-06-19', 'Kazuhide', 'Peha', 'F', '1987-04-03');
INSERT INTO `employees` VALUES ('10019', '1953-01-23', 'Lillian', 'Haddadi', 'M', '1999-04-30');
INSERT INTO `employees` VALUES ('10020', '1952-12-24', 'Mayuko', 'Warwick', 'M', '1991-01-26');

-- ----------------------------
-- Table structure for salaries
-- ----------------------------
DROP TABLE IF EXISTS `salaries`;
CREATE TABLE `salaries` (
  `emp_no` int(11) NOT NULL,
  `salary` int(11) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date NOT NULL,
  PRIMARY KEY (`emp_no`,`from_date`),
  CONSTRAINT `salaries_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of salaries
-- ----------------------------
INSERT INTO `salaries` VALUES ('10001', '60117', '1986-06-26', '1987-06-26');
INSERT INTO `salaries` VALUES ('10001', '62102', '1987-06-26', '1988-06-25');
INSERT INTO `salaries` VALUES ('10001', '66074', '1988-06-25', '1989-06-25');
INSERT INTO `salaries` VALUES ('10001', '66596', '1989-06-25', '1990-06-25');
INSERT INTO `salaries` VALUES ('10001', '66961', '1990-06-25', '1991-06-25');
INSERT INTO `salaries` VALUES ('10001', '71046', '1991-06-25', '1992-06-24');
INSERT INTO `salaries` VALUES ('10001', '74333', '1992-06-24', '1993-06-24');
INSERT INTO `salaries` VALUES ('10001', '75286', '1993-06-24', '1994-06-24');
INSERT INTO `salaries` VALUES ('10001', '75994', '1994-06-24', '1995-06-24');
INSERT INTO `salaries` VALUES ('10001', '76884', '1995-06-24', '1996-06-23');
INSERT INTO `salaries` VALUES ('10001', '80013', '1996-06-23', '1997-06-23');
INSERT INTO `salaries` VALUES ('10001', '81025', '1997-06-23', '1998-06-23');
INSERT INTO `salaries` VALUES ('10001', '81097', '1998-06-23', '1999-06-23');
INSERT INTO `salaries` VALUES ('10001', '84917', '1999-06-23', '2000-06-22');
INSERT INTO `salaries` VALUES ('10001', '85112', '2000-06-22', '2001-06-22');
INSERT INTO `salaries` VALUES ('10001', '85097', '2001-06-22', '2002-06-22');
INSERT INTO `salaries` VALUES ('10001', '88958', '2002-06-22', '9999-01-01');
INSERT INTO `salaries` VALUES ('10002', '65828', '1996-08-03', '1997-08-03');
INSERT INTO `salaries` VALUES ('10002', '65909', '1997-08-03', '1998-08-03');
INSERT INTO `salaries` VALUES ('10002', '67534', '1998-08-03', '1999-08-03');
INSERT INTO `salaries` VALUES ('10002', '69366', '1999-08-03', '2000-08-02');
INSERT INTO `salaries` VALUES ('10002', '71963', '2000-08-02', '2001-08-02');
INSERT INTO `salaries` VALUES ('10002', '72527', '2001-08-02', '9999-01-01');
INSERT INTO `salaries` VALUES ('10003', '40006', '1995-12-03', '1996-12-02');
INSERT INTO `salaries` VALUES ('10003', '43616', '1996-12-02', '1997-12-02');
INSERT INTO `salaries` VALUES ('10003', '43466', '1997-12-02', '1998-12-02');
INSERT INTO `salaries` VALUES ('10003', '43636', '1998-12-02', '1999-12-02');
INSERT INTO `salaries` VALUES ('10003', '43478', '1999-12-02', '2000-12-01');
INSERT INTO `salaries` VALUES ('10003', '43699', '2000-12-01', '2001-12-01');
INSERT INTO `salaries` VALUES ('10003', '43311', '2001-12-01', '9999-01-01');
INSERT INTO `salaries` VALUES ('10004', '40054', '1986-12-01', '1987-12-01');
INSERT INTO `salaries` VALUES ('10004', '42283', '1987-12-01', '1988-11-30');
INSERT INTO `salaries` VALUES ('10004', '42542', '1988-11-30', '1989-11-30');
INSERT INTO `salaries` VALUES ('10004', '46065', '1989-11-30', '1990-11-30');
INSERT INTO `salaries` VALUES ('10004', '48271', '1990-11-30', '1991-11-30');
INSERT INTO `salaries` VALUES ('10004', '50594', '1991-11-30', '1992-11-29');
INSERT INTO `salaries` VALUES ('10004', '52119', '1992-11-29', '1993-11-29');
INSERT INTO `salaries` VALUES ('10004', '54693', '1993-11-29', '1994-11-29');
INSERT INTO `salaries` VALUES ('10004', '58326', '1994-11-29', '1995-11-29');
INSERT INTO `salaries` VALUES ('10004', '60770', '1995-11-29', '1996-11-28');

-- ----------------------------
-- Table structure for titles
-- ----------------------------
DROP TABLE IF EXISTS `titles`;
CREATE TABLE `titles` (
  `emp_no` int(11) NOT NULL,
  `title` varchar(50) NOT NULL,
  `from_date` date NOT NULL,
  `to_date` date DEFAULT NULL,
  PRIMARY KEY (`emp_no`,`title`,`from_date`),
  CONSTRAINT `titles_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of titles
-- ----------------------------
INSERT INTO `titles` VALUES ('10001', 'Senior Engineer', '1986-06-26', '9999-01-01');
INSERT INTO `titles` VALUES ('10002', 'Staff', '1996-08-03', '9999-01-01');
INSERT INTO `titles` VALUES ('10003', 'Senior Engineer', '1995-12-03', '9999-01-01');
INSERT INTO `titles` VALUES ('10004', 'Engineer', '1986-12-01', '1995-12-01');
INSERT INTO `titles` VALUES ('10004', 'Senior Engineer', '1995-12-01', '9999-01-01');
INSERT INTO `titles` VALUES ('10005', 'Senior Staff', '1996-09-12', '9999-01-01');
INSERT INTO `titles` VALUES ('10005', 'Staff', '1989-09-12', '1996-09-12');
INSERT INTO `titles` VALUES ('10006', 'Senior Engineer', '1990-08-05', '9999-01-01');
INSERT INTO `titles` VALUES ('10007', 'Senior Staff', '1996-02-11', '9999-01-01');
INSERT INTO `titles` VALUES ('10007', 'Staff', '1989-02-10', '1996-02-11');
INSERT INTO `titles` VALUES ('10008', 'Assistant Engineer', '1998-03-11', '2000-07-31');
INSERT INTO `titles` VALUES ('10009', 'Assistant Engineer', '1985-02-18', '1990-02-18');
INSERT INTO `titles` VALUES ('10009', 'Engineer', '1990-02-18', '1995-02-18');
INSERT INTO `titles` VALUES ('10009', 'Senior Engineer', '1995-02-18', '9999-01-01');
INSERT INTO `titles` VALUES ('10010', 'Engineer', '1996-11-24', '9999-01-01');
INSERT INTO `titles` VALUES ('10011', 'Staff', '1990-01-22', '1996-11-09');
INSERT INTO `titles` VALUES ('10012', 'Engineer', '1992-12-18', '2000-12-18');
INSERT INTO `titles` VALUES ('10012', 'Senior Engineer', '2000-12-18', '9999-01-01');
INSERT INTO `titles` VALUES ('10013', 'Senior Staff', '1985-10-20', '9999-01-01');
INSERT INTO `titles` VALUES ('10014', 'Engineer', '1993-12-29', '9999-01-01');
INSERT INTO `titles` VALUES ('10015', 'Senior Staff', '1992-09-19', '1993-08-22');



复杂查询

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数,查询一般都是查多个(查到一堆东西),凡是可迭代对象,都给你挨个打印出来
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'

class Employee(Base): # 构建Employee表类,对照这上面的sql语句填写,继承至Base
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    emp_no = Column(Integer,primary_key = True) # Column()表示数据库表中的列。Integer表示类型是int类型,primary_key = True设置为主键
    birth_date =Column(Date,nullable = False) # Date类型,不允许为空nullable = False
    first_name =Column(String(14),nullable = False) # String(14)字符串类型
    last_name = Column(String(16),nullable = False)
    # gender = Column(MyEnum,nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块,在写一个枚举类
    hire_date = Column(Date,nullable = False)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<self.__class__.__name__= {}    emp_no:{}    name:{}>".format(self.__class__.__name__, self.emp_no, "self.first_name={}   .  self.last_name={}".format(self.first_name,self.last_name)) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__

############################################ 简单条件查询
emps = session.query(Employee).filter(Employee.emp_no > 10015) # 查询Employee表类,filter就是一个条件(filter都是写在where中的)
show(emps)

############################################ 与或非
from sqlalchemy import or_,not_,and_ # 与或非要么去这个sqlalchemy 名词空间中导入,这是一种逻辑判断需要导入,从sqlalchemy这个名词空间中导,or_(与),not_(或),and_(非),这三个东西导入进来是函数,函数是传参的方式使用 
#################### AND 条件(同时)
emps = session.query(Employee).filter(Employee.emp_no > 10015).filter(Employee.emp_no < 10018) # 要求你大于10015,同时小于10018。链式编程点点点。这条语句是filter.filter,意思是先过滤,然后在过滤,要你然后要他,filter.filter这两个条件必须同时存在,就是谁and谁。 查询Employee表类,filter一个条件Employee.emp_no 大于 10015,两个条件必须都要成立,他生成的语句就是谁and谁。
show(emps)

emps = session.query(Employee).filter(and_(Employee.emp_no > 10015 , Employee.emp_no < 10018)) # 这里使用的是and函数,每一个比较完的参数就作为and_的第一个参数,和第二个参数, 这是是and_()函数,每一个Employee.emp_no > 10015参数都是一个逻辑表达式。查询Employee表类,filter就是一个条件。
show(emps)

#################### & (同时)一定要注意&符号两边表达式都要加括号 另一种逻辑判断,使用运算符重载,使用&符号,需要小心这个符号有优先级的关系,用的时候一定要把参数括起来
emps = session.query (Employee).filter((Employee.emp_no > 10015) & (Employee.emp_no < 10018))
show (emps)

#################### OR 条件(或者)
emps = session.query(Employee).filter((Employee.emp_no > 10018) | (Employee.emp_no < 10003))  # ,用|依然会出现运算优先级的问题,用括号括起来。要求大于10018 |(或者) 小于 10003
show (emps)

emps = session.query(Employee).filter (or_(Employee.emp_no > 10018, Employee.emp_no < 10003)) # 想使用函数也一样,Employee.emp_no > 10018, Employee.emp_no < 10003后面这两种作为or_()函数的参数
show (emps)

#################### Not(取反)
emps = session.query(Employee).filter(not_(Employee.emp_no < 10018)) # not_()只能定义一个参数,对他取反
show (emps)

#################### ~(取反)
emps = session.query(Employee).filter(~(Employee.emp_no < 10018)) # ~取反,(Employee.emp_no < 10018)这里一定要加括号,因为里面有一个<大于号,有一个符号,有符号就有优先级的问题,符号的优先级太高了。
show (emps)

############################################ in 什么东西在什么里面
emplist = [10010,10015,10018]  # 给一个可迭代对象,in把要的值写进去
emps = session.query(Employee).filter(Employee.emp_no.in_(emplist)) # 他自己在字段上自动给你增加了一些方法,比如emp_no主键上in_(emplist)有什么东西
show(emps)

############################################ not in # 取反,不是这些可迭代对象中的
emps = session.query(Employee).filter(~Employee.emp_no.in_(emplist)) # not in 直接取反,~Employee.emp_no.in_(emplist)这里没有符号,~符号后面可以视作一个整体
show(emps)

############################################ like # 可以忽略大小写匹配
emps = session.query(Employee).filter(Employee.last_name.like('P%')) # 可以在后面加上filter(Employee.last_name.like('P%')).filter这表示and的意思,like里面'P%'就这么写
show(emps)


# 打印结果
# 2020-08-07 03:21:36,104 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-07 03:21:36,199 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s
# 2020-08-07 03:21:36,200 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10015}
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 03:21:36,244 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s AND employees.emp_no < %(emp_no_2)s # 这个结果转为AND
# 2020-08-07 03:21:36,244 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10015, 'emp_no_2': 10018}
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# ~~~~~~~~~~~~~~~

# 2020-08-07 03:21:36,259 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s AND employees.emp_no < %(emp_no_2)s  # 这个结果转为AND
# 2020-08-07 03:21:36,259 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10015, 'emp_no_2': 10018}
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# ~~~~~~~~~~~~~~~

# 2020-08-07 03:21:36,265 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s AND employees.emp_no < %(emp_no_2)s # 这个结果转为AND
# 2020-08-07 03:21:36,277 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10015, 'emp_no_2': 10018}
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# ~~~~~~~~~~~~~~~  # 上面三个都是AND方法

# 2020-08-07 03:21:36,319 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s OR employees.emp_no < %(emp_no_2)s # 这个结果转为OR
# 2020-08-07 03:21:36,322 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10018, 'emp_no_2': 10003}
# <self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>
# <self.__class__.__name__= Employee    emp_no:10002    name:self.first_name=Bezalel   .  self.last_name=Simmel>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 03:21:36,337 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s OR employees.emp_no < %(emp_no_2)s # 这个结果转为OR
# 2020-08-07 03:21:36,337 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10018, 'emp_no_2': 10003}
# <self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>
# <self.__class__.__name__= Employee    emp_no:10002    name:self.first_name=Bezalel   .  self.last_name=Simmel>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~  # 上面三个都是OR方法

# 2020-08-07 03:21:36,346 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no >= %(emp_no_1)s # 这个结果转为>= ,因为判断条件是小于,not取反就是大于
# 2020-08-07 03:21:36,359 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10018}
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 03:21:36,362 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no >= %(emp_no_1)s # 这个结果转为>= ,因为判断条件是小于,not取反就是大于
# 2020-08-07 03:21:36,365 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10018}
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~ # 上面三个都是Not方法

# 2020-08-07 05:22:50,687 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no IN (%(emp_no_1)s, %(emp_no_2)s, %(emp_no_3)s)  # 这个结果转为IN方法,匹配到的条件内的值
# 2020-08-07 05:22:50,691 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010, 'emp_no_2': 10015, 'emp_no_3': 10018}
# <self.__class__.__name__= Employee    emp_no:10010    name:self.first_name=Duangkaew   .  self.last_name=Piveteau>
# <self.__class__.__name__= Employee    emp_no:10015    name:self.first_name=Guoxiang   .  self.last_name=Nooteboom>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# ~~~~~~~~~~~~~~~

# 2020-08-07 05:22:50,700 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no NOT IN (%(emp_no_1)s, %(emp_no_2)s, %(emp_no_3)s) # 这个结果转为NOT IN方法,取反的值结果
# 2020-08-07 05:22:50,704 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010, 'emp_no_2': 10015, 'emp_no_3': 10018}
# <self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>
# <self.__class__.__name__= Employee    emp_no:10002    name:self.first_name=Bezalel   .  self.last_name=Simmel>
# <self.__class__.__name__= Employee    emp_no:10003    name:self.first_name=Parto   .  self.last_name=Bamford>
# <self.__class__.__name__= Employee    emp_no:10004    name:self.first_name=Chirstian   .  self.last_name=Koblick>
# <self.__class__.__name__= Employee    emp_no:10005    name:self.first_name=Kyoichi   .  self.last_name=Maliniak>
# <self.__class__.__name__= Employee    emp_no:10006    name:self.first_name=Anneke   .  self.last_name=Preusig>
# <self.__class__.__name__= Employee    emp_no:10007    name:self.first_name=Tzvetan   .  self.last_name=Zielinski>
# <self.__class__.__name__= Employee    emp_no:10008    name:self.first_name=Saniya   .  self.last_name=Kalloufi>
# <self.__class__.__name__= Employee    emp_no:10009    name:self.first_name=Sumant   .  self.last_name=Peac>
# <self.__class__.__name__= Employee    emp_no:10011    name:self.first_name=Mary   .  self.last_name=Sluis>
# <self.__class__.__name__= Employee    emp_no:10012    name:self.first_name=Patricio   .  self.last_name=Bridgland>
# <self.__class__.__name__= Employee    emp_no:10013    name:self.first_name=Eberhardt   .  self.last_name=Terkki>
# <self.__class__.__name__= Employee    emp_no:10014    name:self.first_name=Berni   .  self.last_name=Genin>
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 05:22:50,733 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.last_name LIKE %(last_name_1)s # 这个结果转为LIKE方法
# 2020-08-07 05:22:50,734 INFO sqlalchemy.engine.base.Engine {'last_name_1': 'P%'} # 
# <self.__class__.__name__= Employee    emp_no:10006    name:self.first_name=Anneke   .  self.last_name=Preusig>
# <self.__class__.__name__= Employee    emp_no:10009    name:self.first_name=Sumant   .  self.last_name=Peac>
# <self.__class__.__name__= Employee    emp_no:10010    name:self.first_name=Duangkaew   .  self.last_name=Piveteau>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# ~~~~~~~~~~~~~~~


排序

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'

class Employee(Base): # 构建Employee表类,对照这上面的sql语句填写,继承至Base
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    emp_no = Column(Integer,primary_key = True) # Column()表示数据库表中的列。Integer表示类型是int类型,primary_key = True设置为主键
    birth_date =Column(Date,nullable = False) # Date类型,不允许为空nullable = False
    first_name =Column(String(14),nullable = False) # String(14)字符串类型
    last_name = Column(String(16),nullable = False)
    # gender = Column(MyEnum,nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块
    hire_date = Column(Date,nullable = False)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<self.__class__.__name__= {}    emp_no:{}    name:{}>".format(self.__class__.__name__, self.emp_no, "self.first_name={}   .  self.last_name={}".format(self.first_name,self.last_name)) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__

# 排序
## 升序,(asc(),除非你写降序默认就是升序)
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no) # 在字段上要不要升序,不写默认就是升序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.asc()) # 在字段上要不要升序,不写默认就是升序,asc()写了也是升序
show(emps)

## 降序,(desc())
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.desc()) # 在字段上要不要升序,不写默认就是升序,desc()写是就是降序
show(emps)

## 多列排序
emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.last_name).order_by(Employee.emp_no.desc()) # 两个order_by,顺序是谁在前面谁前排,组合语句
show(emps)

# 打印结果
# 2020-08-07 05:56:21,449 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-07 05:56:21,462 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s ORDER BY employees.emp_no ASC # ASC升序排一下
# 2020-08-07 05:56:21,462 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# <self.__class__.__name__= Employee    emp_no:10011    name:self.first_name=Mary   .  self.last_name=Sluis>
# <self.__class__.__name__= Employee    emp_no:10012    name:self.first_name=Patricio   .  self.last_name=Bridgland>
# <self.__class__.__name__= Employee    emp_no:10013    name:self.first_name=Eberhardt   .  self.last_name=Terkki>
# <self.__class__.__name__= Employee    emp_no:10014    name:self.first_name=Berni   .  self.last_name=Genin>
# <self.__class__.__name__= Employee    emp_no:10015    name:self.first_name=Guoxiang   .  self.last_name=Nooteboom>
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 05:56:21,481 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s ORDER BY employees.emp_no DESC # DESC降序排一下
# 2020-08-07 05:56:21,487 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10015    name:self.first_name=Guoxiang   .  self.last_name=Nooteboom>
# <self.__class__.__name__= Employee    emp_no:10014    name:self.first_name=Berni   .  self.last_name=Genin>
# <self.__class__.__name__= Employee    emp_no:10013    name:self.first_name=Eberhardt   .  self.last_name=Terkki>
# <self.__class__.__name__= Employee    emp_no:10012    name:self.first_name=Patricio   .  self.last_name=Bridgland>
# <self.__class__.__name__= Employee    emp_no:10011    name:self.first_name=Mary   .  self.last_name=Sluis>
# ~~~~~~~~~~~~~~~

# 2020-08-07 05:56:21,510 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
# WHERE employees.emp_no > %(emp_no_1)s ORDER BY employees.last_name, employees.emp_no DESC  # 先filter,后last_name排序
# 2020-08-07 05:56:21,511 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>
# <self.__class__.__name__= Employee    emp_no:10012    name:self.first_name=Patricio   .  self.last_name=Bridgland>
# <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>
# <self.__class__.__name__= Employee    emp_no:10014    name:self.first_name=Berni   .  self.last_name=Genin>
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10015    name:self.first_name=Guoxiang   .  self.last_name=Nooteboom>
# <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>
# <self.__class__.__name__= Employee    emp_no:10011    name:self.first_name=Mary   .  self.last_name=Sluis>
# <self.__class__.__name__= Employee    emp_no:10013    name:self.first_name=Eberhardt   .  self.last_name=Terkki>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~


分页

分页很简单,但是很有用,以后做网页展示都需要用上分页

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'

class Employee(Base): # 构建Employee表类,对照这上面的sql语句填写,继承至Base
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    emp_no = Column(Integer,primary_key = True) # Column()表示数据库表中的列。Integer表示类型是int类型,primary_key = True设置为主键
    birth_date =Column(Date,nullable = False) # Date类型,不允许为空nullable = False
    first_name =Column(String(14),nullable = False) # String(14)字符串类型
    last_name = Column(String(16),nullable = False)
    # gender = Column(MyEnum,nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块
    hire_date = Column(Date,nullable = False)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<self.__class__.__name__= {}    emp_no:{}    name:{}>".format(self.__class__.__name__, self.emp_no, "self.first_name={}   .  self.last_name={}".format(self.first_name,self.last_name)) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__

# 分页
emps = session.query(Employee).limit(4) # limit(4)在查询结果里面取几个,这里没有条件,表示从所有记录里面拿4个
show(emps)

emps = session.query(Employee).limit(4).offset(18) # limit(4)在查询结果里面取几个,offset(18)但是我要求从多少个后开个偏移然后向后取
show(emps)


# 打印结果
# 2020-08-07 06:09:55,328 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-07 06:09:55,337 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
#  LIMIT %(param_1)s
# 2020-08-07 06:09:55,338 INFO sqlalchemy.engine.base.Engine {'param_1': 4} # limit(4)在查询结果里面取几个,这里没有条件,表示从所有记录里面拿4个,这里就显示4个
# <self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>
# <self.__class__.__name__= Employee    emp_no:10002    name:self.first_name=Bezalel   .  self.last_name=Simmel>
# <self.__class__.__name__= Employee    emp_no:10003    name:self.first_name=Parto   .  self.last_name=Bamford>
# <self.__class__.__name__= Employee    emp_no:10004    name:self.first_name=Chirstian   .  self.last_name=Koblick>
# ~~~~~~~~~~~~~~~

# 2020-08-07 06:09:55,356 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
#  LIMIT %(param_1)s, %(param_2)s
# 2020-08-07 06:09:55,357 INFO sqlalchemy.engine.base.Engine {'param_1': 18, 'param_2': 4} # limit(4)在查询结果里面取4个,offset(18)但是重18以后开个取,以后就20个,所以取两个,如果有22个就取4个
# <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>
# <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>
# ~~~~~~~~~~~~~~~


消费者方法

此方法使用的比较多

消费者方法调用后,Query对象(可迭代)就转换成了一个容器

消费者方法调用后,Query对象是一个可迭代对象,我们认为查询回来的结果集,就因该是一个多记录放在里面的容器,当然可以迭代

我只要用了消费者方法,就可以把你转化为真正的容器了,比如说list,也就是说你原来是迭代器,我们现在给你转化为list这些数据类型了,我们就对列表进行操作了,而不是对结果集进行操作了。


import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'

class Employee(Base): # 构建Employee表类,对照这上面的sql语句填写,继承至Base
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    emp_no = Column(Integer,primary_key = True) # Column()表示数据库表中的列。Integer表示类型是int类型,primary_key = True设置为主键
    birth_date =Column(Date,nullable = False) # Date类型,不允许为空nullable = False
    first_name =Column(String(14),nullable = False) # String(14)字符串类型
    last_name = Column(String(16),nullable = False)
    # gender = Column(MyEnum,nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块
    hire_date = Column(Date,nullable = False)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<self.__class__.__name__= {}    emp_no:{}    name:{}>".format(self.__class__.__name__, self.emp_no, "self.first_name={}   .  self.last_name={}".format(self.first_name,self.last_name)) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__

# 总行数
# 我们最长用的就是count
emps = session.query(Employee)  # (这是查询出来所有结果)
print(len(list(emps))) # 返回大量的结果集,然后转换list ,这种方式看似很美好但是不用,这种方法太消耗资源了
# 我们发现人家也替我们对emps做list,然后拿回来一个集合,然后len取一下长度就可以了,这种不好的原因在于,这相当于把select * from真的是把所有字段所有数据全部返回回来,然后再做list,然后再取的长度,当然len本身没有什么太多的时间消耗,它相当于再list记录里的元数据里面已经知道里面有多少行,关键是在于用list去转换emps是个大问题,因为虽然他是一个可迭代对象以list相当于是把可迭代对象的所有你要的记录全拿回来后在本地存一下,有可能在数据库都在折腾一把,他就把所有列所有行全给你了,在做list之后就知道长度了  

print(emps.count())  # 聚合函数count(*)的查询,一般用这种方法
# 这一句相当于在做聚合运算,现在做的不是聚合我们叫消费者方法,他会知道你想做聚合运算,虽然你没有用聚合运算的方法,你用的是在这个查询结果集上用count方法,他就会自动的去给你调用一条select count(*) from一张表,语句,这有个好处这速度挺快的,不但快就返回就一个单值,现在就查询单列的一行记录

# 取所有的函数
print(emps.all()) # 返回列表,数据太多了不推荐,all方法本身用的就是list,查询应该带条件,emps = session.query(Employee).filter(Employee.emp_no == 10013) | (Employee.emp_no == 10020),这样就返回两条,这种就叫消费者方法


# 取一行
# print(emps.one()) # ,只显示一行,你查询的结果超过一行就不行,返回一行,如果查询结果是多行抛异常,可以使用 emps = session.query(Employee).filter(Employee.emp_no == 10013),查询主键记录,保证返回一条记录,这条语句就不抛异常了。。
print(emps.limit(1).one()) # emps.limit(1)从头拿一行,后拿one(),这保证是一个。one()只能一个超过一行就不行print(emps.one())执行后就会抛异常


# 删除 delete by query(先查,在过滤,在删除)
session.query(Employee).filter(Employee.emp_no > 10018).delete() # session.query(Employee).filter(Employee.emp_no > 10018)查到的这一批都是我想要的,然后做delete(),这种方法也是为了先持久化在delete,不查到结果怎么能拿到持久化对象,拿不到持久化对象就不能delete
# session.commit() # 提交则删除

# 执行结果
# 2020-08-07 07:10:47,653 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-07 07:10:47,654 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees
# 2020-08-07 07:10:47,657 INFO sqlalchemy.engine.base.Engine {}  # print(len(list(emps)))返回的结果,都是20
# 20
# 2020-08-07 07:10:47,672 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1 
# FROM (SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees) AS anon_1
# 2020-08-07 07:10:47,676 INFO sqlalchemy.engine.base.Engine {} # print(emps.count())返回的结果,都是20
# 20
# 2020-08-07 07:10:47,679 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees  # print(emps.all())相当于print(len(list(emps)))方法,all方法返回的是一个列表
# 2020-08-07 07:10:47,680 INFO sqlalchemy.engine.base.Engine {}
# [<self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>, <self.__class__.__name__= Employee    emp_no:10002    name:self.first_name=Bezalel   .  self.last_name=Simmel>, <self.__class__.__name__= Employee    emp_no:10003    name:self.first_name=Parto   .  self.last_name=Bamford>, <self.__class__.__name__= Employee    emp_no:10004    name:self.first_name=Chirstian   .  self.last_name=Koblick>, <self.__class__.__name__= Employee    emp_no:10005    name:self.first_name=Kyoichi   .  self.last_name=Maliniak>, <self.__class__.__name__= Employee    emp_no:10006    name:self.first_name=Anneke   .  self.last_name=Preusig>, <self.__class__.__name__= Employee    emp_no:10007    name:self.first_name=Tzvetan   .  self.last_name=Zielinski>, <self.__class__.__name__= Employee    emp_no:10008    name:self.first_name=Saniya   .  self.last_name=Kalloufi>, <self.__class__.__name__= Employee    emp_no:10009    name:self.first_name=Sumant   .  self.last_name=Peac>, <self.__class__.__name__= Employee    emp_no:10010    name:self.first_name=Duangkaew   .  self.last_name=Piveteau>, <self.__class__.__name__= Employee    emp_no:10011    name:self.first_name=Mary   .  self.last_name=Sluis>, <self.__class__.__name__= Employee    emp_no:10012    name:self.first_name=Patricio   .  self.last_name=Bridgland>, <self.__class__.__name__= Employee    emp_no:10013    name:self.first_name=Eberhardt   .  self.last_name=Terkki>, <self.__class__.__name__= Employee    emp_no:10014    name:self.first_name=Berni   .  self.last_name=Genin>, <self.__class__.__name__= Employee    emp_no:10015    name:self.first_name=Guoxiang   .  self.last_name=Nooteboom>, <self.__class__.__name__= Employee    emp_no:10016    name:self.first_name=Kazuhito   .  self.last_name=Cappelletti>, <self.__class__.__name__= Employee    emp_no:10017    name:self.first_name=Cristinel   .  self.last_name=Bouloucos>, <self.__class__.__name__= Employee    emp_no:10018    name:self.first_name=Kazuhide   .  self.last_name=Peha>, <self.__class__.__name__= Employee    emp_no:10019    name:self.first_name=Lillian   .  self.last_name=Haddadi>, <self.__class__.__name__= Employee    emp_no:10020    name:self.first_name=Mayuko   .  self.last_name=Warwick>]
# 2020-08-07 07:10:47,694 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.hire_date AS employees_hire_date 
# FROM employees 
#  LIMIT %(param_1)s # print(emps.limit(1).one())返回的结果,给个one()就不用列表套起来了,all方法返回的是一个列表
# 2020-08-07 07:10:47,695 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
# <self.__class__.__name__= Employee    emp_no:10001    name:self.first_name=Georgi   .  self.last_name=Facello>
# 2020-08-07 07:10:47,697 INFO sqlalchemy.engine.base.Engine DELETE FROM employees WHERE employees.emp_no > %(emp_no_1)s
# 2020-08-07 07:10:47,699 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10018} 


聚合,分组
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect # inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '172.18.32.72'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'

class Employee(Base): # 构建Employee表类,对照这上面的sql语句填写,继承至Base
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    emp_no = Column(Integer,primary_key = True) # Column()表示数据库表中的列。Integer表示类型是int类型,primary_key = True设置为主键
    birth_date =Column(Date,nullable = False) # Date类型,不允许为空nullable = False
    first_name =Column(String(14),nullable = False) # String(14)字符串类型
    last_name = Column(String(16),nullable = False)
    # gender = Column(MyEnum,nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块
    hire_date = Column(Date,nullable = False)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<self.__class__.__name__= {}    emp_no:{}    name:{}>".format(self.__class__.__name__, self.emp_no, "self.first_name={}   .  self.last_name={}".format(self.first_name,self.last_name)) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__


# 聚合函数
# conut统计一下有多少记录

from sqlalchemy import func  # 聚合函数都是在sqlalchemy名词空间下的func,他能让你使用数据库中的那些聚合函数

query = session.query(func.count(Employee.emp_no)) # 一个conut完了只有一个结果,大胆的使用one(),func.conut(Employee.emp_no)按照select *来查,这里查完后下面个查个的
print(query.one())  # one()返回一个实例,实例里有好多字段,只能有一行结果
print(query.scalar()) # 他去one()返回的一行一列,scalar()取one()返回元组的第一个元素,one(),scalar()这两个结果是差不多的但是有差别

# max/min/avg (最大值,最小值,平均值)
print(session.query(func.max(Employee.emp_no)).scalar())
print(session.query(func.min(Employee.emp_no)).scalar())
print(session.query(func.avg(Employee.emp_no)).scalar())

# 分组
print(session.query(func.count(Employee.emp_no)).group_by(Employee.emp_no).all()) # 分组的目的经常是前面做聚合运算的,func.count(Employee.emp_no)这里要用聚合函数,分组可以更复杂一直,.group_by(),分组是group_by按照emp_no来分组完了后,然后统计一下func.count(Employee.emp_no)这个东西,最后all()消费者方法,分组完了后一般来讲不应该是单行


# 打印结果
# 2020-08-08 22:16:23,464 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-08 22:16:23,465 INFO sqlalchemy.engine.base.Engine SELECT count(employees.emp_no) AS count_1 
# FROM employees
# 2020-08-08 22:16:23,465 INFO sqlalchemy.engine.base.Engine {}
# (20,)                                                        # query.one(),one()返回的是一个元组
# 2020-08-08 22:16:23,478 INFO sqlalchemy.engine.base.Engine SELECT count(employees.emp_no) AS count_1 
# FROM employees
# 2020-08-08 22:16:23,478 INFO sqlalchemy.engine.base.Engine {}
# 20                                                           # query.scalar(),scalar()返回one()元组中的第一个元素
# 2020-08-08 22:16:23,479 INFO sqlalchemy.engine.base.Engine SELECT max(employees.emp_no) AS max_1 
# FROM employees
# 2020-08-08 22:16:23,479 INFO sqlalchemy.engine.base.Engine {}
# 10020                                                        # session.query(func.max(Employee.emp_no)).scalar()   max最大是10020
# 2020-08-08 22:16:23,480 INFO sqlalchemy.engine.base.Engine SELECT min(employees.emp_no) AS min_1 
# FROM employees
# 2020-08-08 22:16:23,480 INFO sqlalchemy.engine.base.Engine {}
# 10001                                                        # session.query(func.min(Employee.emp_no)).scalar()   min最小是10001
# 2020-08-08 22:16:23,482 INFO sqlalchemy.engine.base.Engine SELECT avg(employees.emp_no) AS avg_1 
# FROM employees
# 2020-08-08 22:16:23,482 INFO sqlalchemy.engine.base.Engine {}
# 10010.5000                                                   # session.query(func.avg(Employee.emp_no)).scalar()   avg平均是10010.5000
# 2020-08-08 22:16:23,484 INFO sqlalchemy.engine.base.Engine SELECT count(employees.emp_no) AS count_1 
# FROM employees GROUP BY employees.emp_no
# 2020-08-08 22:16:23,485 INFO sqlalchemy.engine.base.Engine {}
# [(1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,)]  # session.query(func.count(Employee.emp_no)).group_by(Employee.emp_no).all() 因为取的是all()里面一个个都是元组,(1,)这是元素每一个都是他封装过的东西,all()方法返回一个列表,分组的目的,通常都是前面做聚合运算的


关联查询

隐式内连接 和 join方法这两种写法,返回都只有一行数据,为什么?原因在于query(Employee)这个只能返回一个实体对象中去,为了解决这个问题,需要修改实体类Employee,增加属性用来存放部门信息qlalchemy.orm.relationship(实体类名字符串)

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect,ForeignKey # ForeignKey导入外键函数 inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承,这个名词空间中也有可能有函数,sqlalchemy名词空间中找不到,就来这里找
from enum import Enum # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(Enum): # 需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'



# 关联查询

# employees员工表
# CREATE TABLE `employees` (
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# departments部门表
# CREATE TABLE `departments` (
#   `dept_no` char(4) NOT NULL,
#   `dept_name` varchar(40) NOT NULL,
#   PRIMARY KEY (`dept_no`),
#   UNIQUE KEY `dept_name` (`dept_name`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# dept_emp部门员工关系表
# 这张表中有外键,和联合主键PRIMARY KEY (`emp_no`,`dept_no`)
# CREATE TABLE `dept_emp` (
#   `emp_no` int(11) NOT NULL,
#   `dept_no` char(4) NOT NULL,
#   `from_date` date NOT NULL,
#   `to_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`,`dept_no`),
#   KEY `dept_no` (`dept_no`),
#   CONSTRAINT `dept_emp_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE,
#   CONSTRAINT `dept_emp_ibfk_2` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 这三张表实际上是有关系的,一个部门可能有多个员工,一个员工可能在多个部门,这就叫多对多的关系,多对多往往需要一张第三方表,来存储他们之间的关系,比如dept_emp部门员工关系表,来解决多对多的问题单独来做

class Employee(Base): # 这是我们要建的表
    # 指定表名
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    # 定义属性对应的字段
    emp_no = Column(Integer,primary_key = True) 
    birth_date =Column(Date,nullable = False)
    first_name =Column(String(14),nullable = False)
    last_name = Column(String(16),nullable = False)
    gender = Column(Enum(MyEnum),nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块,最好不要用,性别用1个字节表示就够了
    hire_date = Column(Date,nullable = False)
    # 第一参数是字段名,如果和属性名不一致,一定要指定
    # age = Column('age',Integer)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{}    no= {}    name:{}  {}   gender:{}>".format(self.__class__.__name__, self.emp_no, self.first_name,self.last_name,self.gender.value) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__


class Department(Base):
    # Department对应的departments部门表只有两个字段dept_no,dept_name
    __tablename__ = 'departments'

    dept_no = Column(String(4),primary_key=True) # 这个是主键,对应d001这样的东西
    dept_name = Column(String(40),nullable=false,unique=True) # 这个记录名字

    def __repr__(self):
        return "{} no={} name={}".format(
            type(self.__name__,self.dept_no,self.dept_name)

class Dept_emp(Base):
    # 多对多关系一张表
    __tablename__ = "dept_emp"

    # 这几个字段填上,这是要求。ondelete='CASCADE'表示定义级联操作
    emp_no = Column(Integer,ForeignKey('employees.emp_no',),primary_key = True) # 虽然ForeignKey告诉他是外键,但同时他也是primary_key主键约束,外键约束跟另外一张表数据必需在,我才能关联,外键约束时管另外一张表约束的,有这种约束就是告诉他不能谁便删,如果删除就告诉他不能删我还用这呢不准删。ForeignKey要求参数第一个位置参数必需给,ondelete='CASCADE'不写也没事,
    dept_no = Column(String(4),ForeignKey('departments.dept_no',ondelete='CASCADE'),primary_key = True)
    from_date = Column(Date,nullable = False)
    to_date = Column(Date,nullable = False)

    def __repr__(self):
        return "{} empno={} deptno={}".format(
            type(self).__name__,self.emp_no,self.dept_no)



关联查询,需求 查询10010员工的所在的部门编号,1.使用隐式内连接


import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect,ForeignKey # ForeignKey导入外键函数 inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承,这个名词空间中也有可能有函数,sqlalchemy名词空间中找不到,就来这里找
import enum  # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(enum.Enum): # 这里的enum是python本身自己带的这个类,要被gender = Column(Enum(MyEnum),nullable = False)这里的Enum包装一下,需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'



# 关联查询

# employees员工表
# CREATE TABLE `employees` (
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# departments部门表
# CREATE TABLE `departments` (
#   `dept_no` char(4) NOT NULL,
#   `dept_name` varchar(40) NOT NULL,
#   PRIMARY KEY (`dept_no`),
#   UNIQUE KEY `dept_name` (`dept_name`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# dept_emp部门员工关系表
# 这张表中有外键,和联合主键PRIMARY KEY (`emp_no`,`dept_no`)
# CREATE TABLE `dept_emp` (
#   `emp_no` int(11) NOT NULL,
#   `dept_no` char(4) NOT NULL,
#   `from_date` date NOT NULL,
#   `to_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`,`dept_no`),
#   KEY `dept_no` (`dept_no`),
#   CONSTRAINT `dept_emp_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE,
#   CONSTRAINT `dept_emp_ibfk_2` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 这三张表实际上是有关系的,一个部门可能有多个员工,一个员工可能在多个部门,这就叫多对多的关系,多对多往往需要一张第三方表,比如dept_emp部门员工关系表,来解决多对多的问题单独来做

class Employee(Base): # 这是我们要建的表
    # 指定表名
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    # 定义属性对应的字段
    emp_no = Column(Integer,primary_key = True) 
    birth_date =Column(Date,nullable = False)
    first_name =Column(String(14),nullable = False)
    last_name = Column(String(16),nullable = False)
    gender = Column(Enum(MyEnum),nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块,最好不要用,性别用1个字节表示就够了
    hire_date = Column(Date,nullable = False)
    # 第一参数是字段名,如果和属性名不一致,一定要指定
    # age = Column('age',Integer)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{}    no= {}    name:{}  {}  gender:{}>".format(self.__class__.__name__, self.emp_no, self.first_name,self.last_name,self.gender.value) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__


class Department(Base):
    # Department对应的departments部门表只有两个字段dept_no,dept_name
    __tablename__ = 'departments'

    dept_no = Column(String(4),primary_key=True) # 这个是主键,对应d001这样的东西
    dept_name = Column(String(40),nullable=False,unique=True) # 这个记录名字

    def __repr__(self):
        return "{} no={} name={}".format(
            type(self).__name__,self.dept_no,self.dept_name)

class Dept_emp(Base):
    # 多对多关系一张表
    __tablename__ = "dept_emp"

    # 这几个字段填上,这是要求。ondelete='CASCADE'表示定义级联操作
    emp_no = Column(Integer,ForeignKey('employees.emp_no',),primary_key = True) # 虽然ForeignKey告诉他是外键,但同时他也是primary_key主键约束,外键约束跟另外一张表数据必需在,我才能关联,外键约束时管另外一张表约束的,有这种约束就是告诉他不能谁便删,如果删除就告诉他不能删我还用这呢不准删。ForeignKey要求参数第一个位置参数必需给,ondelete='CASCADE'不写也没事,
    dept_no = Column(String(4),ForeignKey('departments.dept_no',ondelete='CASCADE'),primary_key = True)
    from_date = Column(Date,nullable = False)
    to_date = Column(Date,nullable = False)

    def __repr__(self):
        return "{} empno={} deptno={}".format(
            type(self).__name__,self.emp_no,self.dept_no)
 

# 查询10010员工所在的部门编号
results = session.query(Employee,Dept_emp).filter(Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010).all() # 查询Employee,Dept_emp两张表用逗号隔开,这种写法与[Employee,Dept_emp]这种写法是一样的 ,filter(Employee.emp_no == Dept_emp.emp_no)要求等值连接,.filter(Employee.emp_no == 10010)后面相当于加了一个and条件,all()最后显示一批结果
show(results)

# 查询结果
# 2020-08-08 06:35:53,349 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-08 06:35:53,363 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.gender AS employees_gender, employees.hire_date AS employees_hire_date, dept_emp.emp_no AS dept_emp_emp_no, dept_emp.dept_no AS dept_emp_dept_no, dept_emp.from_date AS dept_emp_from_date, dept_emp.to_date AS dept_emp_to_date 
# FROM employees, dept_emp 
# WHERE employees.emp_no = dept_emp.emp_no AND employees.emp_no = %(emp_no_1)s
# 2020-08-08 06:35:53,363 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# (<Employee    no= 10010    name:Duangkaew  Piveteau  gender:F>, Dept_emp empno=10010 deptno=d004)
# (<Employee    no= 10010    name:Duangkaew  Piveteau  gender:F>, Dept_emp empno=10010 deptno=d006)
# 相当于生成了一个这样的sql语句
# select *
# FROM employees, dept_emp 
# WHERE employees.emp_no = dept_emp.emp_no AND employees.emp_no = %(emp_no_1)s


关联查询,需求 查询10010员工的所在的部门编号,2.使用join方法

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect,ForeignKey # ForeignKey导入外键函数 inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker # sessionmaker类从_SessionClassMethods来继承,这个名词空间中也有可能有函数,sqlalchemy名词空间中找不到,就来这里找
import enum  # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(enum.Enum): # 这里的enum是python本身自己带的这个类,要被gender = Column(Enum(MyEnum),nullable = False)这里的Enum包装一下,需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'



# 关联查询

# employees员工表
# CREATE TABLE `employees` (
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# departments部门表
# CREATE TABLE `departments` (
#   `dept_no` char(4) NOT NULL,
#   `dept_name` varchar(40) NOT NULL,
#   PRIMARY KEY (`dept_no`),
#   UNIQUE KEY `dept_name` (`dept_name`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# dept_emp部门员工关系表
# 这张表中有外键,和联合主键PRIMARY KEY (`emp_no`,`dept_no`)
# CREATE TABLE `dept_emp` (
#   `emp_no` int(11) NOT NULL,
#   `dept_no` char(4) NOT NULL,
#   `from_date` date NOT NULL,
#   `to_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`,`dept_no`),
#   KEY `dept_no` (`dept_no`),
#   CONSTRAINT `dept_emp_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE,
#   CONSTRAINT `dept_emp_ibfk_2` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 这三张表实际上是有关系的,一个部门可能有多个员工,一个员工可能在多个部门,这就叫多对多的关系,多对多往往需要一张第三方表,比如dept_emp部门员工关系表,来解决多对多的问题单独来做

class Employee(Base): # 这是我们要建的表
    # 指定表名
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    # 定义属性对应的字段
    emp_no = Column(Integer,primary_key = True) 
    birth_date =Column(Date,nullable = False)
    first_name =Column(String(14),nullable = False)
    last_name = Column(String(16),nullable = False)
    gender = Column(Enum(MyEnum),nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块,最好不要用,性别用1个字节表示就够了
    hire_date = Column(Date,nullable = False)
    # 第一参数是字段名,如果和属性名不一致,一定要指定
    # age = Column('age',Integer)

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{}    no= {}    name:{}    gender:{}>".format(self.__class__.__name__, self.emp_no, self.first_name,self.last_name,self.gender.value) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套

    __str__ = __repr__


class Department(Base):
    # Department对应的departments部门表只有两个字段dept_no,dept_name
    __tablename__ = 'departments'

    dept_no = Column(String(4),primary_key=True) # 这个是主键,对应d001这样的东西
    dept_name = Column(String(40),nullable=False,unique=True) # 这个记录名字

    def __repr__(self):
        return "{} no={} name={}".format(
            type(self).__name__,self.dept_no,self.dept_name)

class Dept_emp(Base):
    # 多对多关系一张表
    __tablename__ = "dept_emp"

    # 这几个字段填上,这是要求。ondelete='CASCADE'表示定义级联操作
    emp_no = Column(Integer,ForeignKey('employees.emp_no',),primary_key = True) # 虽然ForeignKey告诉他是外键,但同时他也是primary_key主键约束,外键约束跟另外一张表数据必需在,我才能关联,外键约束时管另外一张表约束的,有这种约束就是告诉他不能谁便删,如果删除就告诉他不能删我还用这呢不准删。ForeignKey要求参数第一个位置参数必需给,ondelete='CASCADE'不写也没事,
    dept_no = Column(String(4),ForeignKey('departments.dept_no',ondelete='CASCADE'),primary_key = True)
    from_date = Column(Date,nullable = False)
    to_date = Column(Date,nullable = False)

    def __repr__(self):
        return "{} empno={} deptno={}".format(
            type(self).__name__,self.emp_no,self.dept_no)
 

# 查询10010员工所在的部门编号
# 第一种写法,不推荐使用,会自动生成等值连接,不确定会生成什么
# results = session.query(Employee).join(Dept_emp).filter(Employee.emp_no == 10010).all() # Employee这张表join这张表Dept_emp,要求Employee.emp_no == 10010谁等于谁

# 第二种写法
results = session.query(Employee).join(Dept_emp,Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010).all() # Employee这张表join这张表Dept_emp,并且顺带Employee.emp_no == Dept_emp.emp_no把等值条件给我,filter(Employee.emp_no == 10010).all()最后改where就where

print(results)

# 查询结果
# 2020-08-08 06:59:29,371 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-08 06:59:29,374 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.gender AS employees_gender, employees.hire_date AS employees_hire_date 
# FROM employees INNER JOIN dept_emp ON employees.emp_no = dept_emp.emp_no 
# WHERE employees.emp_no = %(emp_no_1)s
# 2020-08-08 06:59:29,378 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# [<Employee    no= 10010    name:Duangkaew  Piveteau  gender:F>] # 返回的是一行记录,不同的写法尽然产生了不同的结果,查的是对的,但是部门信息没有了


关联查询,上面的查询方式有些问题

隐式内连接 和 join方法这两种写法,返回都只有一行数据,为什么?原因在于query(Employee)这个只能返回一个实体对象中去,为了解决这个问题,需要修改实体类Employee,增加属性用来存放部门信息qlalchemy.orm.relationship(实体类名字符串)



import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column ,Integer,String,Date,Enum ,inspect,ForeignKey # ForeignKey导入外键函数 inspect函数查看状态,如果要定义一个类型来讲,必需把这些类型给加载出来。Integer这个东西能让你解决一些类型上的关系,Date日期,Enum枚举
from sqlalchemy.orm import sessionmaker,relationship # sessionmaker类从_SessionClassMethods来继承,这个名词空间中也有可能有函数,sqlalchemy名词空间中找不到,就来找。导入relationship来解决join连接的问题。
import enum  # 解决enum('M','F')枚举值类型,定义一个类,继承Enum类

# 实体类的基类
Base = declarative_base() # 实体类的基类创建好了,下面的实体类继承至他

# 实体类
class Student(Base):
    __tablename__ = "student" # 使用之前告诉我你是谁,你跟那张表映射,如果不提供name,连表都不告诉我,省不了,直接抛异常

    id = Column(Integer, primary_key=True,nullable=False,autoincrement=True) # Column是解决列对应的问题,id是Integer, primary_key=True跟主键相关,id的nullable=False可以为空,当这段定义反过来生成数据库中的表的时候他自然把这个主键加进去了,主键一定不可以为空 ,Column相当于ORM中的Field类,autoincrement=True是否自增,只不过这个类型他从外面当参数给送进来,这东西用的肯定是一个描述器
    name = Column(String(64),nullable=False) # name的nullable=False可以为空
    age = Column(Integer) # 年龄无所谓,可有可无

    # Column # 什么都可以省,但是类型不能省
    # :param name: 请给一个名字,这名字是再数据库中对列的的一种表现的名字,官方的例子省略了名字,但是应该name = Column('name',string)这样用,省略name是靠*args来数数,第一个是什么,如果是字符串一定不是类型,如果第一个是字符串他就把字符串当name这就判断出来了,如果第一个不是字符串那么第一个应该是type类型
    # :param type\_: 请你给一下这个类型,官方的例子省略了名字,直接使用type
    # :param *args: 附加的位置参数还包括派生于SchemaItem的各种构造,这些构造将用作列的选项。如果第一个不是type那么后面就应该有一些别的类型判断 

    def __repr__(self): # 如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{} id:{} name:{} age:{}>".format(self.__class__.__name__, self.id,self.name,self.age) # 想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,self.id,self.name,self.age显示字段里面的内容

    __str__ = __repr__


    
# mysqldb的连接
# # mysql+mysqldb://<user>:<password>&<host>[:<port>]/<dbname> # 连接语法格式,mysqldb的库在2.几右可能看到所以列出来了
# engine = sqlalchemy.create_engine("mysql+mysqldb://root:root@127.0.0.1:3306/test")

#  pymysql的连接
# # mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # pymysql允许在后面使用?的方式传递一些跟数据连接象关的选项,知道有什么连接在填不要乱填,进来用的很少
# engine = sqlalchemy.create_engine("mysql+pymysql://root:root@127.0.0.1:3306/test") # root:root用户名和密码传递进来需要明文给他,所以这个链接字符串就不要暴露出去了,真正要操作数据库应该严格限制每一个用户所关联的表应该有哪些,要之前定义好他能再这张表上应该做增删改查,还是只能做查的权限,这是你要给的
# 引擎,管理连接池
host = '192.168.161.203'
port = 3306
user = 'root'
password = 'root'
database = 'test'

conn_str = "mysql+pymysql://{}:{}@{}:{}/{}".format(user,password,host,port,database)
engine = sqlalchemy.create_engine(conn_str,echo=True) # echo=True意思是引擎是否打印执行的语句,便于调试,生产环境中就不要做这个事了
# Base.metadata.create_all(engine) # 这条语句不用的时候关掉,否者一直创建表。把定义的表创建起来。 所有从Base继承的在metadata这边(元类这边)他有一张表记录了所有用这个元类创建的所有实体类。create_all我能遍历所有实体类,来做映射
# Base.metadata.drop_all(engine) # 把定义的表删除掉。  drop_all,中bind=None给我一个绑定engine,tables=None把要删除的表告诉我,我们没给


# s = Student(name='tom')
# print(s.name)
# s.age = 20
# print(s.age)
Session = sessionmaker(bind=engine) # 返回类。应当创建一个Session类,当作工厂来创建session对象。bind=engine告诉我这个Session应该用那个引擎这里使用engine
# session = Student(bind=engine) # 这样后绑定也行
session = Session() # 实例化。session创建完后就可以对表进行操作了

def show (entity):
    ins = inspect(entity)
    print('状态信息 ','transient=',ins.transient,'pending=',ins.pending ,'persistent=',ins.persistent,'deleted=',ins.deleted,'detached=',ins.detached )

# 打印函数
def show(emps): # 给我一个可迭代对象,我就替你做打印 ,凡是可迭代对象,挨个打印出来
    for x in emps:
        print(x)
    print('~~~~~~~~~~~~~~~',end='

')

# CREATE TABLE `employees` ( 
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`) # 主键是emp_no字段
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

class MyEnum(enum.Enum): # 这里的enum是python本身自己带的这个类,要被gender = Column(Enum(MyEnum),nullable = False)这里的Enum包装一下,需要定义一个类,从Enum继承一下就行了
    M = 'M' # 将枚举类型进行赋值
    F = 'F'



# 关联查询

# employees员工表
# CREATE TABLE `employees` (
#   `emp_no` int(11) NOT NULL,
#   `birth_date` date NOT NULL,
#   `first_name` varchar(14) NOT NULL,
#   `last_name` varchar(16) NOT NULL,
#   `gender` enum('M','F') NOT NULL,
#   `hire_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# departments部门表
# CREATE TABLE `departments` (
#   `dept_no` char(4) NOT NULL,
#   `dept_name` varchar(40) NOT NULL,
#   PRIMARY KEY (`dept_no`),
#   UNIQUE KEY `dept_name` (`dept_name`)
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# dept_emp部门员工关系表
# 这张表中有外键,和联合主键PRIMARY KEY (`emp_no`,`dept_no`)
# CREATE TABLE `dept_emp` (
#   `emp_no` int(11) NOT NULL,
#   `dept_no` char(4) NOT NULL,
#   `from_date` date NOT NULL,
#   `to_date` date NOT NULL,
#   PRIMARY KEY (`emp_no`,`dept_no`),
#   KEY `dept_no` (`dept_no`),
#   CONSTRAINT `dept_emp_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE,
#   CONSTRAINT `dept_emp_ibfk_2` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 这三张表实际上是有关系的,一个部门可能有多个员工,一个员工可能在多个部门,这就叫多对多的关系,多对多往往需要一张第三方表,比如dept_emp部门员工关系表,来解决多对多的问题单独来做

class Employee(Base): # 这是我们要建的表
    # 指定表名
    __tablename__ = 'employees' # 定义表名,实际的数据库中的表名
    # 定义属性对应的字段
    emp_no = Column(Integer,primary_key = True) 
    birth_date =Column(Date,nullable = False)
    first_name =Column(String(14),nullable = False)
    last_name = Column(String(16),nullable = False)
    gender = Column(Enum(MyEnum),nullable = False) # 枚举类型使用我们定义的MyEnum类 enum('M','F')写枚举值比较麻烦,需要导入模块,最好不要用,性别用1个字节表示就够了
    hire_date = Column(Date,nullable = False)
    # 第一参数是字段名,如果和属性名不一致,一定要指定
    # age = Column('age',Integer)

    depts = relationship('Dept_emp') # relationship查完返回一个容器,里面放的可能是好几条记录,这就是一个对多的关系。 relationship两个之间产生关系,这个关系在emp_no = Column(Integer,ForeignKey('employees.emp_no',),primary_key = True)已经表达过外键是谁, ,类名要引用起来官网要求。现在想等于给他生成一个属性,这个属性解决多个部门的问题,因为原来的这些字段中没有depts,但是你还不能用Column来定义,这不是字段,这跟建表时的字段没有关系,因为如果定义了,表中就有depts这个字段了,所以说不能用他写,他depts是什么?他depts纯粹叫关系,在session.query(Employee)这里你写的谁,他投影的就是谁,跟Dept_emp就没关系了,所以需要这个字段来添加关系。

    def __repr__(self): # 将show()打印函数打印的结果做分割,如果不这样做,将来用容器观察的时候看不清楚,因为他都显示一个某某对象,地址就完了,看不清楚里面到底有什么东西
        return "<{}    no= {}    name:{}  {}  gender:{} depts={}>".format(self.__class__.__name__, self.emp_no, self.first_name,self.last_name,self.gender.value ,self.depts ) # ,打印的时候,添加上depts,否则没有用他。想要谁都return出去,self.__class__.__name__显示我自己本身自己的实体类,format嵌套


    __str__ = __repr__


class Department(Base):
    # Department对应的departments部门表只有两个字段dept_no,dept_name
    __tablename__ = 'departments'

    dept_no = Column(String(4),primary_key=True) # 这个是主键,对应d001这样的东西
    dept_name = Column(String(40),nullable=False,unique=True) # 这个记录名字

    def __repr__(self):
        return "{} no={} name={}".format(
            type(self).__name__,self.dept_no,self.dept_name)

class Dept_emp(Base):
    # 多对多关系一张表
    __tablename__ = "dept_emp"

    # 这几个字段填上,这是要求。ondelete='CASCADE'表示定义级联操作
    emp_no = Column(Integer,ForeignKey('employees.emp_no',),primary_key = True) #外键第一个参数容易出错,ForeignKey要求表名加字段参数第一个位置参数必需给,ondelete='CASCADE'不写也没事, 虽然ForeignKey告诉他是外键,但同时他也是primary_key主键约束,外键约束跟另外一张表数据必需在,我才能关联,外键约束时管另外一张表约束的,有这种约束就是告诉他不能谁便删,如果删除就告诉他不能删我还用这呢不准删。
    dept_no = Column(String(4),ForeignKey('departments.dept_no',ondelete='CASCADE'),primary_key = True)
    from_date = Column(Date,nullable = False)
    to_date = Column(Date,nullable = False)

    def __repr__(self):
        return "{} empno={} deptno={}".format(
            type(self).__name__,self.emp_no,self.dept_no)
 

# 查询10010员工所在的部门编号
# 第一种写法,不推荐使用,会自动生成等值连接,不确定会生成什么
# results = session.query(Employee).join(Dept_emp).filter(Employee.emp_no == 10010).all() # Employee这张表join这张表Dept_emp,要求Employee.emp_no == 10010谁等于谁

# 第二种写法
results = session.query(Employee).join(Dept_emp,Employee.emp_no == Dept_emp.emp_no).filter(Employee.emp_no == 10010).all() # 查询要些类名加属性,跟设计表填写的不一致,容易出错。Employee这张表join这张表Dept_emp,并且顺带Employee.emp_no == Dept_emp.emp_no把等值条件给我,filter(Employee.emp_no == 10010).all()最后改where就where

print(results)


# 查询结果
# 2020-08-08 12:17:24,132 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
# 2020-08-08 12:17:24,145 INFO sqlalchemy.engine.base.Engine SELECT employees.emp_no AS employees_emp_no, employees.birth_date AS employees_birth_date, employees.first_name AS employees_first_name, employees.last_name AS employees_last_name, employees.gender AS employees_gender, employees.hire_date AS employees_hire_date 
# FROM employees INNER JOIN dept_emp ON employees.emp_no = dept_emp.emp_no 
# WHERE employees.emp_no = %(emp_no_1)s   # 先查一波。操作方式,这里做投影拿到这些值
# 2020-08-08 12:17:24,149 INFO sqlalchemy.engine.base.Engine {'emp_no_1': 10010}
# 2020-08-08 12:17:24,167 INFO sqlalchemy.engine.base.Engine SELECT dept_emp.emp_no AS dept_emp_emp_no, dept_emp.dept_no AS dept_emp_dept_no, dept_emp.from_date AS dept_emp_from_date, dept_emp.to_date AS dept_emp_to_date 
# FROM dept_emp 
# WHERE %(param_1)s = dept_emp.emp_no  # 但是他又发了一些语句过去,这会他到dept_emp这这张表里给你查找个id等于10010的,原来他做了两次查询,他没做多表关联,查完在查一次
# 2020-08-08 12:17:24,168 INFO sqlalchemy.engine.base.Engine {'param_1': 10010} # 这里反正等于10010的有两条记录拿回来就行了
# [<Employee    no= 10010    name:Duangkaew  Piveteau  gender:F depts=[Dept_emp empno=10010 deptno=d004, Dept_emp empno=10010 deptno=d006]>] #拿回来了怎么办封装depts=进里面去 查回来了depts=[Dept_emp empno=10010 deptno=d004, Dept_emp empno=10010 deptno=d006]>]多条记录用逗号分隔

原文地址:https://www.cnblogs.com/hao-ran/p/13375333.html