python之网络

socket

网络开发模式:
C/S模式(客户端与服务端架构)使用特定的协议、特定的数据结构、隐藏的端口,安全性较高
B/S模式(浏览器与服务器架构)基于WEB设计的一种架构,一般基于HTTP协议完成处理,安全性不高,使用公共的80端口

TCP通讯

#python提供socket.socket类实现TCP程序开发
"""
socket():获取socket对象
bind(hostname, port):在指定主机端口绑定监听
listen():在绑定端口开启监听
accept():等待连接,连接后返回客户端地址
send(data):发送数据
recv(buffer):接收数据
close():关闭套接字连接
connect(hostname,port):设置要连接的主机名及端口号
"""
#server
import socket
SERVER_HOST = "localhost" #"127.0.0.1"
SERVER_PORT = 8080
def main():
	#可以利用with结构实现自动调用close()
	with socket.socket() as ser_socket: #创建服务端的socket
		ser_socket.bind((SERVER_HOST, SERVER_PORT)) #绑定服务端的IP端口,使用元组形式
		ser_socket.listen() #开启监听
		print("server 等待连接")
		cli_con,address = ser_socket.accept() #接受连接,进入阻塞状态等待连接,接收到连接请求后会返回客户端的socket和地址(元组类型)
		with cli_con: #处理客户端
			print("客户端地址:%s,连接端口:%s" % address) #客户端地址:127.0.0.1,连接端口:35997
			while True:
				data = cli_con.recv(100).decode("UTF-8") #接收数据,接收客户端数据
				if data.upper() == "END":
					cli_con.send("exit".encode("UTF-8")) #发送数据,给客户端,以二进制形式发送
					break
				else:
					cli_con.send("abc".encode("UTF-8")) 
"""
该socket程序是标准的TCP协议实现,可以直接使用telnet命令进行访问(windows操作系统提供的命令功能,win10需要手动进入设置安装)
telnet localhost 8080 测试服务端,访问本机8080端口
"""
#client
import socket
SERVER_HOST = "127.0.0.1" #网络服务器的主机名称或IP
SERVER_PORT = 8080
def main():
	with socket.socket() as cli_socket: #创建客户端的socket
		cli_socket.connect((SERVER_HOST, SERVER_PORT)) #连接服务端
		while True:
			input_data = input("输入数据:")
			cli_socket.send(input_data.encode("UTF-8"))
			data = cli_socket.recv(100).decode("UTF-8")
			if data.upper() == "EXIT":
				break
			else:
				print(data)

UDP通讯

#与TCP都是通过socket.socket类完成实现
#server
import socket
SERVER_HOST = "localhost" #"127.0.0.1"
SERVER_PORT = 8080
def main():
	#socket.AF_INET使用IPV4网络协议,socket.SOCK_DGRAM使用UDP数据报协议
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as ser_socket: #创建服务端的socket
		ser_socket.bind((SERVER_HOST, SERVER_PORT))
		print("server 已开启")
		while True:
			data, addr = ser_socket.recvfrom(30) #接收数据与地址
			print("客户端地址:%s,连接端口:%s" % addr)
			ser_data = ("响应:%s" % data.decode("UTF-8")).encode("UTF-8") #响应数据
			ser_socket.sendto(ser_data, addr) #发送数据,将响应数据发回发送端

#client
import socket
SERVER_HOST = "127.0.0.1" #网络服务器的主机名称或IP
SERVER_PORT = 8080
def main():
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as cli_socket: #创建客户端的socket
		while True:
			input_data = input("输入数据:")
			if input_data:
				cli_socket.sendto(input_data.encode("UTF-8"), (SERVER_HOST, SERVER_PORT)) #发送数据
				data = cli_socket.recv(100).decode("UTF-8")
				print(data)
			else:
				break
#UDP没有受到连接的控制

UDP广播

UDP可以实现数据广播的操作,可以实现一个局域网内所有主机信息的广播处理

"""
UDP广播处理方法
setsockopt(self, lever:int, optname:int, value:Union[int,bytes]) UDP广播处理方法,设置socket相关的一些选项
lever:设置选项所在协议层编号
		socket.SOL_SOCKET:基本套接字接口
		socket.IPPROTO_IP:IPV4套接字接口
		socket.IPPROTO_IPV6:IPV6套接字接口
		socket.IPPROTO_TCP:TCP套接字接口
optname:设置选项名称,如要进行广播可以使用"socket.BROADCAST"
value:设置选项的具体内容
"""
#定义广播接收端
import socket
BROADCAST_CLIENT_ADDR = ("0.0.0.0",21567) #设置广播客户端的地址,当前主机的21567
def main():
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as cli_socket: #创建客户端的socket
		cli_socket.setsockopt(socket.SOL_SOCKET, socket.BROADCAST, 1) #设置广播模式
		cli_socket.bind(BROADCAST_CLIENT_ADDR) #绑定广播客户端的地址
		while True: #不断接收数据
			message, address = cli_socket.recvfrom(100) #接收广播数据
			print("消息内容:%s  消息来源:ip:%s  port:%s" % (message.decode("UTF-8"), address[0], address[1]))
#广播发送端
import socket
BROADCAST_SERVER_ADDR = ("<broadcast>",21567) #广播地址,要明确写出要发送的是广播
def main():
	#socket.AF_INET使用IPV4网络协议,socket.SOCK_DGRAM使用UDP数据报协议
	with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as ser_socket: #创建服务端的socket
		ser_socket.setsockopt(socket.SOL_SOCKET, socket.BROADCAST, 1)#设置广播模式
		ser_socket.sendto("abcd".encode("UTF-8"), BROADCAST_SERVER_ADDR)
#广播处理时只需要设置有一个"<broadcast>"地址就可以实现广播的处理,接收端不开启就不能保证信息可以进行正常接受 

HTTP

