三、进阶编程
变量在内存中的存储形式前言:全局变量与局部变量的访问
全局变量的访问范围:
①)在全局作用域中可以正常访问全局变量
②)在局部作用域中也可以正常访问全局变量
局部变量的访问范围
博学谷 1697207 正在观看视频
① 在局部作用域中可以正常访问局部变量
② 在全局作用域中无法正常访问局部变量,因为函数执行完毕后,其内部的变量会随之消失。
扩展:为什么函数执行完毕,其内部的变量会随之消失呢??答:内存的垃圾回收机制
1. 闭包
闭包的作用
当函数调用完,函数内定义的变量都销毁了,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一些列的操作比如:每次在这个变量的基础上和其它数字进行求和计算,那怎么办呢?
闭包的定义:
在函数嵌套的前提下内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
'''问题:如何把官方的闭包掌握下来?闭包三步走
答: ① 有嵌套(函数嵌套)
②有引用
'''
def outer():
# outer兩数中帕局部必量
num2 = 100
def inner(): #闭包
print(num2)
# 返回内部函数
return inner
fn = outer() # 当outer函数执行完毕后,把inner函数名称赋值给左边的fn这个全局变量
fn() #100
1.1global
声明全局变量,代表从这行代码开始,使用的变量都是全局中的变量
num = 10
def func():
# 尝试在局部作用域中修改全局变量
global num
num = 106
func()
print(num) #106
1.2nonlocal
声明局部变量,可以在内局部修改外局部变量
def outer():
num = 10
def inner():
nonlocal num #使用的外部变量(距离这个函数最近的变量)
num = 100
inner()
print(num) #100
outer()
案例:
def func():
# 局部变量
result = 0
# 嵌套函数
def inner(num):
# 访问离它最近的局部变量
nonlocal result
# 针对esult与num参数相加
result += numprint(result)
return inner
fn = func()
fn(1) # 1
fn(2) # 3
fn(3) #6
由于闭包并不会删除内存地址因此上一次运行结果仍然存在
2. 装饰器
装饰器的定义
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器的功能特点:
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
案例:给comment发表评论函数增加一个额外的功能(要求先登录,有了账号以后才能发表评论),要求:不能能改变现有comment函数
不能改变comment函数调用方式,还要增加一个登录功能
使用装饰器实现 => 装饰器的本质是一个闭包函数 =>
闭包三步 ① 有嵌套 ② 有引用 ③ 有返回
装饰器都有一个默认参数叫做fn,代表要修饰函数数的本身
def logging(fn):
def inner():
# 在引用fn之前增加额外功能
print('请先登录')
# 引用局部变量 fn
fn() #执行函数
return inner
@logging
def comment():
print('发表评论')
comment()
输出:
请先登录
发表评论
2.1 案例:求程序执行时间
提出需求:在不改变原有函数源代码以及调用方式的前提下,为其增加一个统计执行时间功能
import time
def get_time(fn):
def inner():
# 返回一个起始时间
start = time.time()
fn()
# 返回一个结束时间
end = time.time()
print(f'程序一共执行了{end-start}')
return inner
@get_time
def demo():
list1 = []
for i in range(100000):
list1.append(i)
demo()
2.2 使用场景
- 统计程序的执行时间
- 辅助系统功能输出日志信息
2.3 装饰带有参数的函数
定义一个函数sum_num(),针对参数求和
需求:① 在不改变原有函数代码 ② 不改变原有函数调用方式的前提下 ③ 为其新增一个输出日志功能
注:实际日志应该写入到日志文件=>print('------ 日志信息:------)
# 定义一个函数sum_num(),针对参数求和
def logging(fn):
def inner(a,b): #参数num1,num2
# 添加功能
print('------日志信息--------')
fn(a,b) #添加参数,执行函数
def sum_num(num1,num2):
result = num1+num2
print(result)
sum_num(10,20)
不定长参数
# 定义一个函数sum_num(),针对参数求和
def logging(fn):
def inner(*args,**kwargs): #不定长参数
# 添加功能
print('------日志信息--------')
fn(*args,**kwargs) #添加参数,执行函数
return inner
@logging
def sum_num(*args,**kwargs):
sum = 0
for i in args:
sum+=i
for v in kwargs.values():
sum+=v
print(sum)
sum_num(10,20,1,2,a=2,b=5)
输出:
------日志信息--------
40
有返回值函数
inner 也必须有返回值
# 定义一个函数sum_num(),针对参数求和
def logging(fn):
def inner(a,b): #不定长参数
# 添加功能
print('------日志信息--------')
return fn(a,b) #fn =func,fn()=>func(10,20) =>30
# 默认返回为none
return inner
@logging
def func(a,b):
return a+b
print(func(10,20)) # func(10,20) => inner(10,20)
2.4 通用装饰器(重要)
重点:通用装饰器 =>既可以装饰器带有参数的函数,也可以用于装饰带有返回值的函数
总结口诀:通用装饰器五步走 => ① 有嵌套 ② 有引用 ③ 有不定长参数 ④ 有返回值 ⑤ 返回内层函数的地址
def logging(fn):
def inner(*args,**kwargs):
print('-----日志-----')
return fn(*args,**kwargs)
return inner()
@logging
def sub_num(num1,num2):
return num1-num2
print(sub_num(20,10))
2.5 装饰器传参
需求:给sum_num函数以及sub num函数添加,个输出口志的功能,要求
sum_num输出日志:正在努力进行加法运算
sub_num输出日志,正在努力进行减法运算。
再在装饰器外部定义一个类用来接收语法糖传的参数,同时返回内层方法
def decoration(flag):
def logging(fn):
def inner(*args, **kwargs):
print(f'-----日志:努力进行{flag}运算-----')
return fn(*args, **kwargs)
return inner
return logging
@decoration('-')
def sub_num(num1, num2):
return num1 - num2
print(sub_num(20, 10))
def decoration(flag):
def logging(fn):
def inner(*args, **kwargs):
print(f'-----日志:努力进行{flag}运算-----')
return fn(*args, **kwargs)
return inner
return logging
@decoration('-')
def sub_num(num1, num2):
return num1 - num2
print(sub_num(20, 10))
@decoration('+')
def sum_num(num1, num2):
return num1 + num2
print(sum_num(20, 10))
2.6 类装饰器
装饰器大多数都是通过函数来装饰函数,其实也可以通过一个类来装饰函数,这种装饰器就称之为”类装饰器"
需求:使用类装饰器装饰comment函数,在不改变源代码以及调用方式的前提下,为其增加一个权限验证(请先登录)功能
类装饰器:
- 必须有一个__init__初始化方法,用于接收要装饰的函数
- 必须把这个类转换为可以调用的函数 def__call__()
class check():
def __init__(self, fn):
self.__fn = fn
def __call__(self, *args, **kwargs):
# 添加的功能
print('请登录')
# 调用comment函数4身=>__fn
self.__fn()
@check
def comment():
print('评论')
#调用函数
comment()
3. 前端基础 HTML+CSS
4. Socket网络编程
4.1 IP地址的表现形式
在计算机中,一般会有两个IP地址:
①上网IP地址 =>192.168.89.100
② 本地回环IP =>127.0.0.1
4.2 什么是端口号
4.3 TCP
网络应用程序之间的通信流程
之前我们学习了 IP 地址和端口号,通过 IP 地址能够找到对应的设备,然后再通过端口号找到对应的端口,再通过把数据传输给应用程序,这里要注意,数据不能随便发送,在发送之前还需要选择一个对应的传输协议,保证程序而这个传输协议就是TCP按照指定的传输规则进行数据的通信,
TCP 的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、
可靠的、基于字节流的传输层通信协议。
面向连接的效果图:
TCP通信步骤:①创建连接 ② 传输数据 ㊐ 关闭连接
TCP的特点
☆ 面向连接
通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源
☆ 可靠传输
① TCP 采用发送应答机制
② 超时重传
③ 错误校验
④ 流量控制和阻塞管理
4.4 socket 套接字
5. TCP 网络应用程序开发流程
TCP 网络应用程序开发分为:
① TCF 客户端程序开发
② TCP 服务端程序开发
说明:
客户端程序是指运行在用户设备上的程序服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务。
5.1 客户端开发
导入 socket 模块
import socket
创建客户端 socket 对象
socket.socket(AddressFamily, Type)
参数说明:
- AddressFamily 表示IP地址类型,分为IPv4和IPv6
- Type 表示传输协议类型
方法说明:
- connect(host,port))表示和服务端套接字建立连接,host是服务器ip地址,port是应用程序的端口号
- send(data) 表示发送数据,data是二进制数据
- recv(buffersize)表示接收数据,buffersize是每次接收数据的长度
socket.socket(socket.AF_INET,socket.SOCK STREAM)
socket.AF_INET :IP V4
socket.SOCK_STREAM :TCP协议
tcp client socket.connect()=>连接服务器端(IP地址和端口号)=> 参数是个元组(IP地址,端口号)
发送send()、接收数据recv(),注意事项:无论信息的发送还是接收都必须使用二进制流数据
encode 编码 将字符串转化为字节码
str.encode(encoding="utf-8")
decode 解码 将字节码转化为字符串bytes.decode(encoding="utf-8”)
案例:
'''
TCP客户端开发五步走:
① 创建客户端套接字对象 ② 建立连接 ③ 发送教据 ④ 接收数据 ⑤ 关闭套接字对象
socket.socket(socket.AF_INET,socket.SOCK STREAM)
socket.AF_INET :IP V4
socket.SOCK_STREAM :TCP协议
tcp client socket.connect()=>连接服务器端(IP地址和端口号)=> 参数是个元组(IP地址,端口号)
发送send()、接收数据recv(),注意事项:无论信息的发送还是接收都必须使用二进制流数据
'''
import socket
#第一步:创建客户端套按宇对象
tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 第二步:创建连接
tcp_client_socket.connect(('127.0.0.1',8090))
# 第三步:发送数据到服务器端
tcp_client_socket.send('I Love Python!'.encode('GBK'))
# 第四步:接收服务器端返回的数据
content =tcp_client_socket.recv(1024).decode('GBK')
print(content)
# 关闭连接
tcp_client_socket.close()
5.2 服务器端开发(重要)
绑定ip和端口
bind(元组参数)=>bind(('IP地址’,端口号=>建议8000以后))
设置监听
设置允许最大的监听数=>128
listen(整数参数)代表最大允许连接的总数
接收客户端连接请求(关键点、难点)
accept()方法:接收客户端连接 accept方法在接收数据的同时,如何识别到底是哪个客户端发起的连接呢?tcp_server_socket.accept() =>
(<socket.socket fd=800, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090),raddr=('127.0.0.1', 65206)>, ('127.0.0.1', 65206))
①本质是一个元组,元组内部理论上可以存放多个元素,元素与元素之间通过逗号隔开
② 这个元组中第一个参数是一个socket套接字对象(思考一下,是否与tcp_server_socket相同)
③ 这个元组中第二个参数也是一个元组,元组中有两个值(IP地址 + 端口号),通过査看内容可知,这代表客户端IP + 端口
案例:
'''
TCP服务器端开发七步走:① 创建服务器端套接字对象 ② 绑定IP和端囗 开始监哳 接收客户端连接凊求 接収数搌 ➅
发送数据 ⑦ 关闭套接字对象
绑定ip和端口
bind(元组参数)=>bind(('IP地址’,端口号=>建议8000以后))
设置监听
设置允许最大的监听数=>128
listen(整数参数)代表最大允许连接的总数
接收客户端连接请求(关键点、难点)
accept()方法:接收客户端连接 accept方法在接收数据的同时,如何识别到底是哪个客户端发起的连接呢?
'''
import socket
# 第一步:创建服务器端套接字对象
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 第二步:绑定本地IP和端口
tcp_server_socket.bind(('127.0.0.1',8090)) #如果绑定本机ip可以不写
# 第三步:设置监听
tcp_server_socket.listen(128)
# 第四步:接受客户端连接
new_socket,ip_port = tcp_server_socket.accept()
# print(new_socket) # 新问题:新套接字对象到底用来做什么?
# print(ip_port) #客户端IP+端口
# (<socket.socket fd=800, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090),raddr=('127.0.0.1', 65206)>, ('127.0.0.1', 65206))
# ①本质是一个元组,元组内部理论上可以存放多个元素,元素与元素之间通过逗号隔开
# ② 这个元组中第一个参数是一个socket套接字对象(思考一下,是否与tcp_server_socket相同)
# ③ 这个元组中第二个参数也是一个元组,元组中有两个值(IP地址 + 端口号),通过査看内容可知,这代表客户端IP + 端口
# 第五步:接收客户端发送的数据
content = new_socket.recv(1024).decode() #decode 不写默认也是GBK
print(f'{ip_port}发过来{content}')
# 第六步:响应数据给客户端
new_socket.send('信息收到'.encode('GBK'))
# 第七步:关闭新套接字对象(不能收发消息了)与服务器端套接字对象(不能接收客户端连接了)
new_socket.close()
tcp_server_socket.close()
5.3 服务器端开发-面向对象版
通过面向对象开发 =>分析有哪些对象=>创建类 => 属性和方法
服务器端对象 =>版务器端类
'''
通过面向对象开发 =>分析有哪些对象=>创建类 => 属性和方法
服务器端对象 =>版务器端类
'''
import socket
# 第一步:创建WebServer类
class WebServer(object):
# 第四步:定义魔术方法
def __init__(self):
# 创建服务器端套接字对象
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用(让服务器端占用的端口在执行结束可以立即释放、不影响后续程序的使用)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定IP和端口号
self.tcp_server_socket.bind(('127.0.0.1', 8090))
# 设置监听
self.tcp_server_socket.listen(128)
def handle_request(self, new_socket, ip_port):
# 接收客户端发送的数据
content = new_socket.recv(1024).decode('GBK') # 1024代表什么意思 =>1024字节=>1kb = 实际工作中,一条数据大小在1~1.5k之间
print(f'{ip_port}客户端发送了:{content}')
# 返回数据给客户端
new_socket.send('信息收到'.encode('GBK'))
# 关闭客户端
new_socket.close()
# 第五步:定义一个start方法 => 客户端连接
def start(self):
# 接收客户端连接
while True: # 允许多个客户端连接
new_socket, ip_port = self.tcp_server_socket.accept()
# 调用自身的handle_request()方法,用于接收消息与发送消息
self.handle_request(new_socket, ip_port)
if __name__ == '__main__':
# 第二步:实例化对象,调用__init__()初始化方法
ws = WebServer()
# 第三步: 启动服务,接收客户端连接
ws.start()
# 遗留问题:整个程序其实目前位置只能接收一个客户端,接收其他客户端,必须等待上一个连接结束,才可以继续连接发送接收
# 数据,因为我们目前编写的程序都是单进程!-个Python只能处理一个连接请求!如何期望服务器端,可以同时处理多个客户端请求,怎么办呢?答:使用多任务编程
1.当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接
2.TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的,否则客户端找不到这个TCP服务端程序
4.listen后的套接字是被动套接字,只负责接收新的客户的连接请求,不能收发消息
5.当TCP客户端程序和TCP服务端程序连接成功后,TCP服务器端程序会产生个新的套接字,收发客户端消息使用该套接字。6.关闭accept返回的套接字意味着和这个客户端已经通信完毕
7.当客户端的套接字调用close后服务端可以通过返回数据的长度来判断客户端是否ecv会解M返回的数据长度为0已经下线,反之服务端关闭套接字返回的数据长度也为0
6. 多任务
多任务的两种表现形式:并发、并行
并发:在一段时间内交替去执行多个任务。
并行:在一段时间内真正的同时一起执行多个任务
6.1 并发
6.2 并行
7. 进程
7.1 创建多进程
1.导入进程包
import multiprocessing
2.通过进程类创建进程对象
进程对象=multiprocessing.Process()
3.启动进程执行任务
进程对象.start()
通过进程类创建进程对象
进程对象=multiprocessing.Process(target=任务名)
案例(并行):
import time
# 导入多进程包
import multiprocessing
# 定义一个函数,用于实现听音乐
def music():
for i in range(3):
print('正在听音乐...')
time.sleep(0.2)
# 定义一个函数,用于实现写代码
def coding():
for i in range(3):
print('正在写代码...')
time.sleep(0.2)
if __name__ == '__main__':
# 从主进程创建两个子进程
music_process = multiprocessing.Process(target=music)
coding_process = multiprocessing.Process(target=coding)
# 第三步:启动刚才创建的子进程
music_process.start()
coding_process.start()
结果:
正在听音乐...
正在写代码...
正在写代码...正在听音乐...
正在写代码...正在听音乐...
7.2 进程执行带有参数的任务
*args 以元组的方式给执行任务传参
**kwargs 以字典方式给执行任多传参
示例:
music_process = multiprocessing.Process(target=music,args=(3,))
coding_process = multiprocessing.Process(target=coding,kwargs={'n':3})
案例:
import time
# 导入多进程包
import multiprocessing
# 定义一个函数,用于实现听音乐
def music(n):
for i in range(n):
print('正在听音乐...')
time.sleep(0.2)
# 定义一个函数,用于实现写代码
def coding(n):
for i in range(n):
print('正在写代码...')
time.sleep(0.2)
if __name__ == '__main__':
# 从主进程创建两个子进程
music_process = multiprocessing.Process(target=music,args=(3,))
coding_process = multiprocessing.Process(target=coding,kwargs={'n':3})
# 第三步:启动刚才创建的子进程
music_process.start()
coding_process.start()
# 结果:
# 正在听音乐...
# 正在写代码...
# 正在写代码...正在听音乐...
#
# 正在写代码...正在听音乐...
7.4 获取进程编号
import os
os.getpid() 获取当前进程
os.getppid() 获取当前父进程
案例:
import os
import time
import multiprocessing
#定义一个任务working,专门用于实现某个任务
def working():
print(f'获取子进程{os.getpid()}')
print(f'获取当前父进程{os.getppid()}')
for i in range(3):
print('working.....')
time.sleep(0.2)
if __name__ == '__main__':
# 第一步:获取主进程的编号
print(f'主进程编号{os.getpid()}')
# 创建子进程
sub_pro = multiprocessing.Process(target=working)
sub_pro.start()
输出:
主进程编号26644
获取子进程25384
获取当前父进程26644
7.5 kill杀掉进程
根据进程编号杀死指定进程
os.kill(要杀掉进程的pid,信号)
信号:
9:强制杀掉PID进程15:通知PID进程,正常结束
7.6 主进程与子进程的结束顺序
默认情况下 主进程会随着子进程的结束而结束
如需主进程结束子进程跟着结束:
设置守护进程
销毁子进程
8. 线程
进程是分配资源的最小单位,一旦创建一个进程就会分配一定的资源,就像跟两个人聊QQ就需要打开两个QQ软件样是比较浪费资源的
线程是程序执行的最小单位,实际上进程只负责分配资源,而利用这些资源执行程序的是线程,也就说进程是线程的容器,一个进程中最少有一个线程来负责执行程序,同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源,这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样,实现多任务的同时也节省了资源
一个进程可以同时包含多个线程
什么是使用进程?什么时候使用线程?
偏CPU计算任务 =>推荐使用多进程 =>CPU
偏IO型任务 =>文件操作/网络连接 =>推荐多线程
多进程=>CPU允许的情况下 =>并行
多线程 =>多任务 =>并发执行
示例:
1.导入线程模块
import threading
2.通过线程类创建线程对象线程对象=threading.Thread(target=任务名)
3.启动线程执行任务
线程对象.start()
案例:
'''
在Python代码中,一个Python文件可以创建一个进程=> 创建多线程
线程包含在进程内部,一个进程理论上最少有一个线程
进程资源分配最小单元 => 中请资源
线程专门用于执行程序(干活的)
'''
# 导入模块
import threading
import time
def music():
for i in range(3):
print('正在听音乐...')
time.sleep(0.2)
# 定义一个函数,用于实现写代码
def coding():
for i in range(3):
print('正在写代码...')
time.sleep(0.2)
if __name__ == '__main__':
# 创建线程对象
music_thread = threading.Thread(target=music)
coding_thread = threading.Thread(target=coding)
# 启动线程
music_thread.start()
coding_thread.start()
8.1 线程执行带有参数的任务
music_thread = threading.Thread(target=music,args=(3,))
coding_thread = threading.Thread(target=coding,kwargs={'t':3})
'''
在Python代码中,一个Python文件可以创建一个进程=> 创建多线程
线程包含在进程内部,一个进程理论上最少有一个线程
进程资源分配最小单元 => 中请资源
线程专门用于执行程序(干活的)
'''
# 导入模块
import threading
import time
def music(n):
for i in range(n):
print('正在听音乐...')
time.sleep(0.2)
# 定义一个函数,用于实现写代码
def coding(t):
for i in range(t):
print('正在写代码...')
time.sleep(0.2)
if __name__ == '__main__':
# 创建线程对象
music_thread = threading.Thread(target=music,args=(3,))
coding_thread = threading.Thread(target=coding,kwargs={'t':3})
# 启动线程
music_thread.start()
coding_thread.start()
8.2 多线程共享全局变量
多进程实现多任务:无法共享全局变量
多线程实现多任务:因为所有线程都位于同一个进程中,所以不仅资源共享,而且全局变量也是共享的
import time
import threading
my_list = []
# 定义一个write_data()任务
def write_data():
for i in range(3):
my_list.append(i)
print('add:', i)
print(my_list)
# 定义一个read_data()任务
def read_data():
print(my_list)
if __name__ == '__main__':
write_thread = threading.Thread(target=write_data)
read_thread = threading.Thread(target=read_data)
write_thread.start()
time.sleep(1)
read_thread.start()
结果:
add: 0
[0]
add: 1
[0, 1]
add: 2
[0, 1, 2]
[0, 1, 2]
8.3 主进程与子进程的结束顺序
设置守护线程
8.4 线程的执行顺序
获取当前线程信息
# 通过current_thread方法获取线程对象
current_thread = threading.current_thread()
# 通过current_thread对象可以知道线程的相关信息,例如被创建的顺序print(current_thread)
线程间的执行顺序
线程与线程之间没有执行顺序
import threading
import time
def get_info():
#可以暂时先不加,查看效果
time.sleep(0.5)
current_thread = threading.current_thread()
print(current_thread)
if __name__ =='__main__':
# 创建5个子线程
for i in range(5):
sub_thread = threading.Thread(target=get_info)
sub_thread.start()
结果:
<Thread(Thread-2, started 24384)>
<Thread(Thread-1, started 10704)>
<Thread(Thread-3, started 16088)>
<Thread(Thread-4, started 28596)>
<Thread(Thread-5, started 13588)>
9. 进程和线程对比
关系对比
区别对比
优缺点对比
10. 案例:多任务版本的TCP服务器端
同一时间同时接收多个客户端
# 来一个客户,我们就为其创建一个线程,调用自身的handle_request()方法,用于接收消息与发送消息
sub_thread = threading.Thread(target=self.handle_request,args=(new_socket, ip_port))
# 启动线程
sub_thread.start()
'''
通过面向对象开发 =>分析有哪些对象=>创建类 => 属性和方法
服务器端对象 =>版务器端类
'''
import socket
import threading
# 第一步:创建WebServer类
class WebServer(object):
# 第四步:定义魔术方法
def __init__(self):
# 创建服务器端套接字对象
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用(让服务器端占用的端口在执行结束可以立即释放、不影响后续程序的使用)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定IP和端口号
self.tcp_server_socket.bind(('127.0.0.1', 8090))
# 设置监听
self.tcp_server_socket.listen(128)
def handle_request(self, new_socket, ip_port):
# 接收客户端发送的数据
content = new_socket.recv(1024).decode('GBK') # 1024代表什么意思 =>1024字节=>1kb = 实际工作中,一条数据大小在1~1.5k之间
print(f'{ip_port}客户端发送了:{content}')
# 返回数据给客户端
new_socket.send('信息收到'.encode('GBK'))
# 关闭客户端
new_socket.close()
# 第五步:定义一个start方法 => 客户端连接
def start(self):
# 接收客户端连接
while True: # 允许多个客户端连接
new_socket, ip_port = self.tcp_server_socket.accept()
# 来一个客户,我们就为其创建一个线程,调用自身的handle_request()方法,用于接收消息与发送消息
sub_thread = threading.Thread(target=self.handle_request,args=(new_socket, ip_port))
# 启动线程
sub_thread.start()
if __name__ == '__main__':
# 第二步:实例化对象,调用__init__()初始化方法
ws = WebServer()
# 第三步: 启动服务,接收客户端连接
ws.start()
11. HTTP协议
11.1 URL
11.2 请求报文(用户=>服务器)
GET
POST
11.3 响应报文(服务器=>用户)
12. 搭建静态Web服务器
12.1 如何搭建Python自带的静态Web服务器
1. 准备一个带有index.html页面的文件夹,在终端进入
2.输入 python -m http.server 9000
12.2 开发自己的静态Web服务器
12.2.1 返回固定页面
实现步骤:
- 编写一个TCP服务端程序
- 获取浏览器发送的http请求报文数据
- 读取固定页面数据把页面数据组装成HTTP响应报文数据发送给浏览器
- HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
实现步骤:
编写一个TCP服务端程序(七步走)获取浏览器发送的HTTP请求报文
数据读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器
HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
'''
实现步骤:
编写一个TCP服务端程序(七步走)
获取浏览器发送的HTTP请求报文
数据读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器
HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
'''
import socket
if __name__ == '__main__':
# 创建套接字对象
tcp_server_scoker = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定IP端口
tcp_server_scoker.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
tcp_server_scoker.bind(('127.0.0.1',9000))
# 设置监听
tcp_server_scoker.listen(128)
# 等待客户端连接
while True:
new_scoker, ip_port = tcp_server_scoker.accept()
# 接收数据
content = new_scoker.recv(4069).decode('UTF-8')
print(content)
# 返回数据
with open('./html/index.html','rb') as f:
file_data = f.read()
# 关键点:我们需要把以上数据拼接为HTTP响应报文(响应行、响应头、空行、响应体)
response_line = 'HTTP/1.1 200 0K\r\n'
response_header = 'Server:PWB/1.1\r\n'
# 空行 => \r\n
response_body = file_data
# 组装HTTP想要数据
response_data = (response_line + response_header +'\r\n').encode('utf-8') + response_body
new_scoker.send(response_data)
# 关闭套接字对象
new_scoker.close()
12.2.2 返回指定页面
目前的Web服务器,不管用户访问什么页面,返回的都是固定页面的数据,接下来需要根据用户的请求返回指定页面的数据。
返回指定页面数据的实现步骤:
- 获取用户请求资源的路径
- 根据请求资源的路径,读取指定文件的数据
- 组装指定文件数据的响应报文,发送给浏览器
- 判断请求的文件在服务端不存在,组装404状态的响应报文,发送给浏览器
'''
实现步骤:
编写一个TCP服务端程序(七步走)
获取浏览器发送的HTTP请求报文
数据读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器
HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
返回指定页面:
获取用户请求资源的路径
根据请求资源的路径,读取指定文件的数据
组装指定文件数据的响应报文,发送给浏览器
'''
import socket
if __name__ == '__main__':
# 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定IP端口设置端口复用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
tcp_server_socket.bind(('127.0.0.1', 9000))
# 设置监听
tcp_server_socket.listen(128)
while True:
# 接收连接
new_socket, ip_port = tcp_server_socket.accept()
# 接收数据
content = new_socket.recv(4096)
# 判断(易错点)=>新版的很多浏览器都带了一个自动刷新功能(每隔一段时间不操作,浏览器会自动刷新1次)
# => 发送了一个空包
if content:
# 如果浏览器发送过来了数据,则我们对HTTP请求数据进行解析
content = content.decode('UTF-8')
# 获取用户请求的页面 => 请求行
# maxsplit = 2 分割两次
request_path = content.split(' ',maxsplit = 2)[1] #/list.html
# print(request_path)
# 解决城名直接访问首页问题
if request_path == '/':
request_path = '/index.html'
# 第六步 返回数据给浏览器客户端
try:
with open(f'./html/{request_path}', 'rb') as f:
file_data = f.read()
except:
# 拼接http响应报文 => 响应行,响应头,空行,响应体
response_line = 'HTTP/1.1 404 Not Found\r\n'
response_head = 'Server:PWB//1.1\r\nContent-type:text/html;charset=utf-8\r\n'
# 空行 \r\n
response_body ='页面不存在\r\n'
response_data = (response_line + response_head + '\r\n'+ response_body).encode('utf-8')
# 返回数据
new_socket.send(response_data)
else:
# 拼接http响应报文 => 响应行,响应头,空行,响应体
response_line = 'HTTP/1.1 200 OK\r\n'
response_head = 'Server:PWB//1.1\r\n'
# 空行 \r\n
response_body = file_data
response_data = (response_line + response_head + '\r\n').encode('utf-8') + response_body
# 返回数据
new_socket.send(response_data)
finally:
# 关闭连接
new_socket.close()
13. Fast框架
简单讲FastAPI就是把做web开发所需的相关代码全部简化,我们不需要自己实现各种复杂的代码例如多任务,路由装饰器等等,只需要调用FastAP!提供给我们的函数,一调用就可以实现之前需要很多复杂代码才能实现的功能
13.1 fast的的基本使用
示例:
# 第一步:导入模块
from fastapi import FastAPI
from fastapi import Response
import uvicorn #启动服务
# 第二步:创建FastAPI刘象
app = FastAPI()
# 第三步:使用装饰器收发消息 (收消息 => @app.get(),@app.post())
@app.get('/index')
def main():
with open('../html/index.html','rb') as f:
file_data = f.read()
# 返回数据给客户端游览器
return Response(content=file_data,media_type='text/html')
# 第四步:启动服务 uvicorn相当于 =>socket套接字
uvicorn.run(app,host='127.0.0.1',port=9000)
13.2 基于FastAPI之Web站点开发
from fastapi import FastAPI
from fastapi import Response
import uvicorn
# 创建对象
app = FastAPI()
# 使用装饰器收发消息
@app.get('/')
def main():
with open('../html/index.html', 'rb') as f:
file_data = f.read()
# 返回数据给服务器
return Response(content=file_data,media_type='text/html')
@app.get('/img/{path}') #前端传递的参数
def img(path:str):
with open(f'../html/img/{path}.jpg', 'rb') as f:
file_data = f.read()
return Response(content=file_data,media_type='jpg')
uvicorn.run(app,host='127.0.0.1',port=8000)
14. 高级语法与正则表达式
14.1 with 语句(上下文管理器)
with语句属fPuthon高级语法
等价于
try:
except:
finally:
示例:
with open('python.txt')as f:
# 文件操作(读写)
f.write('hello world')
14.2 yield生成器
什么是生成器
根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束,数据不是一次性全部生成出来,而是使用一个再生成一个,可以节药大量的内存。
创建生成器的方式:① 生成器推导式 ② yield 关键字
生成器中并没有真正的保存具体的数据,其返回的结果是一个对象,对象中保存了所有数据的生成规则
生成器中有一个特别重要的函数next(),没调用一次next()则自动从生成器中根据生成规则获取一个元素
生成器的意义:降低程序的能耗(因为其不需要存大量的数据、只是在调用next()时,才生成一个元素,所以龍耗大大降低)
示例:
my_generator = (i*2 for i in range(5))
print(my_generator) # 对象格式 => <generator object <genexpr> at 0x000001EA1CF71510>
value = next(my_generator)
print(value) #第一个数据 => 0
for i in my_generator:
print(i) # 0,2,4,6,8
14.2.1 yield生成器
其结构一共分为两部分:
①首先要定义一个数 ② 在函数内部存在一个yield关键字
我们把①② 组合在一起就称之为叫做
yield生成器另外要特别强调yield生成器是一个对象而不是一个函数
def generator(n)
for i in range(n):
print('开始生成..')yield i
print('完成一次...')
g = generator(5)
示例:
def generator(n):
for i in range(n):
print('开始')
yield i #暂时把yield当做return理解,每次遇到yield,生成器就相当于执行1次
print('完成1次')
# 在使用时,由于此成器需要传递参数,所以通常将其赋子给个量
g= generator(5)
print(g) #<generator object generator at 0x000001DBCE131510> => 对象
print(next(g)) #0
print(next(g)) #1
print(next(g)) #2
print(next(g)) #3
print(next(g)) #4
# 生成器中默认只有5个元素的生成规则,当尝试打印第6个元素时,会弹出yield后面的输出语句
print(next(g)) # 完成1次
print(next(g)) #报错
14.3 深浅拷贝
浅拷贝:
可变类型只能拷贝外层 => [1,2,3,4,[7,9]]
不可变类型只能引用不能拷贝
深拷贝:
可变类型能拷贝内外层 => [1,2,3,4,[7,9]]
不可变类型只能引用不能拷贝
Python中深浅拷贝特殊案例
可变嵌套不可变类型
外层是可变类型,所以可以进行完全拷贝(需要生成内存空间),但是内层对象是不可变数据类型,所以只能拷贝引用关系
不可变类型嵌套可变
可以整体拷贝
14.4 正则表达式
14.4.1 re模块
案例:
# 1、导入模块
import re
# 2、使用match方法通过正则匹配结果(match只能匹配开头,如果匹配任意位置也可以使用findall())
str1 = '1324799075478'
str2 = '!.5-=@$%^w4gfds'
# 3、使用正则匹配
# 查找字符串是否包含‘8’
result1 = re.findall('8', str1) # 没有返回空列表
# 定义一个字符串,判断这个字符串中是否包含数字
result2 = re.findall('\d', str2) # \d => 数字
# 使用re模块结合正则表达式获取所有非数字
result3 =re.findall('\D',str2) # \D => 非数字
print()
正则编写三步走
查什么、查多少、从哪查
查什么
字符簇常见写法=> []:
查多少
从哪查
re.match():匹配满足正则表达式的结果(只能第一个满足条件的结果),而且有限制,要匹配的内容必须出现在开头match()方法返回的是一个re正则对象,必须通过result.group()才能获取结果
re.search():匹配满是正则表达式的结果(也只能匹配到第一个满是条件的结果),没有位置限制,在哪里都可以,search(方法返回的是一个re正则对象,必须通过result.group()才能获取结果,优点,如果正则中有分组(子表达式),可以通过group(分组编号)获取内容
re.findall():匹配所有满足正则表达式的结果(所有),没有位置限制,findall()只能匹配对应的结果,无法获取某个分组中内容。返回的结果是一个列表类型的数据。
re.finditer():匹配所有满足正则表达式的结果(所有)没有位置限制,finditer()不仅可以匹配到整个正则匹配到的结果其还可以专门用于获取分组中得到的数据,可以使用result.group(分组编号)获取分组内容1:
15. 案例:正则表达式
15.1 列表中匹配
在列表中["apple","banang","orange","pear"],匹配apple和pear
import re
list1 =["apple","banang","orange","pear"]
# 第一步:把列表转换为字符肃
str1 = str(list1)
#第二步:使用正则表达式匹配apple或pear
result = re.findall(r'(apple|pear)', str1)
for i in result:
print(i) #apple pear
15.2 匹配邮箱
要求:匹配出163、126、qq等邮箱
import re
str1 ='14786700@qq.com,go@126.com,heima123@163.com'
result = re.finditer(r'\w+@(126|163|qq).com', str1)
for i in result:
print(i.group())
15.3 split()分割
需求:匹配qq:10567这样的数据,提取出来qg文字和gg号码
split():按照指定字符对字符串进行分制操作,也支持正则表达式
import re
str1 ='qq:10672'
result = re.split(r':',str1)
print(f'{result[0]}:{result[1]}')
15.4 匹配网页标签
从字符串中匹配出<html><h1>www.itcast.cn</h1></html>
import re
str1 = '<div><h1>666</h1></div>'
result = re.match(r'<(\w+)><(\w+)>(.*)</\2></\1>', str1)
print(result.group()) # <div><h1>666</h1></div>
print(result.group(3)) # 666
16. 爬虫
入门案例:
import requests
# 发送请求
data = requests.get('https://www.baidu.com/')
# 解析
content = data.content.decode('UTF-8')
print(content)
16.1 xpath
16.2 多任务爬虫
17. pyecharts 数据可视化
快速上手
from pyecharts.charts import Bar
from pyecharts import options as opts
# 内置主题类型可查看 pyecharts.globals.ThemeType
from pyecharts.globals import ThemeType
bar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
.add_xaxis(["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"])
.add_yaxis("商家A", [5, 20, 36, 10, 75, 90])
.add_yaxis("商家B", [15, 6, 45, 20, 35, 66])
.set_global_opts(title_opts=opts.TitleOpts(title="主标题", subtitle="副标题"))
)
17.1 zip函数的使用
为什么要把数据进行合并?
因为我们要把GDP的数据组装成 [(),(),(),()] 结构
zip()函数有何作用呢?其作用就是把两个列表甚至多个列表进行合并作
特别说明:zip()返回的结果是一个对象,不支持直接打印,所以我们需要通过list()函数对其进行转换
a = [1, 2, 3]
b = [4, 5, 6]
c = zip(a, b)
print(list(c)) #[(1, 4), (2, 5), (3, 6)]
17.2 Faker
from pyecharts.faker import Faker #生成随机测试数据
print(Faker.choose()) # ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
print(Faker.values()) # [127, 40, 57, 138, 58, 149, 120]
17.3 配置项
示例:
c=(
Bar(
# InitOpts 初始化配置项
init_opts=opts.InitOpts(
width='300px',
height='400px',#设置画布大小
renderer=RenderType.CANVAS,#渲染风格,可选: canvas,svg
page_title='网页标题',
theme=ThemeType.DARK, #主题
bg_color='pink' #背景颜色
)
)
.add_xaxis(Faker.choose())
.add_yaxis('商家A',Faker.values())
.add_yaxis('商家B',Faker.values())
# 全局配置项
.set_global_opts(
# TitleOpts 标题配置项
title_opts=opts.TitleOpts(
title='柱形图',#主标题
title_link='https://www.baidu.com',#主标题点击连接
title_target='blank',#blank 新窗口打开,self 当前窗口打开
# 副标题
subtitle='副标题',
subtitle_link='https://www.baidu.com',#主标题点击连接
subtitle_target='blank',#blank 新窗口打开,self 当前窗口打开
# 位置
pos_left='40px',#左边 left靠左,rihgt靠右,40px,20%
pos_top='0px',
# pos_right=''
# pos_bottom=''
# 内边距
padding=20,
item_gap=5,#主副标题之间的间隙
),
#DataZoomOpts: 区域缩放配置项
datazoom_opts=opts.DataZoomOpts(
is_show=True,#显示
type_='slider',#组件类型:slider,inside
is_realtime=True,#拖动是否实时更新
range_start=20,#数据窗口起始位置
range_end=80,
orient='horizontal',# horizontal或vertical 水平或者垂直 组件位置
is_zoom_lock=True #是否锁定选择区域
),
# 图例配置项
legend_opts=opts.LegendOpts(
#图例类型 plain 普通图例,scroll 可以滚动翻页的图例
type_='scroll',
is_show=True,#是否显示
# 位置
pos_left='20%',#左边 left靠左,rihgt靠右,40px,20%
# pos_top='0px',
# pos_right=''
# pos_bottom=''
orient='horizontal',# horizontal或vertical 水平或者垂直 组件位
# 选择模式
# Ture :开启
# False:关闭
# single:单选
# multiple:多选
selected_mode='multiple',
# 图标和文字的位置
align='left',
padding='20',#内边距
item_gap=5, #图例每一项的间距
inactive_color='#ccc', #图例关闭的颜色
# 常见的图标
legend_icon='circle',#圆形
),
# VisualMapOpts:视觉映射配置项
visualmap_opts=opts.VisualMapOpts(
is_show=True,
type_='color',
min_=0,
max_=200
),
# 提示框配置项
tooptip_opts=opts.TooltipOpts(
is_show=True,
# 触发类型
# item:一般用于散点图
# axis:坐标轴提示线,用于条形图
trigger='item',
)
)
)
c.render_notebook()
# c.render()
18. Logging日志
18.1 输出到控制台
18.1 输出到文件