第四章 多任务编程
4.1 多任务介绍
-
多任务:一个时间段内,执行多个任务
-
多任务的执行方式:
-
并发:多个任务交替的去执行(单核CPU一定是并发)
-
并行:在多核CPU中,多个任务在多个CPU上同时执行,并行
一般来说,并发和并行是同时存在的,是操作系统自动调度
-
-
多任务的实现方法(编码实现):
- 多进程实现
- 多线程实现
- 协程实现
-
时间片:操作系统分配给程序的一小段CPU时间。
4.2 进程
概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说没启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行
注意:
一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程
进程是操作系统资源分配的基本单位
创建一个进程,就会把主进程的资源复制一份
进程的状态切换
-
(1 ==> 2)新建态(new):新创建一个进程,分配资源
-
(2 <==> 3)就绪态(ready):万事俱备,只欠时间片
-
( 3 ==> 4; 3 ==> 5 )运行态(running)<==>:进程获得了时间片,可以运行
-
(4 ==>2)等待态/阻塞态(waitting/blocking)<==>:等待外部条件的满足
-
死亡态:进程执行结束
注意:
- 只能由就绪态到运行态
- 阻塞的条件满足,会再次进入就绪态,等待CPU
4.3 多进程的使用
1. 导入进程包
import multiprocessing
2. 创建进程对象
进程对象 = multiprocessing.Process(target=任务函数名, name='进程名字')
3. 启动进程对象
进程对象.start()
4. 其他的函数
import time
time.sleep(sec) # 让进程休眠sec秒
4.4 获取进程编号
-
获取进程对象
multiprocessing.current_process()
-
获取进程编号
import os os.getpid() # get process id # 进程对象的.pid multiprocessing.current_process().pid
-
获取父进程编号
import os os.getppid() # get parent process id
-
结束(杀死进程)
os.kill(进程id, 9)
4.5 进程执行带参数的任务
args
传参,实参是元祖类型(位置传参)kwargs
传参,实参是字典(关键字传参),字典的key
值是任务函数的形参
4.5 进程注意点
-
进程之间不共享全局变量
创建一个进程,就会把主进程的资源复制一份
-
默认情况下主进程会等待所有的子进程结束再结束
-
让子进程随着主进程结束而结束
-
方案一:主进程结束时终止子进程的执行
子进程对象.terminate()
-
方案二:子进程设置为
daemon
进程子进程对象.daemon = True
-
4.6 线程
介绍
在 Python
中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另一种方式
概念
- 线程是进程中执行代码的一个分支
- 线程是依附在进程中
- 线程是 CPU 调度的基本单位
线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要
cpu
进行调度,也就是说线程是cpu
调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程
作用
程序启动默认会有一个主线程,程序员自己创建的线程可以称为子线程,多线程可以完成多任务
4.7 多线程的使用
-
导入线程模块
import threading
-
创建线程对象
线程对象 = threading.Thread(target=任务函数名)
-
启动线程
线程对象.start()
4.8 线程传参
同进程传参
4.9 线程的注意点
-
线程之间执行是无序的
- 循环创建多个线程,start 之后,都处于就绪态,
cpu
调度哪个线程,哪个线程就会执行 - 进程的执行也是随机的,操作系统调度哪个进程,哪个进程执行
- 循环创建多个线程,start 之后,都处于就绪态,
-
主线程会等待所有的子线程执行结束再结束
-
线程之间共享全局变量
-
线程之间共享全局变量数据出现错误问题
import threading import time g_num = 0 def task(): # 修改全局变量的值,需要使用 global 声明 global g_num for i in range(1000000): g_num += 1 print(threading.current_thread().name, g_num) if __name__ == '__main__': thread_1 = threading.Thread(target=task) thread_2 = threading.Thread(target=task) thread_1.start() thread_2.start()
解决方法:
线程同步:保证同一时刻,只能有一个线程去操作全局变量。
同步:就是协同步调,按预定的先后次序进行运行。
如:你说完,我再说,好比现实生活中的对讲机
-
线程同步的方式:
-
线程等待(join)
-
互斥锁
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作
作用:可以保护共享数据,避免资源竞争
注意:
- 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
互斥锁的使用:
# 创建锁 mutex = threading.Lock() # 上锁 mutex.acquire() pass # 释放锁 mutex.release()
-
死锁
- 会造成应用程序的停止响应,不能再处理其它任务了
代码示例:
import threading g_list = [1, 2, 3, 4, 5] mutex = threading.Lock() def func(index): mutex.acquire() if index >= len(g_list): print(threading.current_thread().name, '下标越界') # 没有解锁,直接返回,剩余的线程都在这里等待,死锁 # mutex.release() # 解决办法:在这里释放锁,避免死锁 return print(threading.current_thread().name, g_list[index]) mutex.release() if __name__ == '__main__': for i in range(10): sub_thread = threading.Thread(target=func, args=(i,)) sub_thread.start()
-
-
4.10 进程和线程的对比
4.10.1 关系对比
- 线程是依附在进程里面的,没有进程就没有线程
- 一个进程默认提供一条线程,进程可以创建多个线程
4.10.2 区别对比
- 进程之间不共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或者线程同步
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依附在进程中
- 多进程开发比单进程多线程开发稳定性要强
4.10.3 优缺点对比
- 进程优缺点:
- 优点:可以用多核
- 缺点:资源开销大
- 线程优缺点:
- 优点:资源开销小
- 缺点:不能使用多核
4.11 使用场景
进程可以使用多核 CPU,cpython 的线程不能使用多个 CPU
- 进程:适合 计算密集型程序(大量的数学计算)
- 线程:适合 IO 密集型程序(读写操作,例如爬虫程序)
4.12 GIL
GIL 全局解释器锁(解释器层级的锁)。
GIL 保证同一时间,只有一个线程使用 CPU。
一个进程有一个 GIL 锁。
GIL 不是 Python 的特性,只是 CPython 解释器的概念。历史遗留问题。
GIL 锁什么时候释放?
- 在当前线程执行超时后会自动释放
- 在当前线程执行阻塞操作时会自动释放(input,io/输入输出)
- 当前执行完成时
GIL 的弊端
- GIL 对计算密集型的程序会产生影响。因为计算密集型的程序,需要占用系统资源
- GIL 的存在,相当于始终在进行单线程运算,这样自然就慢了
- IO 密集型影响不大的原因在于,IO ,input/output,这两个词就表明程序的瓶颈在于输入输出所耗费的时间,线程大部分时间在等待,所以他们是多个一起等(多线程)还是单个等(单线程)无所谓的
解决方案:
要提升多线程执行效率,解决方案:
- 更换解释器
- 使用多进程替换多线程
- 子线程使用C语言实现(绕过GIL锁)
必须要知道的是:
- CPU 密集型不太适合多线程
- I/O 密集型适合多线程(GIL锁会释放)爬虫程序
第五章 网络编程
端口号的分类
-
知名端口号
知名端口号是指众所周知的端口号,范围从0到1023。
- 这些端口号一般固定分配给一些服务,比如21端口分配给FTP(文件传输协议)服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务。
-
动态端口号
一般程序员开发应用程序使用端口号称为动态端口号,范围是从1024到65535
- 如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用
- 当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放
5.1 TCP 介绍
概念
TCP 的英文全拼 Transmission Control Protocol
简称传输控制协议,他是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP 的特点:
- 面向连接
- 通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源
- 可靠传输
- TCP 采用发送应答机制
- 超时重传
- 错误校验
- 流量控制和阻塞管理
5.2 socket 介绍
概念
socket(简称 套接字)是进程之间通信的一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要基于这个 socket。
作用
负责进程之间的网络数据传输,好比数据的搬运工
5.3 TCP 网络应用程序开发流程
程序架构:
B/S:brower/server(浏览器/服务器)
C/S:client/server(客户端/服务器),想要使用某个服务,必须下载他对应的客户端软件
5.4 TCP 客户端程序开发
Python 中要使用 socket,必须先导入 socket 模块
5.4.1 客户端开发流程
- 创建 socket 对象(
socket.socket(ip类型,协议)
) - 和服务器建立连接(
socket对象.connect((服务器ip地址,端口号)) 参数类型是元祖
) - 发送信息(
socket对象.send(发送的信息) 参数类型bytes
) - 接受对方发送的信息(
socket对象.recv(一次接收多少字节的数据)
) - 关闭连接(
socket对象.close()
)
5.4.2 网络调试助手的使用
5.4.3 python 中的字符串
python 中的字符串可以分为两种
-
str
-
bytes
字节,二进制,网络中数据的传输都是 bytes 类型
str --> bytes: str.encode(编码类型)
bytes --> str: bytes.decode(编码类型)
常用编码类型:gbk 和 utf-8,这两种编码类型,都是处理中文字符
gbk: 将一个中文字符编码为 2 个字节
utf-8:将一个中文字符编码为 3 个字节
5.4.4 代码实现
import socket
if __name__ == '__main__':
# 1. 创建 socket 对象(`socket.socket(ip 类型,协议)`)
# ipv4:socket.AF_INET;ipv6: socket.AF_INET6
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 固定写法
# 2. 和服务器建立连接(`socket对象.connect((服务器ip地址,端口号))`) 类型是元祖
client_socket.connect(('10.10.22.114', 8080))
print('客户端建立连接成功......')
# 3. 发送信息(`socket对象.send(发送的信息) 参数类型bytes`)
send_data = '你好服务器,我是客户端......'.encode('gbk')
client_socket.send(send_data)
# 4. 接受对方发送的信息(`socket对象.recv(一次接收多少字节的数据)`)
# 如果对方没有发送信息,recv 函数会在此阻塞等待
buf = client_socket.recv(4096)
try:
print(buf.decode('utf-8'))
except UnicodeDecodeError:
print(buf.decode('gbk'))
# 5. 关闭连接(`socket对象.close()`)
client_socket.close()
5.5 TCP 服务端程序开发
5.5.1 服务端开发流程分析
- 主动套接字:可以收发消息的套接字
- 被动套接字:不能收发消息
- socket():创建 socket 对象
- bind():绑定,固定服务器的
ip
和端口 - listen():设置监听,将主动套接字变为被动套接字,可以接受新的客户端连接
- accept():阻塞等待客户端的连接
- 一直阻塞到客户端连接到达(TCP三次握手):有客户端连接,会返回一个新的 socekt 套接字,用来和客户端进行通信
- recv():新的socket 接收信息
- send():新的socket 发送信息
- close():新的socket 关闭,表示不能通信;如果监听的socket 关闭,表示不能接收新的客户端连接
5.5.2 代码实现
import socket
if __name__ == '__main__':
# 1. 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 ip 和 port bind((ip, port) 元组类型)
server_socket.bind(('', 9000)) # 第一个参数为空,表示绑定服务器中任意的一个网卡
# 3. 设置监听,参数代表同时最大可以连接服务器的客户端数,连接成功后,就不再占用这个名额了
server_socket.listen(128)
# 4. 阻塞等待客户端的连接,返回一个元组(新的socket,(客户端的IP, 端口))
print('等待连接中......')
new_socket, ip_port = server_socket.accept()
print(f'客户端:{ip_port} 上线了......')
# 5. 新的 socket 收信息,阻塞等待
buf = new_socket.recv(4096)
try:
print('接收到的信息为:', buf.decode('utf-8'))
except UnicodeDecodeError:
print('接收到的信息为:', buf.decode('gbk'))
# 6. 新的 socket 发信息
send_data = '信息已收到'.encode('gbk')
new_socket.send(send_data)
# 7. 关闭
new_socket.close()
server_socket.close()
遇到的问题:
出现报错:
OSError:[Errno 98] Address already in use
正常情况下,服务器的代码是永远不会关闭的,就不会出现这问题。
特殊情况:启动服务器之后,运行一次,马上关闭再次运行,会出现这个bug,地址已经被使用
端口需要等待
30s ~ 2min
会彻底关闭,才可以再次使用解决方案:
1. 换个端口 1. 代码解决,代码实现,让程序关闭之后,可以立即使用这个端口(端口复用)
5.5.3 设置端口复用
import socket
if __name__ == '__main__':
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用
# setsockopt 设置端口权限
# 第一个参数:level 设置哪个级别的 socket socket.SOL_SOCKET 当前的socket
# 第二个参数:optname 设置什么内容(权限) socket.SO_REUSEADDR 端口复用
# 第三个参数:value 设置什么值,True
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server_socket.bind(('', 9000))
server_socket.listen(128)
print('等待连接中......')
new_socket, ip_port = server_socket.accept()
print(f'客户端:{ip_port} 上线了......')
buf = new_socket.recv(4096)
try:
print('接收到的信息为:', buf.decode('utf-8'))
except UnicodeDecodeError:
print('接收到的信息为:', buf.decode('gbk'))
send_data = '信息已收到'.encode('gbk')
new_socket.send(send_data)
new_socket.close()
server_socket.close()
5.5.4 判断客户端断开连接
import socket
if __name__ == '__main__':
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server_socket.bind(('', 9000))
server_socket.listen(128)
print('等待连接中......')
new_socket, ip_port = server_socket.accept()
print(f'客户端:{ip_port} 上线了......')
# 当对方的 socket close 之后,自己的 socket 不再阻塞了,recv 接收的内容是空字符串,长度为0
buf = new_socket.recv(4096)
if buf:
try:
print('接收到的信息为:', buf.decode('utf-8'))
except UnicodeDecodeError:
print('接收到的信息为:', buf.decode('gbk'))
send_data = '信息已收到'.encode('gbk')
new_socket.send(send_data)
else:
print(f'客户端 {ip_port} 已下线')
new_socket.close()
server_socket.close()
5.5.5 多任务版 TCP 服务端程序
import socket
import threading
def handle_client_request(n_socket, client_ip_port):
while True:
buf = n_socket.recv(4096)
if buf:
try:
print(f'接收到{client_ip_port}的信息为:', buf.decode('utf-8'))
except UnicodeDecodeError:
print(f'接收到{client_ip_port}的信息为:', buf.decode('gbk'))
send_data = '信息已收到'.encode('gbk')
n_socket.send(send_data)
else:
print(f'客户端 {client_ip_port} 已下线')
break
n_socket.close()
if __name__ == '__main__':
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server_socket.bind(('', 9000))
server_socket.listen(128)
print('等待连接中......')
# 循环等待客户端的连接
while True:
new_socket, ip_port = server_socket.accept()
print(f'客户端:{ip_port} 上线了......')
# 创建线程,执行任务
sub_thread = threading.Thread(target=handle_client_request, args=(new_socket, ip_port))
# 启动线程
sub_thread.start()
server_socket.close() # 关闭监听的 socket
5.6 协程
协程又称微线程,纤程,英文名 Coroutine
。
pip/pip3
是 python 的包管理工具,可以通过这个工具安装第三方模块和包如果你的电脑同时存在 python 和 python3,pip 是 python 2 的管理工具,pip3 是 python 3 的管理工具
-
gevent
gevent
是一个第三方库。Python 中仅提供了对协程的基本支持,但是不完全,而第三方的
gevent
为 Python 提供了比较完善的协程支持
5.6.1 gevent
的使用
# 1. 导入 gevent
import gevent
def sing(singer, song):
for i in range(5):
print(f'{singer}正在{song}||||||||||||')
# gevent 遇到 I/O 耗时阻塞操作,会切换协程
gevent.sleep(0.1) # 切换协程
def dance(dancer, dance):
for i in range(8):
print(f'{dancer}正在{dance}------------')
gevent.sleep(0.1)
if __name__ == '__main__':
# 2. 创建协程对象
g1 = gevent.spawn(sing, '张三', '唱歌')
g2 = gevent.spawn(dance, '李四', '跳舞')
# 3. 阻塞等待协程对象 join,gevent 遇到 I/O 耗时阻塞操作时才会切换
g1.join()
g2.join()
5.6.2 猴子补丁
from gevent import monkey
import gevent
import time
# 使用 gevent 中模块替换系统中的模块(例如:替换系统中的 time 模块)
monkey.patch_all()
def sing(singer, song):
for i in range(5):
print(f'{singer}正在{song}||||||||||||')
time.sleep(0.1)
def dance(dancer, dance):
for i in range(8):
print(f'{dancer}正在{dance}------------')
time.sleep(0.1)
if __name__ == '__main__':
g1 = gevent.spawn(sing, '张三', '唱歌')
g2 = gevent.spawn(dance, '李四', '跳舞')
g1.join()
g2.join()
第六章 HTTP 协议
6.1 HTTP 协议
-
TCP/IP
四层模型- 应用层:
HTTP
FTP
- 传输层:
TCP
UDP
- 网络层:
IP
协议 - 网络接口层(数据链路层)
- 应用层:
-
超文本传输协议
-
允许传输任意类型的数据
-
HTTP 协议:规定了浏览器和 Web 服务器通信数据的格式
-
传输数据的协议是 TCP 协议
-
浏览器和 Web 服务器之间的通信流程
- 在浏览器输入网址
- 通过
DNS
域名解析服务器,将网址转换为IP
地址 - 使用
IP
地址和http
协议默认的端口和服务器建立连接 - 浏览器向服务器发送符合
http
协议格式的数据 web
服务器解析请求- 通过解析的需求,将浏览器请求的内容,按照
HTTP
协议规定的格式,将数据返回给浏览器 - 浏览器收到数据之后,将页面显示
6.2 URL
URL(
Uniform Resoure Locator
),统一资源定位符 即 网络资源地址
URL 的组成:
- 协议部分:
https://
、http://
、ftp://
- 域名部分(可以直接写
IP
)(域名: 端口):www.baidu.com
- 资源路径部分:
/
、/s
- 查询参数(可选):URL 中
?
开始的内容
6.3 查看网络通信过程
6.4 HTTP 请求报文 Request Headers
报文格式:
- 请求行
- 请求头
- 请求空行
- 请求体
-
GET 方式的请求报文
--- 请求行 --- # 请求的方式 URL的资源路径 HTTP的版本 GET / HTTP/1.1 --- 请求头(key: value \r\n) --- --- 空行(\r\n) # 请求的主机/域名 Host: www.baidu.com # 保持连接 Connection: keep-alive Pragma: no-cache # 不缓存 Cache-Control: no-cache # 1:允许升级不安全的请求(http --> https) Upgrade-Insecure-Requests: 1 # 用户代理:浏览器的唯一标识,不同的浏览器、版本都是不一样的 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 # 可以接受的内容;q 是权重,值越大权重越高 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 # 压缩格式 Accept-Encoding: gzip, deflate, br # 接收的语言 Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: ...... DNT: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows"
-
POST 方式的请求报文
请求行(POST 资源路径 HTTP/1.1\r\n) 请求头(key: value\r\n) 空行(\r\n) # 数据的长度 Content-Length: 417 请求体 # 和 GET 相比多了请求体 Request Payload
6.5 HTTP 响应报文 Response Headers
报文格式:
- 响应行
- 响应头
- 响应空行
- 响应体
-
GET 的响应报文
# 响应行(协议/版本 响应状态码 响应状态码描述) HTTP/1.1 200 OK # 响应头(key:value\r\n) Bdpagetype: 1 Bdqid: 0xa80d091a00003995 Connection: keep-alive # 压缩格式 Content-Encoding: gzip # 类型和编码 Content-Type: text/html; charset=utf-8 # 响应时间 Date: Thu, 29 Dec 2022 10:07:11 GMT # 过期时间 Expires: Mon, 07 Sep 2022 01:49:45 GMT # 服务器 Server: BWS/1.1 Set-Cookie: BDSVRTM=0; path=/ Set-Cookie: BD_HOME=1; path=/ Set-Cookie: H_PS_PSSID=36542_37647_37906_37623_36920_37797_37920_37993_37902_26350_37959_37881; path=/; domain=.baidu.com Strict-Transport-Security: max-age=172800 Traceid: 1672308431023805594612109344980342946197 X-Frame-Options: sameorigin X-Ua-Compatible: IE=Edge,chrome=1 # 文件大小不固定 Transfer-Encoding: chunked
6.6 静态 web 服务器 - 返回固定页面
响应报文,使用
tcp
协议传输,书写tcp
服务端程序,返回响应报文。
- 创建
socket
对象- 绑定
IP
和端口bind
- 设置监听
- 阻塞等待客户端连接
- 接收信息(请求报文)
- 发送信息(响应报文)
- 关闭连接
代码实现:
import socket
def handle_client_request(n_socket, client_ip_port):
# 5. 接收信息(请求报文)
buf = n_socket.recv(4096) # buf 是请求报文
if buf:
print(buf.decode('gbk'))
# 6. 发送信息(响应报文)
# 6.1 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 6.2 响应头
response_header = 'Server:PY\r\nName:Py42\r\n'
# 6.3 空行 \r\n
# 6.4 响应体(具体的页面数据),返回 index.html 的页面
f = open('static/index1.html', 'rb') # 读取到的是 bytes 类型
data = f.read()
f.close()
# 6.5 合成响应体
response = (response_line + response_header + '\r\n').encode('gbk') + data
n_socket.send(response)
else:
print(f'{client_ip_port} 下线了...')
# 7. 关闭连接
n_socket.close()
if __name__ == '__main__':
# 1. 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 2. 绑定`IP`和端口 `bind`
server_socket.bind(("", 8989))
# 3. 设置监听
server_socket.listen(128)
# 4. 阻塞等待客户端连接
while True:
new_socket, ip_port = server_socket.accept()
print(f'{ip_port} 已连接...')
handle_client_request(new_socket, ip_port)
6.7 静态 web 服务器 - 返回指定页面
通过请求行里的 URL 资源路径,判断返回哪一个页面
import socket
def handle_client_request(n_socket, client_ip_port):
# 5. 接收信息(请求报文)
buf = n_socket.recv(4096) # buf 是请求报文
if buf:
file_name = buf.decode('gbk').split(' ', 2)[1]
# 6. 发送信息(响应报文)
# 6.1 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 6.2 响应头
response_header = 'Server:PY\r\nName:Py42\r\n'
# 6.3 空行 \r\n
# 6.4 响应体(具体的页面数据),返回 index.html 的页面
f = open('static' + (file_name, '/index1.html')[file_name == '/'], 'rb') # 读取到的是 bytes 类型
data = f.read()
f.close()
# 6.5 合成响应体
response = (response_line + response_header + '\r\n').encode('gbk') + data
n_socket.send(response)
else:
print(f'{client_ip_port} 下线了...')
# 7. 关闭连接
n_socket.close()
if __name__ == '__main__':
# 1. 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 2. 绑定`IP`和端口 `bind`
server_socket.bind(("", 8989))
# 3. 设置监听
server_socket.listen(128)
# 4. 阻塞等待客户端连接
while True:
new_socket, ip_port = server_socket.accept()
print(f'{ip_port} 已连接...')
handle_client_request(new_socket, ip_port)
6.8 静态 web 服务器 - 返回不存在的页面
try:
f = open('static' + file_name, 'rb') # 读取到的是 bytes 类型
except FileNotFoundError:
f = open('static/404.html', 'rb')
response_line = 'HTTP/1.1 404 NOT FOUND\r\n'
6.9 静态 web 服务器 - 多任务版
6.10 静态 web 服务器 - 面向对象版
6.11 静态 web 服务器 - 命令行启动版
获取命令行参数
import sys
print(sys.argv)
第七章 MySQL
7.1 介绍
-
数据库:存储和管理数据的仓库
-
分类:
-
关系型数据库管理系统(软件)存在关系模型
数据行 数据列 数据表 数据库
Oracle
mysql
SOL server
sqlite
-
非关系型数据库(软件) key-vlaue 形式
Redis
MongoDB
-
-
特点:
- 持久化存储数据
- 读写速度极高(比文件读取高许多)
- 保证数据的有效性(数据类型 约束)
-
查看
mysql
进程ps -aux | grep Mysql
-
查看 MySQL服务状态
sudo service mysql status
-
停止 MySQL 服务
sudo service mysql stop
-
重启 MySQL 服务
sudo service mysql restart
-
MySQL 配置文件路径
/etc/mysql/mysql.conf.d/mysqld.cnf
- port 表示端口号,默认3306
- bind-address 表示服务器绑定的 ip,默认127.0.0.1
- datadir 表示数据库保存路径,默认
/var/lib/mysql
- log_error 表示错误日志路径,默认
/var/log/mysql/error.log
7.2 SQL 数据类型
-
常用数据类型:
-
整数:
int
、bit
(位) 、tinyint
(小整数) -
小数:
decimal
- decimal 表示浮点数,如 decimal(5, 2) 表示小数长度总共为5位,其中小数占2位。
-
字符串:
varchar(最大长度)
(可变长度字符串)char(固定长度)
text
- char 表示固定长度的字符串,如 char(3),如果填充 'ab' 时会补一个空格为 'ab ',3表示字符数
varchar
表示可变长度的字符串,如varchar(3)
,填充 'ab' 时就会存储 'ab',3 表示最长可存储的字符数- text 表示存储大文本,当字符大于4000时推荐使用,比如技术博客
-
日期时间:
date
time
datetime
-
枚举类型:
enum(xx, xxx, ...)
-
-
数据约束
- 主键
primary key
:物理上存储的顺序,MySQL
建议所有表的主键字段都叫 id,类型为 int unsigned - 非空
not null
:此字段不允许填写空置 - 唯一
unique
:此字段的值不允许重复 - 默认
default
:当不填写字段对应的值会使用默认值 - 外键
foreign key
:对关系字段进行约束,当为关系字段填写值时,会到关联的表中查询此值是否存在,如果存在则填写成功,如果不存在则填写失败并抛出异常
- 主键
7.3 SQL
语句
7.3.1 数据库语句
-
select now()
查询系统当前时间 -
show databases
查看所有数据库 -
create database 数据库名 charset=utf8
创建数据库 -
use 数据库名
使用数据库 -
select database()
查看当前使用的数据库 -
drop database 数据库名
删除数据库 -
show create database 数据库名
查看创建数据库的语句 -
show engines;
查看MySQL
数据库支持的表的存储引擎-
常用的表的存储引擎是
InnoDB
和MyISAM
-
InnoDB
是支持事务的 -
MyISAM
不支持事务,优势是访问速度快,对事务没有要求或者以select
、insert
为主的都可以使用该存储引擎来创建表 -
修改引擎:
alter table stu engine = 'MyISAM'
-
7.3.2 数据表语句
-
show tables
查看当前数据库中所有的表 -
create table 表名(字段 数据类型 数据约束,字段 数据类型 数据约束...)
在数据库中创建表创建学生表:
create table stu(id int unsigned primary key not null auto_increment, name varchar(20) not null, age tinyint default 0, height decimal(5,2), gender enum('男','女','保密'));
-
id 主键:
id
int | unsigned
primary key not null auto_increment
-
姓名:
name
varchar(20)
not null
-
年龄:
age
tinyint
default 0
-
身高:
height
decimal(5,2)
-
性别:
gender
`enum('男', '女', '保密')
-
-
show create table 表名
查看创建表的语句 -
desc 表名
查看表结构 -
alter table 表名 add 字段名 类型 约束?
添加一个字段,约束可以不写 -
alter table 表名 change 原字段名 新字段名 类型 约束?
修改字段名和类型 -
alter table 表名 modify 字段名 类型 约束
修改字段的类型和约束 -
alter table 表名 drop 字段
删除字段 -
drop table 表名
删除表
7.3.3 插入数据
insert into 表名 values()
全列插入insert into 表名 (字段, 字段, ...) values (数据, 数据, ...)
插入指定列数据insert into 表名 (字段, 字段, ...) values (...), (...), (...), ...
插入多行数据
7.3.4 修改数据
update 表名 set 字段=数据, 字段=数据
修改所有数据行update 表名 set 字段=数据, 字段=数据 where 字段=数据
带查询条件的修改数据
7.3.5 删除数据
-
物理删除
delete from 表名 where id=3
-
逻辑删除
alter table stu add is_delete bit default 0
select * from stu where is_delete=1
7.3.6 查询数据
-
select * from 表名
查询所有数据 -
select 字段,字段,... from 表名
查询指定列的数据 -
as
关键字起别名,as 可以省略select name as 姓名, age 年龄 from stu
-
distinct
关键字去重select distinct age from stu
对 age 字段去重select distinct age, height from stu
对 age 和 height 字段去重
7.3.7 where 条件查询
7.3.7.1 语法结构
select xx,xx
from 表名
where 条件;
7.3.7.2 支持的运算符
7.3.7.2.1 比较运算符
不等于:!=
或 <>
7.3.7.2.2 逻辑运算符
不在:not
7.3.7.2.3 模糊查询
使用 like
关键字,%
代表任意多个字符,_
任意一个字符
select * from 表名 where 字段 like ''
7.3.7.2.4 范围查询
between A and B
连续的范围select * from stu where not (id between 3 and 7);
select * from stu where id not between 3 and 7;
in (a, b, c)
在里面的内容
7.3.7.2.5 空判断
is null
、is not null
7.3.8 排序 order by
`asc` 升序 、 `desc` 降序
-
查询未删除女生信息,按学号降序
select * from stu where (gender='女' and is_delete=0) order by id desc;
-
显示所有的学生信息,先按照年龄从大到小排序,当年龄相同时,按照身高从高到矮排序
select * from stu order by age desc, height desc;
7.3.9 分页limit
`select * from 表名 limit i,n`
从第`i`条开始,查询`n`条数据
`select * from stu limit (n-1)*m, m;`
每页显示`m`条,查询第`n`页数据
-
查询前3行女生
select * from stu where gender='女' limit 3;
7.3.10 聚合函数
聚合函数又叫组函数,通常是对表中的数据进行统计和计算,一般结合分组(group by)来使用,用于统计和计算分组数据
- 常用的聚合函数
count(col)
:表示求指定列的总行数max(col)
:表示求指定列的最大值min(col)
:表示求指定列的最小值sum(col)
:表示求指定列的和avg(col)
:表示求指定列的平局值round(数字,保留几位小数)
ifnull()
:函数,处理空数据,即遇到null
该如何处理group_concat()
with rollup
:很少用
eg:
select sum(age) from stu;
# round(数字, 保留几位小数)
select round(avg(age), 2) from stu;
# 如果遇到height 为空,返回保密
select name, ifnull(height, '保密') from stu;
7.3.10 分组函数 group by
# 基本格式
select 字段, ...
from 表名
where 条件
group by 字段
having 条件
order by 字段
limit i,n;
# 1. having 是对分组之后的数据进行筛选
# 2. 如果使用了 group by, select 后面不能使用 *,
# 只能使用 聚合函数 和 group 后出现的字段。
-
显示不同性别的分组
select gender from stu group by gender;
-
显示不同性别的分组中,分别有多少人
count()
select gender, count(*) from stu group by gender;
-
显示不同性别的分组中,每组都有哪些人
group_concat()
select gender, group_concat(name) from stu group by gender;
-
显示不同性别的分组中,统计分组条数大于2的
select gender, count(*) from stu group by gender having count(*) > 2;
-
统计汇总聚合函数的信息(很少用
with rollup
)select gender, count(*) from stu group by gender with rollup;
-
对年龄分组之后进行升序排序
select age from stu group by age order by age asc;
7.3.11 连接查询
连接查询可以对多张表进行查询
# 基本格式
select 字段, ...
from 表1 [inner|left|right] join 表2
on 连接条件 # 连接条件:表1、表2之间数据相同的部分
where 条件
group by 字段
having 条件
order by 字段
limit i,n;
7.3.11.1 内连接
查询两个表中符合条件的共同内容,即查交集
inner join
可以省略 inner
简写为 join
# 语法
select * from stu inner join class on stu.cls_id = class.id;
# 对表起别名
select s.name, c.name from stu as s inner join class as c on s.cls_id = c.id;
7.3.11.2 左连接
在内连接查询结果的基础上,还会查出 左
表的数据
select * from stu left join class on stu.cls_id = class.id;
7.3.11.3 右连接
在内连接查询结果的基础上,还会查出 右
表的数据
select * from stu right join class on stu.cls_id = class.id;
7.3.11.4 补充
多表查询,不使用 join on
连接
select 字段, ...
from 表1, 表2
where 表1.xxx = 表2.xxx and 条件 # 在这里添加条件查询,等价于内连接查询
group by 字段
having 条件
order by 字段
limit i,n;
# eg:
select * from stu as s, class as c where s.cls_id = c.id;
7.3.11.5 自连接
左表和右表是同一张表,根据连接查询条件查询同一张表中的数据。
例如,省份地区表中,既包含了省份信息,又包含了省下的市区信息。
select * from areas as a join areas as p on a.pid = p.id;
select * from areas as a join areas as p on a.pid = p.id where p.title = '陕西省';
7.3.11.6 子查询
在一个 select
语句中嵌入了另外一个 select
语句,那么被嵌入的 select
语句称之为子查询语句,外部那个 select
语句则称为主查询。
主查询和子查询的关系:
- 子查询是嵌入到主查询中
- 子查询是辅助主查询的,要么充当条件,要么充当数据源
- 子查询是可以独立存在的语句,是一条完整的
select
语句
eg:
-
查询大于平均年龄的学生:
select * from stu where age > (select avg(age) from stu);
-
查询学生在班的所有班级名字:
select name from class where id in (select cls_id from stu);
-
查询年龄最大,身高最高的学生:
select * from stu where (age, height) = (select max(age), max(height) from stu);
-
查询年龄最大 或 身高最高的学生:
select * from stu where age=(select max(age) from stu) or height = (select max(height) from stu);
7.3.12 外键约束
7.3.12.1 对已经存在的字段添加外键约束
# 语法
alter table 表名 add foreign key(字段) references 另一张表(字段);
# eg:
alter table stu add foreign key(cls_id) references class(id);
-- 注意点
1. 给现有字段添加外键约束之前,需要保证该字段目前的值都满足外键约束才可以
2. 该字段的数据类型要和引用字段的数据类型保持一致
7.3.12.2 在创建数据表时设置外键约束
# 创建学校表
create table school(
id int not null primary key auto_increment,
name varchar(10)
);
# 创建老师表
create table teacher(
id int not null primary key auto_increment,
name varchar(10),
s_id int not null,
foreign key(s_id) references school(id)
);
7.3.12.3 删除外键约束
# 查看外键名字
show create table 表名;
# 删除外键
alter table 表名 drop foreign key 外键名;
7.3.13 将查询结果插入到其它表中
insert into ... select ... from xxx
***Note:*这里查询出来的列数要和要插入的列数相同,例如:
insert into goods_cates(name) select distinct cate_name from goods
7.3.14 使用连接更新表中某个字段数据
update ... join ... on ... set 字段2=字段2
例如:
update goods as g join goods_cates as gc on g.cate_name = gc.name set g.cate_name = gc.id
7.3.15 创建表的同时给某个字段添加数据
create table ... select ...
***Note:*使用 create select 语句时,select 查询出来的字段的名字需要和 create 创建的名字对应,如果不一致,使用 as 对查询的结果起别名,例如:
create table goods_brands(id int unsigned primary key not null auto_increment, name varchar(40) not null) select brand_name as name from goods group by brand_name;
7.3.16 索引
索引在MySQL
中也叫做“键”,它是一个特殊的文件,它保存着数据表里所有记录的位置信息,更通俗的来说,数据 库索引好比是一本书前面的目录,能加快数据库的查询速度。
7.3.16.1
所有主键列默认会自动创建索引。
-
查看表中已有索引:
show index from 表名
-
创建索引(索引名不指定默认使用字段名):
alter table 表名 add index [索引名] (列名, ...)
alter table classes add index my_name (name)
-
删除索引
alter table 表名 drop index 索引名
7.3.16.2 联合索引
联合索引又叫复合索引,即一个索引覆盖表中两个或者多个字段,一般用在多个字段一起查询的时候。
alter table 表名 add index (name, age)
联合索引的好处:
-
减少磁盘空间开销,因为每创建一个索引,其实就是创建了一个索引文件,那么会增加磁盘空间的开销
联合索引的最左原则
- 在使用联合索引的时候,我们要遵守一个最左原则,即
index(name, age)
支持name
、name
和age
组合查询,而不支持单独age
查询,因为没有用到创建的联合索引。 - 查询数据时一定要保证联合索引的最左侧字段出现在查询条件里,否则联合索引失效。
7.3.16.3 MySQL 中索引的优点、缺点和使用原则
-
优点:
加快数据的查询速度
-
缺点:
创建索引会耗费时间和占用磁盘空间,并且随着数据量的增加所耗费的时间也会增加
-
使用原则:
-
通过优缺点对比,不是索引越多越好,而是需要自己合理的使用;
-
对经常更新的表就避免对其进行过多索引的创建,对经常用于查询的字段应该创建索引;
-
数据量小的表最好不要使用索引,因为由于数据较少,可能查询全部数据花费的时间比遍历索引的时间还要短,索引就可能不会产生优化效果;
-
在一个字段上相同值比较多就不要建立索引,比如在学生表的”性别“字段上只有男、女两个不同值。相反的,在一个字段上不同值较多可以建立索引。
-
7.4 数据库设计之三范式
-
第一范式(1NF)
强调的是列的原子性,即列不能够再分成其他几列
-
第二范式(2NF)
满足1NF,另外包含两部分内容
- 表必须有一个主键
- 非主键字段必须完全依赖于主键,而不能只依赖于主键的一部分
-
第三范式(3NF)
满足2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。
即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
7.5 E-R模型的介绍
E-R 模型即 实体—关系
模型,E-R 模型就是描述数据库存储数据的结构模型。
E-R 模型的使用场景:
- 对于大型公司开发项目,我们需要根据产品经理的设计,先使用建模工具,如:
power designer
,db desinger
等这些软件来画出实体-关系模型(E-R 模型) - 然后根据三范式设计数据库表结构
7.6 事务(ACID 特性)
-
原子性(Atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事物的原子性。
-
一致性(Consistency)
数据库总是从一个一致性的状态转换到另一个一致性的状态。
-
隔离性(Isolation)
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。
-
持久性(Durability)
一旦事务提交,则其所做的修改会永久保存到数据库。
开启事务
```mysql
# 手动开启事务
begin;
# 或者
start transaction;
# 一旦手动开启事务,就不会自动提交,需要手动提交(commit),或者是回滚(rollback)
```
- 开启事务后执行修改命令,变更数据会保存到
MySQL
服务端的缓存文件中,而不维护到物理表中 MySQL
数据库默认采用自动提交(autocommit
)模式,如果没有显示的开启一个事务,那么每条sql
语句都会被当做一个事务执行提交的操作- 当设置
autocommit=0
就是取消了自动提交事务模式,直到显示的执行commit
和rollback
表示该事务结束
7.7 python 中使用 mysql
7.7.1 查询
# 1. 导包
import pymysql
if __name__ == '__main__':
# 2. 创建连接对象
conn = pymysql.connect(
host='localhost',
user='root',
password='root',
database='test',
port=3306,
charset='utf8')
# 3. 创建游标对象
cursor = conn.cursor()
# 4. 执行 SQL 语句
# 4.1 书写 SQL
sql = 'select * from stu;'
# 4.2 执行 SQL
# 返回值是影响的行数
# 对于查询操作来说,查询出来的数据保存在游标对象中
row = cursor.execute(sql)
print(f'{row} rows in set')
# 获取查询结果,对于游标中保存的数据,只能获取一次,获取之后就不能再次获取了
# print(cursor.fetchone()) # 获取一条查询结果
print(cursor.fetchall()) # 获取所有的数据
# cursor.fetchmany()
# 5. 关闭游标
cursor.close()
# 6. 关闭连接对象
conn.close()
7.7.2 增删改
需要判断sql
执行是否成功,成功提交事务,失败回滚事务
try:
# 插入
sql = 'insert into stu(name) values("zpan");'
row = cursor.execute(sql)
print(row)
# 提交事务
conn.commit()
except Exception as e:
print(e)
# 回滚
conn.rollback()
第八章 其他
8.1 装饰器
-
定义
给已有函数增加额功能的函数,它本质上就是一个闭包函数。
-
功能接接特点
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
-
本质是一个闭包函数,只不过比较特殊
- 定义外层函数,要求参数只有一个,类型是函数类型,调用时传参传递的是原函数
- 定义内层函数,在内层函数中,书写新的功能,并在合适的时机调用原函数
- 返回内部函数的地址
-
eg:
def login_check(fn): # 1 def inner(): # 4 print('登录验证...') # 8 fn() # 9 return inner # 5 # @login_check <==> comment = login_check(comment) def comment(): # 2 print('进行评论...') # 10 comment = login_check(comment) # 3 if __name__ == '__main__': # 6 comment() # 7
-
通用装饰器
def decorator(fn): def inner(*args, **kwargs): # code result = fn(*args, **kwargs) # code return result return inner
-
带参数的装饰器
def make_tag(tag): def decorator(fn): def inner(*args, **kwargs): result = fn(*args, **kwargs) return "<" + tag + ">" + result + "</" + tag + ">" return inner return decorator
-
类装饰器
class LoginCheck(object): def __init__(self, fn): self.__fn = fn # code def __call__(self, *args, **kwargs): # code res = self.__fn(*args, **kwargs) # code return res
8.2 property 属性
property
属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用
-
类属性方式实现
class Dog(object): def __init__(self, name, age): self.__name = name self.__age = age def get_name(self): return self.__name def set_name(self, name): self.__name = name # 通过类属性的方式,给对象添加 property 属性 name = property(get_name, set_name)
-
装饰器的方式实现
class Dog(object): def __init__(self, name, age): self.__name = name self.__age = age # @property 装饰器,装饰的方法是 获取属性的方法 # 还会创建一个新的装饰器,@age.setter 设置属性使用的 @property def age(self): return self.__age @age.setter def age(self, new_age): self.__age = new_age
8.3 with 语句
Python 提供了 with
语句(不需要书写关闭代码,会自动进行关闭)的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作。
with 上下文管理器对象 as 变量:
pass
eg:
with open('1.txt', 'w') as f:
f.write('hello world')
8.4 上下文管理器
一个类只要实现了 __enter__
() 和 __exit__()
这两个方法,通过该类创建的对象我们就称之为上下文管理器。
上下文管理器可以使用 with 语句
,with 语句之所以这么强大,背后是由上下文管理器做支撑的。
8.5 生成器
根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据 不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存。
创建生成器的方式:
-
生成器推导式
my_gen = (i for i in range(2))
-
yield
关键字实现生成器只要在
def
函数里面看到有yield
关键字那么就是生成器。yield 关键字作用:
- 类似 return,会将后面跟的内容进行返回;
- 函数代码遇到 yield,函数会暂停执行,等待下一次 next() 再继续执行
8.6 深拷贝和浅拷贝
import copy
浅拷贝
copy.copy()
只会对可变类型的第一层对象进行拷贝并开辟内存空间,不会对内部子对象开辟空间
- 不可变类型
int
float
str
tuple
:浅拷贝不会开辟空间,拷贝的是引用 - 可变类型
list
dist
set
深拷贝
copy.deepcopy()
深拷贝进行拷贝时,只要发现可变类型,就会对可变类型本身以及父对象进行拷贝
- 不可变类型:拷贝引用,不会开辟新空间
- 元组:
- 如果元祖中包含可变类型,会对元祖本身以及子对象都开辟内存空间
- 可变类型:可变类型本身以及父对象进行拷贝
8.7 正则表达式
import re
res = re.match() # 从头部开始匹配,匹配成功返回对象,匹配失败返回 None
res = re.search() # 从任意位置匹配
res.group() # 查看匹配的结果
res.group(n) # 查看第n个分组
代码 | 功能 | |
---|---|---|
. | 匹配任意1个字符,除了 \n | |
[] | 匹配[ ]中列举的字符 | |
\d | 匹配数字0 - 9 | |
\D | 匹配非数字 | |
\s | 匹配空白,即空格、tab键 | |
\S | 匹配非空白 | |
\w | 匹配非特殊字符,即a-z 、A-Z 、0-9 、_ 、汉字 | |
\W | 匹配特殊字符,和 \w 相反 | |
* | 匹配前一个字符至少 0 次 | |
+ | 匹配前一个字符至少 1 次 | |
? | 匹配前一个字符 0 次 或 1 次 | |
{m} | 匹配前一个字符 m 次 | |
{m,n} | 匹配前一个字符至少 m 次,至多 n 次 | |
^ | 匹配字符串开头 | |
$ | 匹配字符串结尾 | |
[^指定字符] | 当 ^ 写在 [] 里表示除了指定字符外都会匹配 | |
` | ` | 匹配左右任意一个表达式 |
(ab) | 将括号中字符作为一个分组 | |
\num | 引用分组 num 匹配到的字符串,例如:<([a-zA-Z]+)>.*</\\1> | |
(?P<name>) | 分组起别名,例如:<(?P<tag>[a-zA-Z]+)>.*</(?P=tag)> | |
(?P=name) | 引用别名为name 的分组匹配到的字符串 |
8.8 日志模块
日志等级说明:
- DEBUG:程序调试bug时使用
- INFO:程序正常运行时使用
- WARNING:程序未按照预期运行时使用,但并不是错误,如:用户登录密码错误
- ERROR:程序出错误时使用,如:IO操作失败
- CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
- 默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息。
- 日志等级从低到高的顺序是:DEBUG < INFO < WARNING < ERROR < CRITICAL
使用:
import logging
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
# 输入日志如下,debug 和 info 不输出
# WARNING:root:warning
# ERROR:root:error
# CRITICAL:root:critical
import logging
# level 表示设置的日志等级
# format 表示日志的输出格式,参数说明:
# %(levelname)s:打印日志级别名称
# %(filename)s:打印当前执行程序名
# %(lineno)d:打印日志的当前行号
# %(asctime)s:打印日志的时间
# %(message)s:打印日志信息
# filename 表示日志保存路径
# filemode
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename='log.txt',
filemode='w')
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
# 输入日志如下
# 2023-03-27 19:04:31,322 - 098 日志模块.py[line:6] - DEBUG: debug
# 2023-03-27 19:04:31,323 - 098 日志模块.py[line:7] - INFO: info
# 2023-03-27 19:04:31,323 - 098 日志模块.py[line:8] - WARNING: warning
# 2023-03-27 19:04:31,323 - 098 日志模块.py[line:9] - ERROR: error
# 2023-03-27 19:04:31,323 - 098 日志模块.py[line:10] - CRITICAL: critical
第九章 Redis
9.1 介绍
Redis
是一个C语言写的,数据保存在内存中,重启后可以从磁盘重新拿取再放到内存中的高性能非关系型数据库。
Redis
配置文件地址:/etc/redis/redis.conf
-
默认端口号:
6379
-
默认数据库数量:
16个
-
通过
bind
绑定ip
9.2 Redis
安装
Windows 下安装
9.3 客户端操作命令
9.3.1 对 key 的操作
EXPIRE key seconds
exists key [key ...]
type key
del key [key ...]
KEYS pattern
TTL key
9.3.2 Strings/Blobs/Bitmaps
SET key value
GET key
SETEX key seconds value
MSET key value [key value ...]
9.3.4 Hash Tables
hset key field value
hget key field
hmset key field value [field value ...]
hmget key field [field ...]
hkeys key
hvals key
hdel key field [field ...]
9.3.4 Linked Lists
lpush key value [value ...]
rpush key value [value ...]
lrange key start stop
lrem key count value
9.3.5 Sets
- 无序集合
- 元素为
string
类型 - 元素具有唯一性,不重复
- 说明:对于集合没有修改操作(有删除)
sadd key member [member ...]
smembers key
srem key member [member ...]
9.3.6 Sorted Sets
- 有序集合
- 元素为
string
类型 - 元素具有唯一性,不重复
- 每个元素都会关联一个
double
类型的score
,表示权重,通过权重将元素从小到大排序 - 说明:没有修改操作
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
zrange key start stop [WITHSCORES]
zrem key member [member ...]
9.3.7 在 Python 中操作 Redis
from redis import Redis
redis_cli = Redis(host="localhost", port=6379, db=0)
redis_cli.set('name', 'it')
name = redis_cli.get('name')
print(name)
redis_cli.delete('name')
name = redis_cli.get('name')
print(name)
第十章 Django
10.1 虚拟环境搭建
10.1.1 安装虚拟环境
windows 下安装
- 安装
virtualenvwrapper-win
pip3 install virtualenvwrapper-win
-
重新打开
cmd
或powerShell
,创建虚拟环境mkvirtualenv -p python3 虚拟环境名称
10.1.2 虚拟环境的操作命令
-
查看所有虚拟环境
workon
-
切换/进入虚拟环境
workon NAME
-
删除虚拟环境
rmvirtualenv NAME
-
退出虚拟环境
deactivate
10.1.3 虚拟环境依赖包安装
-
安装
Django
pip install django==2.2.5
-
查看环境内已安装包
pip list
10.2 创建 Django 项目
10.2.1 创建项目
django-admin startproject name
# 目录结构
├─src
│ ├─settings.py 项目整体的配置文件
│ ├─urls.py 项目的URL配置文件
│ ├─wsgi.py 项目与WSGI兼容的Web服务器入口
│ └─__init__.py
└─manage.py 项目管理文件,通过它管理项目
# 运行项目
python manage.py runserver
10.2.2 创建子应用
python manager.py startapp name
10.2.3 注册子应用
在 settings.py
里注册
10.3 模型
10.3.1 使用 Django 进行数据库开发的步骤
1. 定义模型
class BookInfo(models.Model):
name = models.CharField(max_length=10)
class PeopleInfo(models.Model):
name = models.CharField(max_length=10)
gender = models.BooleanField()
# 外键约束:人物属于哪本书
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)
2. 模型迁移(建表)
迁移由两步完成
生成迁移文件:根据模型类生成创建表的语句(需要在settings.py
里先注册子应用)
python manage.py makemigrations
执行迁移文件:根据第一步生成的语句在数据库中创建表
python manage.py migrate
3. 操作数据库
10.4 站点管理
10.4.1 管理界面本地化
-
启动项目下的
manage.py
文件 -
访问
http://127.0.0.1:8000/admin
进入登录页面界面默认是英文,需要改为中文,需要在
/项目名称/settings.py
里进行修改# settings.py # 设置语言 # LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'zh-Hans' # 设置时区 # TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Shanghai'
10.4.2 创建管理员
-
运行
python manage.py createsuperuser
创建超级用户的 账号、密码和邮箱这样就会在表
db.sqlite3/Schemas/main/Tables/auth_user
生成一条数据重置密码使用
python manager.py changepassword 用户名
-
在登录页面用刚才设置的账号密码进行登录
10.4.3 注册模型类
# /src/book/admin.py
from django.contrib import admin
from book.models import BookInfo, PeopleInfo
# 注册模型类
admin.site.register(BookInfo)
admin.site.register(PeopleInfo)
# 重启Django
10.4.4 发布内容到数据库
10.5 视图和路由
10.5.1 视图
# /src/book/views.py
from django.shortcuts import render
from django.http import HttpRequest
from django.http import HttpResponse
# Create your views here.
"""
视图
所谓的视图 其实就是python函数
视图函数有2个要求:
1. 视图函数的第一个参数就是接收请求。这个请求其实就是 HttpRequest 的类对象
2. 必须返回一个响应
"""
def index():
return HttpResponse('ok')
10.5.2 路由
在子应用创建urls.py
# /src/book/urls.py
from django.urls import path
from book.views import index
# urlpatterns 是固定写法
urlpatterns = [
# path('路由','视图函数名')
path('index/', index)
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('book/', include('book.urls'))
]
10.6 模板 Template
在/src/settings.py
文件下的 TEMPLATE
变量里的 DIRS
属性里设置模板路径,例如:
TEMPLATES = [{
...,
# 告知系统,我们的模板文件放在哪里
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
}]
将 /src/book/views.py
里的 def index
改为:
def index(request):
# return HttpResponse('ok')
context = {
'name': '马上双11'
}
return render(request, 'book/index.html', context=context)
10.7 配置文件 /src/settings.py
-
BASE_DIR
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # print(__file__) # C:\Users\Administrator\Envs\vir1\src\src\settings.py # print(os.path.abspath(__file__)) # C:\Users\Administrator\Envs\vir1\src\src\settings.py # os.path.dirname() 获取文件的目录 # print(os.path.dirname(os.path.abspath(__file__))) # C:\Users\Administrator\Envs\vir1\src\src
-
DEBUG = True
调试模式、ALLOWED_HOSTS
在我们开发的时候,我们需要看到更多的信息,所以要开启debug模式,当我们的程序上线之后,就改为False,此时,必须在
ALLOWED_HOSTS
里设置可访问的ip
地址 -
语言与时区
# 设置语言 # LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'zh-Hans' # 设置时区 # TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Shanghai'
10.8 静态文件
为了提供静态文件,需要在/src/settings.py
配置两个参数:
# 存放查找静态文件的目录
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# 访问静态文件的URL前缀
STATIC_URL = '/static/'
在浏览器访问:http://127.0.0.1:8000/static/c.png
10.9 App 应用配置
10.10 更换数据库
10.10. 1 在 MySQL 中创建数据库
create database book charset utf8;
use book;
10.10.2 修改 DATABASES 配置信息
修改 settings.py
中的 DATABASES
:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1', # 主机
'PORT': 3306, # 端口号
'USER': 'root', # 用户名
'PASSWORD': 'root', # 密码
'NAME': 'book', # 数据库
}
}
10.10.3 运行测试
报错:
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. Did you install mysqlclient?
原因:没有安装 mysqlclient
在虚拟环境中安装 mysqlclient
pip install mysqlclient
如果
pip install mysqlclient
报错,需要在当前操作系统中安装libmysqlclient-dev
:
sudo apt-get install libmysqlclient-dev
遇到的问题
1. 返回html
乱码
原因:pycharmUTF-8
编码问题