#HTTP请求模式
"""
GET:请求指定页面信息,并返回实体主体
HEAD:类似get,但返回的响应没有具体内容,用于获取请求头部信息
POST:向指定资源提交数据进行处理请求(提交表单或上传文件)
PUT:从客户端向服务端传送的数据取代指定的文档内容
DELETE:请求服务器删除指定页面
CONNECT:HTTP/1.1协议中预留给能将连接改为管道方式的代理服务器
OPTIONS:允许客户端查看服务器性能
TRACE:回显服务器收到的请求,主要用于测试或诊断
"""
#HTTP常用请求头信息
"""
Accept:设置客户端显示类型:Accept:text/plain,text/html
Accept-Encoding:设置浏览器可以支持的压缩编码类型:Accept-Encoding:compress,gzip
Accept-Language:浏览器可接收的语言:Accept-Language:en,zh
Cookie:将客户端保存的数据发送到服务端:Cookie:name=abc;pwd=def
Content-Length:请求内容长度:Content-Length:257
Content-Type:请求与实体对应的MIME信息:Content-Type:application/x-www-from-urlencoded
Host:请求主机:Host:www.xxxx.com
Referer:访问来路:Referer:http://www.xxxx.com/test.html
"""
#HTTP响应状态码
"""
1xx:信息,服务器收到请求,需要请求者继续执行操作
2xx:成功,操作被成功接收并处理
3xx:重定向,需要进一步的操作以完成请求
4xx:客户端错误,请求包含语法错误或无法完成请求
5xx:服务器错误,服务器在处理请求过程中发生错误
200:成功处理请求,并返回处理结果
404:客户端访问页面路径有错误
500:服务器程序执行出现异常
"""
#HTTP常用响应头信息
"""
Content-Encoding:返回压缩编码类型:Content-Encoding:gzip
Content-Language:响应内容支持的语言:Content-Language:en,zh
Content-Length:响应内容的长度:Content-Length:257
Content-Type:响应数据的MIME类型:Content-Type:text/html;charset=utf-8
Last-Modified:请求资源的最后修改时间:Last-Modified:Tue,15 Nov 2023 03:25:45 GMT
Location:重定向路径:Location:http://www.xxxx.com/test
refresh:资源定时刷新配置:refresh:5;url=http://www.xxxx.com/test.html
Server:Web服务器软件名称:Server:Apache/1.3.27(Unix)(Red-Hat/Linux)
Set-Cookie:设置Http Cookie:Set-Cookie:name=asd;pwd=zxc
"""

HTTP响应

import socket
import multiprocessing #为每次请求开启新进程处理
class HttpServer: #定义http服务器类
	def __init__(self, port): #服务器一定要有监听端口
		self.ser_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建socket实例
		#考虑到不同系统的问题,80端口是系统核心端口,必争的端口,因此加上处理操作,方便将80端口进行服务的绑定
"""
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在,这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错
SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可,对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器
SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器
SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上,一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)
		"""
		self.ser_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.ser_socket.bind(("0.0.0.0", port)) #绑定监听端口,绑定本机
		self.ser_socket.listen()
	def start(self): #服务器开始提供服务
		while True:  #持续提供服务
			cli_socket, cli_addr = self.ser_socket.accept() #接受用户请求
			print("客户端地址:%s:%s" % cli_addr)
			cli_process = multiprocessing.Process(target=self.cli_response, args=(cli_socket, ))
			cli_process.start()
	def cli_response(self, cli_socket): #对指定的socket客户端进行响应
		headers_data = cli_socket.recv(1024) #用户通过浏览器发送的请求本身带有头信息
		print("客户端请求头信息:%s" % headers_data.decode())
		res_start_line = "HTTP/1.1 200 OK \r\n" #本次响应成功
		res_headers = "Server: xxxx Server \r\nContent-Type:text/html\r\n" #可以添加更多的头信息
		res_body = 	"<html>" \
					"	<head>" \
					"		<title>hello</title>" \
					"			<meta charset='UTF-8'/>" \
				    "	</head>" \
					"	<body>" \
					"		<h1>test001</h1>" \
					"		<h1>test002</h1>" \
					"	</body>" \
					"</html>"
		res = res_start_line + res_headers + "\r\n" + res_body #最终的响应内容
		cli_socket.send(bytes(res, "UTF-8")) #转成字节,decode也行
		cli_socket.close() #HTTP不保留用户状态,每次处理之后服务都要断开连接,保留的话就会造成严重的性能开支
def main():
	#80是HTTP默认端口,若使用80端口直接输入域名即可(http://www.xxxx.com/),不使用80端口则必须输入具体的端口(http://www.xxxx.com/8083)
	http_server = HttpServer(80)
	http_server.start() #服务开始
if __name__ == '__main__':
    main()
"""
客户端地址:127.0.0.1:45292
客户端地址:127.0.0.1:45293
客户端请求头信息:GET / HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


客户端请求头信息:GET /favicon.ico HTTP/1.1
Host: localhost
Connection: keep-alive
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69
sec-ch-ua-platform: "Windows"
Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://localhost/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


客户端地址:127.0.0.1:45300
"""
#所有的HTTP服务器一定要通过浏览器进行访问。现在服务器绑定在本机80端口上,就可以直接进行本地服务器访问,在浏览器输入"http://localhost"即可

响应目录

建立专属的html响应代码目录,所有要响应的内容都保存在此目录之中,根据用户请求的路径找到指定的文件进行内容的加载,并将此内容返回给用户,页面维护时只需要修改响应目录中的文件即可

#在每次进行请求响应的时候根据用户发来的地址来进行代码的解析
#'\r\n'是请求头信息里的分隔符
#手动建立存放响应html代码文件或二进制文件的响应目录
import re #利用正则来匹配响应路径
import os #进行文件路径的定义
HTML_ROOT_DIR = os.getcwd() + os.sep + "testhtml" #响应目录
#......
	def cli_response(self, cli_socket): #对指定的socket客户端进行响应
		headers_data = cli_socket.recv(1024)
		filename = re.match(r"\w+ +(/[^ ]*)",headers_data.decode().split("\r\n")[0]) #GET /favicon.ico HTTP/1.1
		#路径的名称就是响应目录文件的名称,filename==/favicon.ico
		if filename=='/': #匹配根路径
			filename = "/index.html" #确定根路径的文件的名称
		if filename.endswith(".html") or filename.endswith(".htm"): #响应普通的HTML代码
			cli_socket.send(bytes(self.get_html(filename), "UTF-8"))
		else: #有可能是二进制文件内容(例如图标)
			cli_socket.send(self.get_binary_file(filename)) #响应二进制数据

		#print("客户端请求头信息:%s" % headers_data.decode())

		cli_socket.close()
	def read_file(self, filename): #读文件数据
		filepath = os.path.normpath(HTML_ROOT_DIR + filename) #文件的完整路径
		file = open(filepath, "rb") #采用二进制流形式进行读取
		filedata = file.read()
		file.close()
		return filedata
	def get_html(self, filename):#读取指定的文件
		res_start_line = "HTTP/1.1 200 OK \r\n"
		res_headers = "Server: xxxx Server \r\nContent-Type:text/html\r\n"
		res_body = self.read_file(filename).decode("UTF-8")#设置响应内容
		res = res_start_line + res_headers + "\r\n" + res_body  # 最终的响应内容
		return res
	def get_binary_file(self,filename): #获取二进制文件
		res = self.read_file(filename)
		return res
#......

动态请求处理

web开发实际分两个处理阶段,静态WEB处理(响应目录),动态WEB处理:可以进行动态的判断决定最终返回的数据内容

#此时响应内容不是html,而是一个python处理程序包
#建立一个包"pagestest",建立一个模块"test.py"(动态的响应程序)
#encoding:UTF-8
def service(param): #响应的处理函数
    if param: #如果param有数据
        return "<h1>" + param + "</h1>"
    else:
        return "<h1>NOPARAM</h1>"


#在进行HTTP处理时加载上面程序文件
#浏览器:http://localhost/pagestest/test/service?param=abcdef
import sys #进行模块加载的路径定位
sys.path.append("pagestest") #追加模块加载路径
#......
def cli_response(self, cli_socket): #对指定的socket客户端进行响应
		headers_data = cli_socket.recv(1024)
		filename = re.match(r"\w+ +(/[^ ]*)",headers_data.decode().split("\r\n")[0])
#GET /pagestest/test/service?param=abcdef HTTP/1.1 ====>/pagestest/test/service?param=abcdef
		if filename.startswith("/pagestest"): #当前要访问的是一个动态页面
            request_name = filename[filename.index("/", 1) + 1 : ] #访问路径,截取 test/service?param=abcdef
            param_value = "" #请求参数
            if request_name.__contains__("?"): #"?"参数的路径分隔符
                request_param = request_name[request_name.index("?") + 1 : ] #param=abcdef
                param_value = request_param.split("=")[1] #获取参数内容:abcdef
                request_name = request_name[0: request_name.index("?")] #获取模块名称  test/service
            model_name =request_name.split("/")[0] #得到模块名称 test
            method_name = request_name.split("/")[1] #得到函数名称 service
            model = __import__(model_name) #加载模块
            method = getattr(model, method_name)
            response_body = method(param_value) #"<h1>abcdef</h1>"
            res_start_line = "HTTP/1.1 200 OK \r\n"
		    res_headers = "Server: xxxx Server \r\nContent-Type:text/html\r\n"
		    res = res_start_line + res_headers + "\r\n" + response_body  # 最终的响应内容
            cli_socket.send(bytes(res, "UTF-8"))
		cli_socket.close()
#......

urllib3

urllib是python提供的url请求访问的模块,可以实现浏览器的模拟访问,urllib3主要是为python3服务,两者功能类似,只有一些细微的差别

#实现请求的发送
import urllib3
URL = "https://www.xxxx.com/video/23" #访问路径
def main():
	request_headers = {"User-Agent":"abc"} #请求头信息
	http = urllib3.PoolManager(num_pools=5, headers=request_headers) #获取urllib3的进程管理对象,num_pools设置进程池大小,headers设置请求头
	res = http.urlopen("GET", URL) #发送GET请求
	print(res.headers) #获取响应头信息
	print(res.data.decode("UTF-8")) #获取网页信息(页面的代码访问)
#urllib3进行页面数据访问也是进行爬虫开发的重要组成的基础环节部分

twisted异步编程

twisted是python中专门实现异步处理的IO概念,主要为提升服务端数据的处理功能,传统的服务端,如果采用阻塞模式,会持续的发生等待问题,是会严重的损耗服务器的性能
twisted是一个事件驱动型的网络引擎,会提供一个事件循环处理,在外部事件发生时使用回调机制触发相应处理操作,多任务在同一线程中执行,可以使程序减少对其他线程的依赖,也可不再关注线程安全问题
twisted中所有处理事件(注册、注销、运行、回调处理等)全部交由reactor进行统一管理,在整个程序运行过程中,reactor循环会以单线程模式持续运行,在需要执行回调处理时reactor会暂停循环,在回调处理完成后继续采用循环形式进行其他任务的处理,就使得可以在网络协议栈的任何位置很容易的进行事件响应

#主要是对服务端开发,为资源利用带来极大便利
#tcp服务端
import twisted #需要下载
import twisted.internet.protocol #导入子模块
import twisted.internet.reactor #导入子模块
SERVER_PORT = 8080
#Server相当于要处理的核心事件的驱动类,自定义的程序类
class Server(twisted.internet.protocol.Protocol): #服务端一定要设置继承父类,事件驱动,根据需要选择覆写方法
    def connectionMade(self): #客户端连接时触发的操作
        print("客户端地址:%s"  % self.transport.getPeer().host) #直接得到每一个操作的连接,然后获取主机名称,表示连接触发

    def dataReceived(self, data): #接收客户端的数据
        print(data.decode("UTF-8")) #data是接收到的数据
        #回应数据
        self.transport.write(("回应的数据:%s" % data.decode("UTF-8")).encode("UTF-8")) #将原始的二进制数据加入字符串,再转为二进制数据
#reactor负责注册,负责轮询
#注册完成后就可以在reactor中去使用回调操作
class DefaultServerFactor(twisted.internet.protocol.Factory): #定义处理工厂类
    protocol = Server #注册回调操作

def main():
    #相当于将回调操作的处理程序放入工厂,工厂再将其放入reactor之中
    twisted.internet.reactor.listenTCP(SERVER_PORT, DefaultServerFactor()) #获得服务的监听
    print("服务器启动完成,等待连接......")
    twisted.internet.reactor.run() #进入事件轮询,轮询客户端的每一次状态

if __name__ == '__main__':
    main()
#整个处理流程就是定义好事件的处理回调程序,随后向工厂中进行注册,reactor依据工厂来获得相应事件的回调处理操作类

#tcp客户端,使用传统客户端也可以
import twisted
import twisted.internet.protocol
import twisted.internet.reactor
SERVER_HOST = "localhost" #服务主机
SERVER_PORT = 8080
class Client(twisted.internet.protocol.Protocol): #定义客户端处理类
    def connectionMade(self): #连接时触发的操作
        print("服务器连接成功,结束通讯则直接回车")
        self.send() #连接完成后可以直接进行数据发送
    def dataReceived(self, data): #接收服务端的数据
        print(data.decode("UTF-8"))
        self.send() #继续发送数据
    def send(self): #数据发送,自定义的方法
        input_data = input("输入数据:")
        if input_data:
            self.transport.write(input_data.encode("UTF-8"))
        else:
            self.transport.loseConnection() #关闭连接
class DefaultClientFactor(twisted.internet.protocol.ClientFactory): #继承的是客户端工厂
    protocol = Client #定义回调
    #连接断开                连接失败
    clientConnectionLost = clientConnectionFailed = lambda self,connector,reason:\
        twisted.internet.reactor.stop() #只要连接失败或连接关闭,就将reactor处理操作(循环的操作)停掉
def main():
    twisted.internet.reactor.connectTCP(SERVER_HOST, SERVER_PORT, DefaultClientFactor())  #进行连接
    twisted.internet.reactor.run() #程序运行
    
if __name__ == '__main__':
    main()

#此时避免了繁琐的并发控制的操作,没有多进程或多线程的操作控制部分,整个的执行流程是基于单线程的运行模式完成(python中的多线程存在有GIL全局锁问题,而该情况就解决了这种问题)

udp
不需要保证可靠连接,只需要定义好用户的处理回调操作即可

#服务端
import twisted
import twisted.internet.protocol
import twisted.internet.reactor
SERVER_PORT = 8080
class Server(twisted.internet.protocol.DatagramProtocol): #数据报协议
    def datagramReceived(self, datagram, addr): #接收数据处理
        print("消息来源:%s, %s" % addr)
        print("接收到消息内容:%s" % datagram.decode("UTF-8"))
        data = "回应信息:%s" % datagram.decode("UTF-8")
        self.transport.write(data.encode("UTF-8"), addr) #发送数据并设置输出的地址,即将数据发送给指定的客户端
#不再需要复杂的工厂操作
def main():
    twisted.internet.reactor.listenUDP(SERVER_PORT, Server()) #启动udp的监听
    print("服务器启动完成,等待连接......")
    twisted.internet.reactor.run() #进入事件轮询

if __name__ == '__main__':
    main()

#客户端
import twisted
import twisted.internet.protocol
import twisted.internet.reactor
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8080
CLIENT_PORT = 0 #客户端地址
class Client(twisted.internet.protocol.DatagramProtocol): #定义客户端处理类,数据报协议
    def startProtocol(self): #连接回调,开始处理操作
        self.transport.connect(SERVER_HOST, SERVER_PORT) #连接
        print("连接成功")
        self.send() #数据发送
    def datagramReceived(self, datagram, addr): #接收服务端的数据
        print(datagram.decode("UTF-8"))
        self.send() #继续发送数据
    def send(self): #数据发送,自定义的方法
        input_data = input("输入数据:")
        if input_data:
            self.transport.write(input_data.encode("UTF-8"))
        else:
            twisted.internet.reactor.stop() #直接使reactor停止轮询
def main():
    twisted.internet.reactor.listenUDP(CLIENT_PORT, Client())  #udp需要设置一个客户端的地址
    twisted.internet.reactor.run() #开启事件轮询

if __name__ == '__main__':
    main()
#使用udp处理时不再需要在中间进行连接的控制,同时也不需要通过工厂才可以与reactor进行衔接

Deferred

网络交互过程中可能需要下载一些庞大的文件内容,需要持续进行下载,但此时的客户端也进入了一个阻塞的状态,为解决这种问题,提供了Deferred的概念

#模拟deferred的调用
import twisted
import twisted.internet.defer
import twisted.internet.reactor
import time
class DeferHandle: #设置一个回调处理类
    def __init__(self):
        self.defer = twisted.internet.defer.Deferred() #获取Deferred对象
    def get_defer(self): #让外部获取实例
        return self.defer
    def work(self): #模拟网络下载
        print("模拟网络下载延迟操作...等待3秒")
        time.sleep(3)
        self.defer.callback("finish") #执行回调,"finish"传参
    def handle_success(self, result):
        print("处理成功,参数接收:%s" % result) #处理完毕后的信息输出
    def handle_error(self, exp): #错误回调
        print("程序错误:%s" % exp)
def main():
    defer_client = DeferHandle() #获得当前的回调操作
    twisted.internet.reactor.callWhenRunning(defer_client.work) #执行耗时操作
    defer_client.get_defer().addCallback(defer_client.handle_success) #设置执行完毕后的回调
    defer_client.get_defer().addErrback(defer_client.handle_error) #错误输出时的回调
    twisted.internet.reactor.callLater(5, stop) #5秒后停止reactor的调用
    twisted.internet.reactor.run() #启用事件循环
def stop():
    twisted.internet.reactor.stop()
    print("服务调用结束")

if __name__ == '__main__':
    main()
"""
模拟网络下载延迟操作...等待3秒
处理成功,参数接收:finish
服务调用结束
"""
#所有的事件处理操作全部交给reactor完成
#整个程序执行完毕后,可以直接利用所设置的callback()操作去进行操作完成后的调用,基于此操作模型就减少了并发编程的使用


#通过deferred模型实现tcp协议的客户端
#Deferred还可以再启动一个自己管理的Deferred线程
import twisted
import twisted.internet.protocol
import twisted.internet.reactor
import twisted.internet.defer
import twisted.internet.threads #twisted自己控制的线程,不需要开发者控制
import time

SERVER_HOST = "localhost"  # 服务主机
SERVER_PORT = 8080

class DeferClient(twisted.internet.protocol.Protocol):  #回调处理类
    def connectionMade(self):  # 连接时触发的操作
        print("服务器连接成功,结束通讯则直接回车")
        self.send()
    def dataReceived(self, data):  # 接收服务端的数据,假设内容很大
        content = data.decode("UTF-8")
        twisted.internet.threads.deferToThread(self.handle_request, content).addCallback(self.handle_sucess) #通过twisted创建一个由twisted管理的操作线程
    def handle_request(self, content): #数据处理过程
        print("客户端处理服务器数据:%s, 此处会产生1秒的延迟" % content)
        time.sleep(1) #模拟延迟
        return content #返回处理结果
    def handle_sucess(self, result): #操作处理完毕
        print("处理完成,接收参数:%s" % result)
        self.send() #继续进行下一次的数据发送
    def send(self):  # 数据发送,自定义的方法
        input_data = input("输入数据:")
        if input_data:
            self.transport.write(input_data.encode("UTF-8"))
        else:
            self.transport.loseConnection()  # 关闭连接

class DefaultClientFactor(twisted.internet.protocol.ClientFactory):  # 继承的是客户端工厂
    protocol = DeferClient #设置回调处理
    clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: \
        twisted.internet.reactor.stop()  # 只要连接失败或连接关闭,就将reactor处理操作(循环的操作)停掉
    
def main():
    twisted.internet.reactor.connectTCP(SERVER_HOST, SERVER_PORT, DefaultClientFactor())  # 进行连接
    twisted.internet.reactor.run()  # 程序运行

if __name__ == '__main__':
    main()

pymysql模块

MySQL配置:
windows下:

  1. 配置环境变量
  2. 创建数据目录:存储真实数据及日志(data、logs)
  3. 在mysql工具目录下创建mysql.ini配置文件(网上查询),用来建立mysql工具目录与数据目录的联系
  4. 数据库初始化:进行文件存储一定要生成一些属于自己的结构性文件,这些文件也保存在数据目录,mysqld -initialize --console,会自动生成一个临时密码,进行mysql数据库的访问
  5. 服务安装(windows本身提供系统服务管理,可通过服务进行mysql服务启动,将启动命令添加到服务中(命令行中执行),需要管理员操作权限):mysqld install 删除服务:mysqld remove
  6. 开启/关闭服务:net start mysql\ net stop mysql,就可以进行登录使用了
#python对于mysql数据库的开发操作提供有两个核心模块:pymysql(python3)、mysqldb(python2),差别并不是很大,需要单独安装
#使用pymysql.connect()工厂函数获取数据库连接对象实例("pymysql.connections.Connection")
"""
def cmd_ping() 数据库连接测试
def get_server_info() 获得数据库版本编号
def get_host_info() 获得主机信息
def get_proto_info() 获得协议信息
def cursor() 创建一个数据库操作对象
def select_db(self, db) 设置要使用的数据库
def close() 关闭数据库连接
"""
#传统的pymysql支持的是原生的SQL操作
import pymysql
import traceback
SQL = "SQL语句"
try:
	#连接数据库
	con = pymysql.connect(host = "主机名或ip", port = "端口号", charset = "UTF8", user = "用户名", password = "密码", database = "数据库名")
	print(con.get_server_info())
	print(con.get_autocommit()) #事务提交模式
	cmd = con.cursor() # 获得一个数据库操作对象
	cmd.execute(SQL) #执行SQL语句
	con.commit() #提交事务,如果不提交事务,更新不生效
	print("更新影响的数据行数:%s" % cmd.rowcount)
	print("最后一次增长id:%s" % cmd.lastrowid) #相当于在命令行执行了SELECT LAST_INSERT_ID()查询
except Exception:
	print(traceback.format_exc()) #异常输出
finally:
	con.close()
"""
rowcount 返回影响的数据行数
lastrowid 返回LAST_INSERT_ID()函数执行结果
def execute(self, query, args = None) 执行单行SQL语句并传入相关参数
def executemany(self, query, args) 执行多行SQL语句并传入相关参数
def callproc(self, procname, args = ()) 调用数据库存储过程并传入相关参数
def nextset(self) 移动到下一个结果集
def fetchone(self) 返回一条查询结果
def fetchmany(self,size = 1) 返回多行查询结果,行数由size设置
def fetchall(self) 返回全部查询结果
"""

#数据库查询时,所有查询结果都会返回到程序调用处,若查询结果过大,有可能会造成内存溢出,所以要尽可能控制查询结果的大小,
con = pymysql.connect(host = "主机名或ip", port = "端口号", charset = "UTF8", user = "用户名", password = "密码", database = "数据库名")
cmd = con.cursor()
cmd.execute(SQL)
#返回单条查询结果时,直接:print("%s" % cmd.fetchone())
for i in cmd.fetchall(): #返回的查询结果可以理解为一个元组列表,每行数据通过元组来描述
	var1 = i[0]
	var2 = i[1]
	print("%s:%s" % (var1, var2))

#预处理,单纯的拼凑SQL语句可能会解析出错,如'%s' % "a'b" == 'a'b'
SQL = "insert into users(id, name) values(%s, %s)" #预处理模式
var1 = 20
var2 = "abc"
con = pymysql.connect(host = "主机名或ip", port = "端口号", charset = "UTF8", user = "用户名", password = "密码", database = "数据库名")
cmd = con.cursor()
cmd.execute(query = SQL, args = [var1, var2]) #args可为列表、元组或字典
con.commit()

#批处理
SQL = "insert into users(id, name) values(%s, %s)" #预处理模式
con = pymysql.connect(host = "主机名或ip", port = "端口号", charset = "UTF8", user = "用户名", password = "密码", database = "数据库名")
cmd = con.cursor()
tmp_list = []
for i in range(1000):
	tmp_list.append((i, "n%s" % i))
	if i%20 == 0:
		cmd.executemany(query = SQL, args = tmp_list) #args为序列
		tmp_list.clear()
con.commit()

#事务处理
"""
def autocommit(self, value) 设置是否自动提交,True为自动提交,自动事务提交会将单条SQL语句作为一条事务
def get_autocommit(self) 获取当前自动提交配置,默认False
def commit(self) #事务提交
def rollback(self) #事务回滚
"""
try:
	con = pymysql.connect(host = "主机名或ip", port = "端口号", charset = "UTF8", user = "用户名", password = "密码", database = "数据库名", autocommit = False) #autocommit 设置是否自动提交事务
	cmd = con.cursor()
	cmd.execute(SQL)
	con.commit() #代码执行正确事务提交
except Exception:
	con.rollback() #代码执行错误事务回滚
finally:
	con.close()
#不采用事务的自动提交,采用手动处理,可以明确事务的提交或回滚操作

#数据库连接池(进行数据库连接管理的区域)
#在连接池保存多个已经存在的数据库连接,用户依次获取连接,若连接池的连接已经用完,则进行等待,而不是创建新连接(持续创建会造成服务器性能下降),等待使用者放回数据库连接资源后再进行获取
#引入"DBUtils"模块
import pymysql
import DBUtils.PooledDB #子模块
pool = DBUtils.PooledDB.PooledDB(
	creator = pymysql, #连接池管理的是pymysql的操作类型,即使用连接池的数据库组件
	mincache = 2, #空闲时维持两个连接,最小维持连接数,不足则创建新连接
	maxcache = 5, #空闲时不超过5个连接,最大维持连接数,超过则关闭部分连接
	maxconnections = 20, #连接池维护的最大连接数(需要进行测试得到结果,根据程序性能与当下服务器的匹配程度来决定最终能开启多少的连接)
	blocking = True, #是否引入阻塞队列,True为是,在连接池已满时进行等待,False则不等待并报错
	host = "主机名或ip", 
	port = "端口号",
	user = "用户名",
	 password = "密码",
	database = "数据库名",
	charset = "UTF8"
)
"""
maxshared 连接池中最多可以共享的连接数量,0表示全部共享
maxusage 一个连接最多重复使用的次数
setsession 会话开始前的操作命令列表
ping 检查服务是否可用(默认值1)(0不进行服务器连接检查;1请求的时候进行服务器连接检查;2当创建Cursor数据库操作对象时检查;4当执行查询时检查;7任何操作都进行检查)
"""
con = pool.connection() #获取数据库连接
cmd = con.cursor()
cmd.execute(SQL)
con.commit()
#通过数据库连接池进行数据库的连接管理,实现传统SQL数据库操作的性能提升

SQLAlchemy

SQLAlchemy描述的是一个ORMapping组件,往往不同的数据库需要使用不同的操作组件,而SQLAlchemy是python中一款开源的ORM工具,是一种简单高效的企业级数据模型操作技术,针对于数据库开发提供了更加丰富的处理功能,对外部隐藏了SQL语句,而后利用对象的形式进行数据库操作(通过程序去调用ORMapping组件,ORMapping组件再去处理调用数据库组件完成对数据库的SQL处理)

#使用SQLAlchemy需要知道特定的数据库组件,随后通过SQLAlchemy操作引擎管理数据库组件(管理数据库连接),在进行数据操作时创建若干个Session(可以理解为一个ORM组件中对数据库连接的包装,通过这个包装类对象实例通过映射对象(用户自定义)的形式进行数据库的数据处理),就能基于数据表的映射实体类进行数据操作
#简单说就是需要设置一个用户与数据表的映射结构类型(自定义的),Session通过操作该结构类型就能直接操作数据表,整个操作过程是通过数据库连接的管理引擎来完成的
"""
Session类常用方法:
def add(self, instance, _warn = True) 向数据库保存一个实体对象
def add_all(self, instances) #向数据库中保存一组实体
def merge(self, instance, load = True) #合并数据实体(更新)
def query(self, *entities, **kwargs) #数据查询
def delete(self, instance) 删除指定的对象实体
def commit(self) #事务提交
def rollback(self) #事务回滚
"""
import sqlalchemy
import sqlalchemy.ext.declarative #为了继承父类结构定义
import sqlalchemy.orm #该开发包描述的是ORM的所有工具
import sqlalchemy.orm.session #数据库操作的核心,是数据操作的主要类,想通过映射类去进行数据库操作,就需要通过session来进行处理

#定义mysql数据库方言(使用的数据库类型,直接在连接上通过字符串形式定义了)以及连接管理操作
#mysqlconnector,mysql-connector操作组件,也需要安装
MYSQL_URL = "mysql+mysqlconnector://用户名:密码@主机:端口/数据库名"

#在每一个ORM项目中,每一个映射类都代表一张表
class user(sqlalchemy.ext.declarative.declarative_base()): #自定义映射结构类型
	__tablename__ = "user" #数据表名称,明确对表进行定义在类中使用__tablename__属性即可定义
	uid = sqlalchemy.Column(sqlalchemy.BIGINT, primary_key = True) #属性与表字段的映射,映射uid字段,同时定义好与数据库中结构匹配的数据类型
	name = sqlalchemy.Column(sqlalchemy.String) #映射name字段
	def __repr__(self)->str:
		return "uid:%s, name:%s" % (self.uid, self.name)

def main():
	engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True) #echo = True返回所有的操作信息
	sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine) #创建一个Session类型,bind :绑定一个执行引擎
	session = sqlalchemy.orm.session.Session() #实例化session对象
	
	user = User(uid = 1, name = "abc")
	session.add(user) #对象操作,相当于SQL处理(ORM引擎转换),插入数据
	session.commit() #事务提交

	user2 = session.query(User).get(1) #查询id为1的数据,仅仅只能实现根据id的查询需求
	print(user2)

	#由于所有的ORM组件都包含有对象状态的处理问题,所以对于修改、删除就不是很友好了
	user3 = User(uid = 1, name = "def")
	session.merge(user3) #修改数据(生成的SQL语句中包含有查询)
	session.commit()

	#由于标准的ORM需要确定对象状态,删除之前必须先查询对象实体,再根据对象实体进行数据删除
	user4 = session.query(User).get(1) #查询id为1的数据
	session.delete(user4) #删除实体(ORM以实体为主,而不是以SQL操作为主)
	session.commit()

	session.close() #关闭session(释放连接)

#数据查询,ORM组件不适合编写特别复杂的查询,复杂查询还是编写原生的查询SQL语句
#session.query(User)可以获得一个查询对象"sqlalchemy.orm.query.Query"
"""
类中常用操作方法
def get(self, ident) #获取指定ID数据
def all(self) #获取全部查询结果
def one(self) #获取一个查询结果
def filter_by(self, **kwargs) #根据指定字段设置过滤条件
def filter(self, *criterion) #设置过滤条件
def slice(self, start, stop) #设置分页查询
def offset(self, offset) #设置分页开始点
def limit(self, limit) #设置获取的结果数量
def distinct(self, *expr) #消除重复数据行
"""
user = session.query(User).filter_by(User.name = 1).all() #filter_by设置过滤条件(普通字段需要User.name),仅仅定义好了要查询的SQL语句,all()之后才真正发出查询返回数据,返回的数据类型是列表
user = session.query(User).filter(uid < 5).all()
#分页查询
user_list = session.query(User).filter(User.name.like("%a%")).all()[0:1] #相当于将全部数据加载到内存中再进行分页,如果是数据查询后再进行切片处理,数据量较大时,会产生严重的性能问题
#真正的分页:
user_list = session.query(User).filter(User.name.like("%a%")).offset(0).limit(1).all() #设置查询的偏移量和获取的数量
user_list = session.query(User).filter(User.name.like("%a%")).slice(0,1).all()

user_list = session.query(User).filter(User.uid.in_([1, 3])).all() #查询uid==1,3的数据行,in的谓词操作,范围查询
user_count = session.query(sqlalchemy.func.count(User.uid)).one() #数据行的统计操作

ORM对象状态
ORM开发框架对于每个实体对象都存在有状态的维护以保证数据的同步性

  • 瞬时态:新创建的实体对象,还没有与Session建立关联
  • 预备态:与Session产生关联。但该数据还未更新到数据库
  • 持久态:更新到数据库,所作修改会与数据库中的数据同步
  • 删除态:从数据库中删除对象实体
  • 游离态:session关闭后该对象与数据库持久化数据之间断开连接,所做修改不会同步到数据表中
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
	
user = User(uid = 1, name = "abc") #瞬时态
session.add(user) #瞬时态进入预备态(与session关联)
session.commit() #预备态变成持久态
user.name = "aaa" #修改持久态下的属性内容
"""
insert, commit, select, update, commit
insert:预备态进入持久态
select:数据存放到数据库,但程序不认该数据的持久态,因此通过查询语句将内存中的user对象设为持久态,将数据库中的数据与user对象做好关联
update持久态更新(直接发出数据库的更新命令)
"""
session.commit()
"""
持久态的维护是由SQLAlchemy组件自行完成处理的,自行处理中就必须由ORM组件自己维护对象状态,所以才会在增加完成后出现有一个SELECT查询语句以维护对象状态
"""

#除了insert形式将操作进入到持久态,还可以通过查询模式来获得持久态对象
user2_a = session.query(User).get(1)
user2_a.name = "bbb" #修改持久态下的数据
user2_b = session.query(User).get(1)
user2_b.uid = 2
user2_c = session.query(User).get(1)
print(user2_c)
"""
ORM组件之中是根据主键的内容实现持久态的存储,若进行数据查询时且是根据id查询,即便重复查询多次,也仅仅是发出一次的查询命令
"""
"""
操作结果:2, bbb
由于是在持久态下进行了修改,所以在没有与数据库同步之前,相同id的持久态下的所有对象都需要发生内容的变更
而该变更实际上与数据库中的内容是完全不同的(相当于缓存的操作过程),想获得真正的数据可以尝试关闭连接
"""
session.close()
"""
由于session关闭影响,导致了持久态的结束,既然没有持久态下的指定id为1的用户信息
所以第三次查询时就必须重复发出一次数据库的查询指令
(之前所作的持久态的变更由于没有提交事务也就不会生效)
"""

user2_a = session.query(User).get(1)
user2_a.name = "bbb"
session.commit()
"""
持久态变更,事务未提交不会与数据库的数据同步,事务提交时,持久态数据会自动同步到数据表中
如果此时的user对象不是通过id查询出的内容,而是用户自定义的新的user对象,就必须先由瞬时态转为持久态(需要发出一次查询指令),而后再进行更新处理
"""

user3 = User(uid = 1, name = "def")
session.merge(user3) #瞬时态进入预备态
session.commit() #进入持久态
user3.name = "ddd" #数据库的数据未发生变化
session.commit()

session_a = sqlalchemy.orm.session.Session()
user = session_a.query(User).get(1) #持久态,发出查询
session_a.close() #游离态(user对象从session_a中消失了)
session_b = sqlalchemy.orm.session.Session()
user = session_b.query(User).get(1) #游离态转为持久态,发出查询
user.name = "ccc" #持久态下修改属性内容
user.uid = 4
session_b.merge(user) #持久态合并
session_b.commit() #事务提交
session_b.close() #持久态转为游离态
#删除态delete()

"""
使用ORM组件最重要的特点是进行持久态的数据更新操作,直接进行属性内容的修改就可以直接影响到真实数据
持久态由于需要保持对象的状态,所以有可能出现后续查询与真实数据不一致的问题,将持久态变为游离态重新查询即可
游离态下的数据无法进行数据的同步修改(修改属性没有效果)
"""

执行原生SQL

#进行数据删除时必须通过一个持久态对象才能删除,而进行数据查询时所有的数据也会被进行缓存处理,会引发一些问题,如一直在持久态下进行删除,每次必然会重复发出一条无意义的数据查询(获取持久态),如果一直持续保持持久态,内存在数据量较大时也可能出现问题(批量操作)
#因此在正常开发过程中可以利用原生SQL进行操作(避开ORM设计问题)
import random
SQL = "delete from user where uid=:uid" #":uid"描述的是一个占位符
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
res = session.execute(SQL, {"uid": 1}) #执行SQL处理
print("删除的数据行数:%s" % res.rowcount)
session.commit()
session.close()
#避免了每次数据删除前的数据持久态的操作控制,最重要的是解决了数据的批量更新问题
res = session.execute(
	User.__table__.insert(), #获取内部生成的SQL语句
	[{"name":"abc-%s" % random.randint(1,999), "uid":i} for i in range(100)]
)# 执行批量插入数据SQL处理
session.commit()
session.close()
#对于批量数据的操作,不使用原生的ORM组件"session.add()",该做法很可能造成内存溢出问题,尽量使用原生SQL

#ORM组件对复杂查询的支持或实现是困难的,可以采用原生的SQL执行查询
SQL = "select uid,name from user limit :start, :size"
res = session.execute(SQL, [{"start": 0, "size":10}]) #执行SQL处理
print(res.rowcount)
for i in res.fetchall():
	print(i)
session.close()

数据关联

#一对多,两张表
#两个映射类:Company(cid,cname,site)、dept(did,cid,dname)
#外键关联:
class Company(sqlalchemy.ext.declarative.declarative_base()):
	__tablename__ = "company" 
	cid = sqlalchemy.Column(sqlalchemy.String, primary_key = True) 
	cname = sqlalchemy.Column(sqlalchemy.String)
	site = sqlalchemy.Column(sqlalchemy.String)
	depts = sqlalchemy.orm.relationship("Dept", order_by="Dept.cid", backref="company")
	#设置映射关系,backref引用哪个进行处理
	def __repr__(self)->str:
		return "%s, %s, %s" % (self.cid, self.cname, self.site)

class Dept(sqlalchemy.ext.declarative.declarative_base()):
	__tablename__ = "dept" 
	did = sqlalchemy.Column(sqlalchemy.BigINT, primary_key = True) 
	dname = sqlalchemy.Column(sqlalchemy.String)
	cid = sqlalchemy.Column(sqlalchemy.String, sqlalchemy.ForeignKey("company.cid")) #外键关联
	def __repr__(self)->str:
		return "%s, %s, %s" % (self.did, self.dname, self.cid)
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
dept_list = [Dept(dname="a"), Dept(dname="b"), Dept(dname="c")] #部门列表
company = Company(cid = "001", cname = "hhh", site = "abcdef", depts = dept_list) #构建company对象
session = sqlalchemy.orm.session.Session()
sessoin.add(company) #Company数据的保存
#当一方的数据保存完后,会利用一方的外键内容进行多方的字段填充
session.commit() #持久态,为维持部门数据对象的持久态,会自动生成查询语句进行查询
#也可直接对现有数据进行追加
dept = Dept(dname = "d", cid = "001")
sessoin.add(dept)
session.commit()
for dept in dept_list:
	print(dept.did)
session.close()

#数据查询
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
company = session.query(Company).get("001") #根据id查询公司信息
print(company)
print(company.depts)
session.close()
#内连接查询
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
company = session.query(Company).join(Dept).filter(Company.cid==Dept.cid).filter(Company.cid=="001").filter(Dept.dname=='a').one()
print(company)
session.close()
#关联关系如果仅仅使用基本操作,还可以保持性能,但若是复杂查询,还是使用原生SQL查询
#多对多,三张表
#由于多对多关联中需要有中间关系表进行处理,所以整体实现中需要定义两个基础映射类(user,role)和一个关系映射结构(user_role关系表)
#设置中间表的处理结构
user_role = sqlalchemy.Table("user_role", Base.metadata, #父类
		sqlalchemy.Column("uid",sqlalchemy.String,sqlalchemy.ForeignKey("user.uid"),
							nullable=False,primary_key=True)
		sqlalchemy.Column("rid",sqlalchemy.String,sqlalchemy.ForeignKey("role.rid"),
							nullable=False,primary_key=True))
#两张表需要设置主控关系,主控的点决定了多对多的关系的维护,由用户控制角色,增加/删除用户的时候要去处理角色,处理的不是真实的表,而是中间关系表
class User(sqlalchemy.ext.declarative.declarative_base()):
	__tablename__ = "user" 
	uid = sqlalchemy.Column(sqlalchemy.String, primary_key = True) 
	name = sqlalchemy.Column(sqlalchemy.String)
	roles = sqlalchemy.orm.relationship("Role", secondary=user_role, backref="user") #用户为主控方,secondary处理的依据,找到用户关键性的角色的配置,user要可以控制user_role这张表
	def __repr__(self)->str:
		return "%s, %s" % (self.uid, self.name)
class Role(sqlalchemy.ext.declarative.declarative_base()):
	__tablename__ = "role" 
	rid = sqlalchemy.Column(sqlalchemy.String, primary_key = True) 
	title = sqlalchemy.Column(sqlalchemy.String)
	def __repr__(self)->str:
		return "%s, %s" % (self.rid, self.title)
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
roles = session.query(Role).filter(Role.rid.in_(["a", "b"])).all() #查询角色信息
user = User(uid="1", name="abc", roles=roles) #创建新的用户
session.add(user)
session.commit()
session.close()
#若想正确的进行用户的信息处理,必须通过程序处理"user_role"关系表内容,又牵扯出持久态的问题,若没有持久态,新的角色数据又会牵扯到角色新增加问题
#查询
engine = sqlalchemy.create_engine(MYSQL_URL, encoding = "UTF8", echo = True)
sqlalchemy.orm.session.Session = sqlalchemy.orm.sessionmaker(bind = engine)
session = sqlalchemy.orm.session.Session()
user = session.query(User).get("a") #获取用户信息
print(user)
for i in user.roles:
	print(i)
session.close()
#获取角色的地方每次都是用延迟的概念来完成的(延迟加载),获取角色时会涉及到多表查询
#考虑性能问题的话,多表查询一般不会在工作中直接使用(除非是自然连接),这种自动化的ORM组件基本都是类似的处理形式,针对于该问题使用单表的操作处理会比较合适性能的控制
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值