python 学习
开发常用的网站
- 博客园
- csdn
- 51cto
- 开源中国
- github
- 知乎
- 简书
1 网络编程
1-1 网络基础概念
- 基本概念
- 数据共享
- IP地址
- 值:192.168.0.0.1
- 作用:标记网络上的一台电脑
- 私有 ip
在众多网络中,国际规定有一部分IP地址是用于我们的局域网使用,也就是属于私网IP,不再公网使用的,它的范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0.0~192.168.255.255注意: IP 地址 127.0.0.1 ~ 127.255.255.255 用于回路测试
- Linux、Windows查看网卡信息
- Linux
- sudo ifconfig ens40 up/down
- Windows
- ipconfig
lo:本地网卡
ens40:外网网卡
- ipconfig
- IP地址的分类-ipv4和ipv6介绍
- ipv4 : 0-255.0-255.0-255.1-255
192.168.0.0.1- A 0
- B 10
- C 110
- D 1110 多点广播
- E 11110 保留
0/255 不能用;1-254用
类型 | 网络号 | 网络号 |
---|---|---|
A | 0******* | ..******** |
B | 10******.******** | . |
C | 110*****.. | ******** |
- ipv6
fe80::4a3:4ee2:d0cd:f011
- (重点)端口
- 给哪个进程? port
- 端口是通过端口号标记的,端口号是标记一个程序的
- 端口分类:知名端口、动态端口
端口号不是随意使用的,而是按照一定的规定进行分配的。
- 知名端口:0~1023
- 动态端口:1024~65535
- socket介绍
- 不同电脑上的进程之前如何通信
- 创建 socket
import socket
# 创建 tcp 套接字
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# ... 这里使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
# 创建 udp 套接字
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# ... 这里使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
说明:
套接字的使用流程与文件的使用流程很类似
1.创建套接字
2.使用套接字收/发数据
3.关闭套接字
1-2 udp 用户数据报协议
运行模式
- python3 ***.py
交互模式 - python3
- ipython3
- (重点)udp 发送数据 demo
# demo
#!/usr/bin/env python3
import socket
def main():
# 创建一个tcp套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 从键盘发送数据
send_data = input("请输入要发送的数据:")
# 可以使用套接字收发数据
# 元组 dest_ip / b"hhahah" 字符类型,加一个 b 转译
udp_socket.sendto( send_data.encode('utf-8'), ("10.10.22.68", 8088))
# 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
# demo-2 循环发
#!/usr/bin/env python3
import socket
def main():
# 创建一个tcp套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
# 从键盘发送数据
send_data = input("请输入要发送的数据:")
# 如果输入的数据是 exit,那么久退出程序
if send_data == 'exit':
break;
# 可以使用套接字收发数据
# 元组 dest_ip / b"hhahah" 字符类型,加一个 b 转译
udp_socket.sendto( send_data.encode('utf-8'), ("10.10.22.68", 8088))
# 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
- (重点)接收 udp 数据
注意:windows 的编码默认是 gbk
#!/usr/bin/env python3
import socket
def main():
# 1 创建套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2 绑定一个本地信息
localaddr = ("",7788)
udp_socket.bind(localaddr) # 必须绑定自己的 ip,以及 port
# 3 接收数据
while True:
recv_data = udp_socket.recvfrom(1024) # 1024 本次接收的最大字节数
# recv_data 这个变量中存储的是一个元组,(接收到的数据,(发送方的IP,port))
recv_msg = recv_data[0] # 存储接收的数据
send_addr = recv_data[1] # 存储发送方的地址信息
# 4 打印接收的数据
# print(recv_data)
print("%s:%s"%(str(send_addr),recv_msg.decode("utf-8")))
# 5 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
- 总结:udp 发送/接收 数据流程
udp 发送数据流程 | udp 接收数据流程 |
---|---|
1.绑定套接字 2.发送数据 3.关闭套接字 | 1.绑定套接字 2.绑定本地信息 3.接收打印数据 4.关闭套接字 |
- (重点)端口绑定的问题
- 同一个端口不允许同一时刻,被用两次
- 发送方可以不绑定端口,接收方必须先绑定端口
- 输入对方 ip、port、全双工、半双公、单工等
-
- recvfrom 在数据没到来之前,会堵塞程序
-
- 单工:只能收;
-
- 半双工:能收发,不能同一时刻操作;
-
- 全双工:同一时刻能收能发;
-
- socket: 全双工
-
- 案例:udp 聊天机器
#!/usr/bin/env python3
import socket
def send_msg(udp_socket):
"""发送消息"""
dest_ip = input("请输入对方的 ip:")
dest_port = int(input("请输入对方的 port:"))
send_data = input("请输入要发送的消息:")
udp_socket.sendto( send_data.encode('utf-8'), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据"""
recv_data = udp_socket.recvfrom(1024) # 1024 本次接收的最大字节数
print("%s:%s" % (str(recv_data[1]), recv_data[0].decode("utf-8"))) # windows 是 gbk
def main():
# 创建套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 绑定信息
udp_socket.bind(("",7788))
# 循环来处理的事情
while True:
# 发送
send_msg(udp_socket)
# 接收
recv_msg(udp_socket)
if __name__ == "__main__":
main()
1-3 tcp 客户端 - 传输协议
- 创建 tcp 套接字
- ** 连接服务器 **
- 发送/接收 数据
- 关闭套接字
#!/usr/bin/env python3
import socket
def main():
# 创建 tcp 套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入服务器的ip:")
server_port = int(input("请输入服务器的port:"))
server_addr = (server_ip,server_port)
tcp_client_socket.connect(server_addr)
# 发送/接收 数据
send_data = input("请输入发送的数据:")
tcp_client_socket.send(send_data.encode("utf-8"))
# 关闭套接字
tcp_client_socket.close()
if __name__ == "__main__":
main()
1-4 tcp 服务器
- socket 创建一个套接字
- bind 绑定 ip和port
- listen 使套接字变为可以被动连接
- accept 等待客户端的连接
- recv/send 接收发送数据
监听套接字负责接收新的套接字
accept产生新的套接字,为客户服务
#!/usr/bin/env python3
import socket
def main():
# 买个手机(创建套接字 socket)
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 插入手机卡(绑定本地信息 bind)
tcp_server_socket.bind("",7890)
# 将手机设置为正常的接听状态 (让默认的套接字由主动变为被动 listen)
tcp_server_socket.listen(128) # 128同一时刻客户端连接服务器的数量
# 循环为多个客户端服务
while True:
print("等待一个新的客户端的到来。。。")
# 等待别人的电话的到来(等待客户端的连接 accept)
new_client_socket,client_addr = tcp_server_socket.accept()
print("一个新的客户端已经到来%s" % str(client_addr))
# 循环多次为同一个客户端服务多次
while True:
# 接收对方发来的数据
revc_data = new_client_socket.recv(1024) # 接收1024个字节
print("客户端发送过来的请求是:%s" % recv_data.decode('gbk'))
# 如果 recv 解堵塞,那么有2种方式
# 1. 客户端发送过来数据
# 2. 客户端调用close导致了 recv 解堵塞
if revc_data:
# 回送一部分数据给客户端
new_client_socket.send("thank you!".encode('gbk'))
else:
break
# 关闭accept返回的套接字,意味着不会再为这个客户端服务
new_client_socket.close()
print("已经服务完毕。。。")
# 如果将监听套接字关闭了,那么会导致不能再吃等待客户端的到来,即 xxxx.accept就会失败
tcp_server_socket.close()
if __name__ == "__main__":
main()
1-5 tcp 下载文件
文件下载器
#!/usr/bin/env python3
#coding:utf-8
# client
import socket,os
def
def main():
# 创建套接字
tcp_socket = socket.socket()
# 获取服务器的 ip port
tcp_socket.connect()
# 连接服务器
# 将文件名字发送到服务器
file_name = input("请输入文件名称:")
# 接收文件数据
# 保存接收数据到一个文件中
# 关闭套接字
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# coding:utf-8
# server
import socket,os
def
def main():
if __name__ == "__main__":
main()
udp 与 tcp
udp: 写信的模型,不安全
tcp: 打电话的模型,比较安全;比较稳定; 采用发送应答机制
— 对比 —
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制 | |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信 | |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
udp | tcp | |
---|---|---|
客户端流程 | socket\bind\sendto/recvfrom\close | socket\connect\send/recv\close |
服务端流程 | socket\bind\listen\accept\recv/send\close |
tcp 总结
- tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
- tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
- tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
- 当客户端需要链接服务器时,就需要使用connect进行连接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
- 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
- listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
- 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信
- 关闭accept返回的套接字意味着这个客户端已经服务完毕
- 当客户端的套接字调用close后,服务端会recv解堵塞,并且返回的长度为0,因此可以通过返回数据的长度来区别客户端是否已经下线
2 多任务
简介
- 多任务
什么叫“多任务”,简单地说,就是操作系统可以同时做多个任务。- 单核CPU 要实现多任务,通过调度算法实现,如:时间片轮转、优先级调度等;四核CPU相当于4个单核CPU。
- 并发: 任务量大于CPU核数,通过操作系统的各种调度算法,实现多个任务“一起”执行(实际上由于切换任务的速度非常快,只是看上去一起执行,并没有真正的同时执行。)
- 并行: 任务量小于等于CPU核数,级任务是真正的一起执行的。
- 进程
进程是具有一定独立功能的程序(就是一坨代码,还没有运行时叫程序)关于某个数据集合上的一次运行活动 (是运行的程序),进程是系统进行资源分配的单位。- 线程
线程是进程的一个实体,是CPU调度的单位 ,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点运行中必不可少的资源(如:程序计数器、一组寄存器和栈)。
进程和线程之间的关系: 举个简单的例子:一个手机中运行了好多后台的APP程序,如微信、QQ、支付宝…,其中,一个进程就是运行中的QQ,而QQ中可以跟不同的人进行聊天,每个聊天的窗口就是一个线程,你可以同时跟好多人聊天(即,开好多个聊天窗口,也就是说一个进程中可以由好多线程 ),但是当一个聊天窗口卡死了,QQ就不能运行了(一个线程死掉就等于整个进程死掉 ),只能强制把它关了然后重启,但是你QQ挂了,并不影响你的微信和支付宝的运行(进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响 ),同时你可以在不同的聊天窗口发送相同的QQ表情包,但是你不能在微信里发送QQ里的表情包(同进程里的多线程之间共享内存数据,不同进程之间是相互独立的,各有个的内存空间 )。
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def sing():
"""唱歌5秒钟"""
for i in range(5):
print("---唱歌---")
time.sleep(1)
def dance():
"""跳舞5秒钟"""
for i in range(5):
print("---跳舞---")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 注意:不能写成 sing();加括号是调用函数,不加括号是找到函数的位置
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == "__main__":
main()
2-1 线程
- 线程的运行是没有先后顺序的
- 查看程序中所有线程数量:threading.enumerate()
- 当调用Thread的时候,不会创建线程;调用start才会运行子线程
- 延时的话可以保证先运行哪个程序,再运行哪个程序
线程创建的两种方法:
- Thread(target=函数名);简单优先使用
- 通过继承Thread类完成创建线程;必须定义 run 方法
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def test1():
for i in range(5):
print("----test1---%d---" % i)
time.sleep(1)
# 如果创建Thread时执行的函数,运行结束那么意味着 这个子线程结束了....
def main():
# 在调用Thread之前先打印当前线程信息
print(threading.enumerate())
t1 = threading.Thread(target=test1)
# 在调用Thread之后打印
print(threading.enumerate())
t1.start()
# 在调用start之后打印
print(threading.enumerate())
if __name__ == "__main__":
main()
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def test1():
for i in range(5):
print("----test1---%d---" % i)
time.sleep(1)
# 如果创建Thread时执行的函数,运行结束那么意味着 这个子线程结束了....
def test2():
for i in range(10):
print("----test2---%d---" % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
while True:
if len(threading.enumerate()) == 1:
break;
print(threading.enumerate())
time.sleep(1)
if __name__ == "__main__":
main()
import threading
import time
# 通过继承 Thread 类完成创建线程
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm"+self.name+'@'+str(i) # name属性中保存的是当前线程的名字
print(msg)
self.login()
def login(self):
print("---login---")
if __name__ == '__main__':
t = MyThread()
t.start()
- 多线程共享全局变量
在一个函数中对全局变量进行修改的时候,到底是否需要使用global进行说明。
要看是否对全局变量的执行指向进行了修改,
如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用 global
如果,仅仅是修改了指向空间中的数据,此时不用必须使用global
import threading
import time
g_num = 100
def test1():
global g_num
g_num += 1
print("---in test1 g_num=%d ---" % g_num)
def test2():
print("---in test2 g_num=%d ---" % g_num)
def test3(temp):
temp.append(33)
print("---in test3 g_num=%s ---" % str(temp))
g_nums = [11, 22]
def main():
# target 指定将来 这个线程去那个函数执行代码
# args 指定将来调用函数的时候传递什么数据过去
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t3 = threading.Thread(target=test3, args=(g_nums,)) # 元组
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
t3.start()
print("---in main thread g_num=%d ---" % g_num)
if __name__ == "__main__":
main()
- 创建线程时指定传递的参数、多线程共享全局变量的问题
- 资源竞争
import threading
import time
# 假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
# 但是由于是多线程同时操作,有可能出现下面情况:
# 1、在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
# 2、然后t2对得到的值进行加1并赋给g_num,使得g_num=1
# 3、然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
# 4、这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("---in test1 g_num=%d ---" % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print("---in test2 g_num=%d ---" % g_num)
def main():
"""开启两个线程,修改同一个全局变量"""
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(5)
print("---in thread g_num=%d ---" % g_num)
if __name__ == "__main__":
main()
-
同步概念、互斥锁解决资源竞争的问题
上锁的代码越少越好-
同步概念
同步就是协同步调,按预定的先后次序进行运行,如:你说完,我再说。 ‘同’字从字面上容易理解为一起动作
其实不是,‘同’字应是指协同,协助,互相配合。 -
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
类似于数据库操作的事务。
互斥锁为资源引入一大状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;
直到该线程释放资源,将资源的状态变成”非锁定“,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
基本操作:
创建锁
mutex =threading.lock()
锁定
mutex.acquire()
释放
mutex.release()
注意:
如果这个锁之前是没有上锁的,那么acquire不会堵塞。
如果在调用acquire对这个锁上锁之前,它已经被其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止 -
import threading
import time
g_num = 0
# 创建一个互斥锁,默认是没有上锁的(全局变量)
mutex = threading.Lock()
def test1(num):
global g_num
# 上锁,如果之前没有被上锁,那么此时上锁成功
# 如果上锁之前 已经被上锁了 那么此时会堵塞在这里,直到 这个锁被解开位置
mutex.acquire()
for i in range(num):
g_num += 1
# 解锁
mutex.release()
print("---in test1 g_num=%d ---" % g_num)
def test2(num):
global g_num
# 上锁
mutex.acquire()
for i in range(num):
g_num += 1
# 解锁
mutex.release()
print("---in test2 g_num=%d ---" % g_num)
def main():
"""开启两个线程,修改同一个全局变量"""
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(1)
print("---in thread g_num=%d ---" % g_num)
if __name__ == "__main__":
main()
- 死锁、银行家算法
- 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,
就会照成死锁
尽管死锁很好发生,但是一旦发生就会照成程序停止响应。
解决方案:- 程序设计时要尽量避免(银行家算法)
- 添加超时时间
- 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,
- 多线程版udp聊天器
可以创建两个不同的文件名,绑定不同的端口号,运行两个文件
import socket
import threading
import time
def recv_msg(udp_socket):
"""接收数据并显示"""
while True:
recv_data = udp_socket.recvfrom(1024)
print(recv_data)
def send_msg(udp_socket, dest_ip, dest_port):
"""发送数据"""
while True:
send_data = input("请输入要发送的数据:")
udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
def main():
"""完成udp聊天的整体控制"""
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(("",7890))
dest_ip = input("请输入对方的ip:")
dest_port = int(input("请输入对方的port:"))
t_recv = threading.Thread(target=recv_msg, args=(udp_socket,))
t_send = threading.Thread(target=send_msg, args=(udp_socket, dest_ip, dest_port))
t_recv.start()
t_send.start()
if __name__ == "__main__":
main()
- 总结
2-2 进程
-
进程、程序 的概念
进程:进程资源分配;运行起来的程序是进程
程序:没有运行的程序就是一堆的代码;是静态的进程的状态:
新建 ---启动--> 就绪 <--调度--> 运行 --结束--> 死亡 - - - - - - 等待
-
使用 Process 完成多进程
multiprocessing -
进程、线程的区别
进程,能够完成多任务;资源分配的单位
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而大大的提高了程序的运行效率
线程,也能够完成多任务;程序调度的单位
线程不能独立运行,必须依存在进程中
可以将进程理解为工厂中的一条流水线,而其中的线程就是流水线上的工人网易云音乐 进程 天天静听 进程 线程:下载音乐 线程:下载音乐 线程:播放歌曲 线程:播放歌曲 -
通过队列完成进程间通信
进程间通信 - Queue(队列)/ 栈- 队列先进先出,栈先进后出
队列解耦
- 队列先进先出,栈先进后出
import multiprocessing
def download_from_web(q):
"""下载数据"""
# 模拟网上下载数据
data = [11, 22, 33, 44]
# 像队列写入数据
for temp in data:
q.put(temp)
print("---下载器已经下载完了数据并且存入到队列中---")
def analysis_data(q):
"""数据处理"""
waitting_analysis_data = list()
# 从队列中获取数据
while True:
data = q.get()
waitting_analysis_data.append(data)
if q.empty():
break
# 模拟数据处理
print(waitting_analysis_data)
def main():
# 创建一个队列
q = multiprocessing.Queue()
# 创建多个进程,将队列的引用当作实参传递到里面
p1 = multiprocessing.Process(target=download_from_web, args=(q,))
p2 = multiprocessing.Process(target=analysis_data, args=(q,))
p1.start()
p2.start()
if __name__ == "__main__":
main()
- 进程池概述
当需要创建的子进程数量不多是,可以直接利用multiprocessing中的Process动态生成多个进程,
但是如果是上百个甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以使用multiprocessing
模块提供的Pool方法。
初始化Pool时,可以指定一个最大的进程数,当有新的请求提交到Pool中时,如果池子还没有满,
那么就会创建一个新的进程来执行该请求,但是如果池子中的进程数已经达到指定的最大值,那么该
请求会等待,直刀池中有进程结束,才会用之前的进程执行新的任务, - 进程池的创建 官网案例
from multiprocessing import Pool import os,time,random def worker(msg): t_start = time.time() print("%s开始执行,进程号为%d" % (msg,os.getpid())) # random.random() 随机生成 0~1 之间的浮点数 time.sleep(random.random()*2) t_stop = time.time() print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start)) po = Pool(3) # 调用一个进程池,最大进程数是3 for i in range(10): # Pool().apply_async(要调用的目标,(传递给目标的参数元组,)) # 每次循环将会用空闲出来的子进程去调用目标 po.apply_async(worker,(i,)) print("---start---") po.close() # 关闭进程池,关闭后 po 不再接收新的请求 po.join() # 等待po中所有子进程执行完好成,必须放在close语句之后 print("---end---")
- 案例:多任务文件夹copy
#!/usr/bin/env python3
#coding=utf-8
import multiprocessing
import os
from multiprocessing import context
def copy_file(q, file_name, old_folder_name, new_folder_name):
"""完成文件的复制"""
# print("=====>模拟copy文件:从 %s ---> 到 %s 文件名是: %s" % (old_folder_name, new_folder_name, file_name))
old_f = open(old_folder_name + "/" + file_name, "rb")
context = old_f.read()
old_f.close()
new_f = open(new_folder_name + "/" + file_name, "wb")
new_f.write(context)
new_f.close()
# 如果拷贝完了文件,那么就像队列中写入一个消息表示已经完成
q.put(file_name)
def main():
# 获取用户要 copy 的文件夹的名字
old_folder_name = input("请输入要copy的文件夹的名字:")
# 创建一个新的文件夹
try:
new_folder_name = old_folder_name + "[复件]"
os.mkdir(new_folder_name)
except:
pass
# 获取文件夹的所有的待 copy 的文件名字 listdir()
file_names = os.listdir(old_folder_name)
# 创建进程池
po = multiprocessing.Pool(5)
# 创建Queue
q = multiprocessing.Manager().Queue()
# 向进程池中添加 copy 文件的任务
for file_name in file_names:
po.apply_async(copy_file, args = (q, file_name, old_folder_name, new_folder_name))
# 复制源文件夹中的文件,到新文件夹中的文件去
po.close()
# po.join()
# 获取所有文件的个数
all_file_num = len(file_names)
copy_ok_num = 0
# 显示进度条
while True:
file_name = q.get()
copy_ok_num+=1
print("已经完成copy: %s " % file_name)
print("\r拷贝的进度为: %.2f %%" % (copy_ok_num*100/all_file_num),end="")
if copy_ok_num >= all_file_num:
break
print()
if __name__ == "__main__":
main()
2-3 协程
- 迭代器:存储生成数据的方式,减少内存空间
- 判断某个类型是否迭代
from collections import Iterable
from collections import Iterable
isinstance([11,22,33],Iterable) - for temp in xxx_obj:
-
- 判断 xxx_obj 是否可以迭代
-
- 在第一步成立的前提下,调用 iter 函数,得到 xxx_obj 对象 iter 方法的返回值
-
- iter 方法的返回值 是一个迭代器
-
- 类中有 iter 和 next
-
- 如果一个程序需要很多的值?能实现一个数据,还不需要很多代码
- 迭代器应用
python2 中 range() 返回的是列表
xrange()返回的是对象
python3 中的 range() == xrange() - 斐波拉契数列(Fibonacci):数列中第一个数为0,第二个数为1,后面的数是前两个数的和
- 判断某个类型是否迭代
#!/usr/bin/env python3
#coding=utf-8
# 方法一:占用空间大
nums = list()
a = 0
b = 1
i=0
while i < 10:
nums.append(a)
a, b = b, a+b
i+=1
for num in nums:
print(num)
# 方法二:
class Fibonacci(object):
def __ini__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a+self.b
self.current_num += 1
return ret
else:
raise StopIteration
fibo = Fibonacci(10)
for num in fibo:
print(num)
- 迭代器使用的其他方式
-
- for
-
- list()
-
- tuple()
-
- 生成器:是一种特殊的迭代器
- 创建
- 方法1: nums = (x*2 for x in range(10)) 中括号[]换成小括号()
- 方法2: yield
# print 调试 def create_num(all_num): print("---1---") a, b = 0, 1 current_num = 0 while current_num < all_num: # print(a) print("---2---") yield a # 如果一个函数中有 yield 语句,那么这个就不再是函数,而是一个生成器模版 print("---3---") a,b = b, a+b current_num += 1 print("---4---") # 如果在调用create_num的时候,发现这个函数中有yield,那么此时,不是在调用函数,而是创建以一个生成器对象 obj = create_num(10) ret = next(obj) print(ret) ret = next(obj) print(ret) for num in obj: print(num)
- 核心
# 获取返回值 # print 调试 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: # print(a) yield a # 如果一个函数中有 yield 语句,那么这个就不再是函数,而是一个生成器模版 a,b = b, a+b current_num += 1 return "ok...." # 如果在调用create_num的时候,发现这个函数中有yield,那么此时,不是在调用函数,而是创建以一个生成器对象 obj2 = create_num(2) while True: try: ret = next(obj2) print("obj2:",ret) except Exception as e: print(ret.value) # 获取 ok...
- send
想用 send 不能在第一次用,next() 第一次用
# 用 send 启动 def create_num(all_num): a, b = 0, 1 current_num = 0 while current_num < all_num: ret = yield a print(">>>ret>>>", ret) a,b = b, a+b current_num += 1 obj = create_num(10) # obj.send(None) # send 一般不会放到第一次启动生成器,如果非要这么做,那么传递None ret = next(obj) print(ret) # send 里面的数据会传递给第5行,当作 yield a 的结果,然后 ret 保存这个结果... # send 的结果是下一次调用 yield时,yield后面的值 ret = obj.send("hahaha") print(ret)
- 创建
- 使用 yield 完成多任务:并发(假)
#!/usr/bin/env python3
#coding=utf-8
import time
def task_1():
while True:
print("---1---")
time.sleep(0.1)
yield
def task_2():
while True:
print("---2---")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
- 使用 greenlet、gevent完成多任务
gevent 用的比较多: pip3 install gevent (遇到延时操作就切换任务) gevent.sleep()
#!/usr/bin/env python3
#coding=utf-8
import time
from greenlet import greenlet
def task_1():
while True:
print("---1---")
gr2.switch()
time.sleep(0.1)
def task_2():
while True:
print("---2---")
gr1.switch()
time.sleep(0.1)
gr1 = greenlet(task_1)
gr2 = greenlet(task_2)
# 切换到 gr1 中运行
gr1.switch()
#!/usr/bin/env python3
#coding=utf-8
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
- 案例:图片下载器
#!/usr/bin/env python3
#coding=utf-8
import urllib.request
import gevent
from gevent import monkey
def downloader(img_name, img_url):
req = urllib.request.urlopen(img_url)
img_content = req.read()
with open(img_name, "wb") as f:
f.write(img_content)
def main():
gevent.joinall([
gevent.spawn(downloader, "1.jpg", "https://pics7.baidu.com/feed/0dd7912397dda144d609e24289982baa0df486f2.jpeg?token=ffa12e40a13e5459a0711150e18ca034"),
gevent.spawn(downloader, "2.jpg", "https://pics7.baidu.com/feed/0dd7912397dda144d609e24289982baa0df486f2.jpeg?token=ffa12e40a13e5459a0711150e18ca034")
])
if __name__ == "__main__":
main()
进程、线程、协程对比
- 进程是资源分配的单位
- 线程是操作系统的调度单位
- 进程切换需要的的资源最大,效率很低
- 线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下)
- 协程切换任务资源很小,效率高
- 多进程.多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发
3 Web服务器v3.1
3-1 正则表达式
- 简介
match 只匹配开头不匹配结尾;默认判断开头
字符 | 功能 |
---|---|
^ | 判断开头 |
$ | 判断结尾 |
() | 分组 |
竖线 | 匹配左右两边任意一个 |
import re
result = re.match(r"正则表达式",要匹配的字符串)
result.group()
- 匹配单个字符
字符 | 功能 |
---|---|
. | 匹配任意一个字符(除了\n) |
[] | 匹配[]中列举的字符 |
\d | 匹配数字,即0-9 |
\D | 匹配非数字,即不是数字 |
\s | 匹配空白,即空格,tab键 |
\S | 匹配非空白 |
\w | 匹配单词字符,即a-z、A-Z、0-9、_ |
\W | 匹配非单词字符 |
- 匹配多个字符
字符 | 功能 |
---|---|
* | 匹配前一个字符出现0次或者无限次,即可有可无 |
+ | 匹配前一个字符出现1次或者无限次,即至少一次 |
? | 匹配前一个字符出现0次或1次,要么有一次,要么没有 |
{m} | 匹配前一个字符出现m次 |
{m,n} | 匹配前一个字符出现m到n次 |
-
简单判断email、转译 \
email = input(“请输入一个邮箱地址:”)
re.match(r"[a-zA-Z0-9]{4,20}@(163|126).com$",email) -
分组
字符 | 功能 |
---|---|
竖线 | 匹配左右两边任意一个表达式 |
(ab) | 分组 |
\num | 引用分组num匹配到的字符串 |
(?P) | 分组起别名 |
(?P=name) | 引用别名为name 分组匹配到的字符串 |
案例:
html_str = "<body><h1>hshshsh</h1></body>"
re.match(r"<(\w*)><(\w*)>.*<(/\2)></\1>",html_str).group() # \1 匹配 (\w*) ,尖括号对应标签
re.match(r"<(?P<p1>\w*)><(?P<p2>\w*)>.*<(/?P=p1)></?P=p2>",html_str).group() # \1 匹配 (\w*) ,尖括号对应标签
- re 的高级用法:search、sub 等
-
match : 从头匹配
-
re.search(pattern, string, flags=0) :方法扫描整个字符串,并返回第一个成功的匹配。如果匹配失败,则返回None。
import re
ret = re.search(r"\d+",“阅读数为:999”)
re.group() -
findall :查找所有匹配项目,返回一个列表,不用 group
-
sub(正则,替换值|函数, 字符串范围) : 查找替换
import re
ret = re.sub(r"\d+",“998”, “python = 999”)返回:python = 998
-
re.split() : 切割功能
案例>>> import re >>> line = 'aaa bbb ccc;ddd eee,fff' >>> line 'aaa bbb ccc;ddd eee,fff' # 单字符切割 >>>> re.split(r';',line) ['aaa bbb ccc', 'ddd\teee,fff'] # 两个字符以上切割需要放在 [ ] 中 >>> re.split(r'[;,]',line) ['aaa bbb ccc', 'ddd\teee', 'fff'] # 所有空白字符切割 >>> re.split(r'[;,\s]',line) ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'] # 使用括号捕获分组,默认保留分割符 >>> re.split(r'([;])',line) ['aaa bbb ccc', ';', 'ddd\teee,fff'] # 不想保留分隔符,以(?:...)的形式指定 >>> re.split(r'(?:[;])',line) ['aaa bbb ccc', 'ddd\teee,fff']
-
3-2 http协议
- 协议就是一种规范,一种约定
- 阿里妈妈: 投放广告使用
- 1688
3-3 简单的web服务器实现
- 案例一:返回固定页面的http服务器
import socket
def service_client(new_socket):
"""为这个客户端返回数据"""
# 接收浏览器的数据
request = new_socket.recv(1024)
print(request)
# 返回数据给浏览器
response = "HTTP/1.1 200 OK \r\n"
response += "\r\n"
# 发送给浏览器数据 body
response += "haha"
new_socket.send(response.encode("utf-8"))
new_socket.close()
def main():
"""用来完成整体的控制"""
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置当服务器先 close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定
tcp_server_socket.bind(("",7890))
# 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 等待客户端链接
new_socket, client_addr = tcp_server_socket.accept()
# 为客户端服务
service_client(new_socket)
tcp_server_socket.close()
if __name__ == "__main__":
main()
- tcp 3次握手,4次挥手
-
3 次握手:conect
过程:双方都在准备资源
C: sync
S: ack+sync
C: ack -
4 次挥手:通常客户端主动断开 close 全双工通信(收发)
过程:双发都在释放资源
C:关闭发送
S:关闭接收
S:关闭发送
C:关闭接收谁先调 close() 谁就先等待,等待时间是:
“超时时间 2msl” 「一个数据包在网上最慢的传输时间是 1~2min 」
-
- 案例二:返回特定页面的http服务
import socket
import re
# 返回特定页面的 http 服务
def service_client(new_socket):
"""为这个客户端返回数据"""
# 接收浏览器的数据
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.spiltlines() # splitlines() 按照行('\r', '\r\n', \n')分隔,返回一个包含各行作为元素的列表
# 返回数据给浏览器
file_name = ""
ret = re.match(r"[^/]+(/[^]*)", request_lines[0])
if ret:
file_name = ret.group(1)
if file_name == "/"
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND \r\n"
response += "\r\n"
response += "------file not found------"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 准备发送给浏览器的数据 --- header
response = "HTTP/1.1 200 OK \r\n"
response += "\r\n"
# 准备发送给浏览器的数据 --- body
new_socket.send(response.encode("utf-8"))
new_socket.send(html_content)
# 关闭套接字
new_socket.close()
def main():
"""用来完成整体的控制"""
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置当服务器先 close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定
tcp_server_socket.bind(("",7890))
# 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 等待客户端链接
new_socket, client_addr = tcp_server_socket.accept()
# 为客户端服务
service_client(new_socket)
tcp_server_socket.close()
if __name__ == "__main__":
main()
3-4 并发web服务器实现
multiprocessing
- 多进程、多线程实现 http 服务
- gevent 实现 http 服务器
- (重要)单进程、线程、非堵塞实现并发的原理
tcp_server_socket.setblocking(False)
- 单进程、线程、非堵塞实现并发的验证-1.flv
# 单进程、线程、非堵塞实现并发的验证
import socket
import time
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.bind(("",7890))
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 设置套接字为非堵塞的方式
client_socket_list = list()
while True:
time.sleep(0.5)
try:
new_socket, new_addr = tcp_server_socket.accept()
except Exception as ret:
print("---没有新的客户端到来---")
else:
print("---只要没有产生异常,那么也就意味着 来了一个新的客户端---")
new_socket.setblocking(False)
client_socket_list.append(new_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024)
except Exception as ret:
print(ret) # debug 思想
print("---这个客户端没有发送过来数据---")
else:
if recv_data:
# 对方发送过来数据
print("---客户端发送过来了数据---")
else:
# 对方调用 close 导致了 recv 返回异常
client_socket_list.remove(client_socket)
client_socket.close()
print("---客户端已经关闭---")
- 培养 debug 思想
- 长链接、短链接
-
- 短链接:HTTP/1.0
操作步骤:建立链接–数据传输–关闭链接___建立链接–数据传输–关闭链接
- 短链接:HTTP/1.0
-
- 长链接:HTTP/1.1
操作步骤:建立链接–数据传输___(保持链接)___数据传输–关闭链接
- 长链接:HTTP/1.1
-
- 区别
长链接节省服务器资源
- 区别
-
- 单进程、线程、非堵塞、长连接的http服务器
- Content-length:在长链接中起到关键的作用,告诉浏览器 body 究竟有多长
import socket
import re
# 单进程、线程、非堵塞、长连接的http服务器
def service_client(new_socket, request):
"""为这个客户端返回数据"""
# 接收浏览器的数据
# request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.spiltlines() # splitlines() 按照行('\r', '\r\n', \n')分隔,返回一个包含各行作为元素的列表
# 返回数据给浏览器
file_name = ""
ret = re.match(r"[^/]+(/[^]*)", request_lines[0])
if ret:
file_name = ret.group(1)
if file_name == "/"
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND \r\n"
response += "\r\n"
response += "------file not found------"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 准备发送给浏览器的数据 --- header/body
response_body = html_content
response_header = "HTTP/1.1 200 OK \r\n"
response_header += "Content-length: %d \r\n" % len(response_body)
response_header += "\r\n"
response = response_header.encode("utf-8") + response_body
new_socket.send(response)
def main():
"""用来完成整体的控制"""
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置当服务器先 close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定
tcp_server_socket.bind(("",7890))
# 变为监听套接字
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 将套接字变为非堵塞
client_socket_list = list()
while True:
# 等待客户端链接
try:
new_socket, client_addr = tcp_server_socket.accept()
except Exception as ret:
pass
else:
new_socket.setblocking(False)
client_socket_list.append(new_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024).decode("utf-8")
except Exception:
pass
else:
if recv_data:
service_client(client_socket, recv_data)
else:
client_socket.close()
client_socket_list.remove(client_socket)
tcp_server_socket.close()
if __name__ == "__main__":
main()
-(重要)epoll的原理过程讲解
应用程序:nginx/apache epoll
特点: - 1. 内存映射技术(mmap):特殊内存,共享内存(操作系统内存+应用内存),不需要复制
- 2. 采用事件的就绪通知方式:不是轮循,是事件通知
- epoll版的http服务器
import socket
import re
import select
""" epoll版的http服务器 """
def service_client(new_socket, request):
"""为这个客户端返回数据"""
# 接收浏览器的数据
# request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.spiltlines() # splitlines() 按照行('\r', '\r\n', \n')分隔,返回一个包含各行作为元素的列表
# 返回数据给浏览器
file_name = ""
ret = re.match(r"[^/]+(/[^]*)", request_lines[0])
if ret:
file_name = ret.group(1)
if file_name == "/"
file_name = "/index.html"
# 返回 http 格式的数据给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND \r\n"
response += "\r\n"
response += "------file not found------"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 准备发送给浏览器的数据 --- header/body
response_body = html_content
response_header = "HTTP/1.1 200 OK \r\n"
response_header += "Content-length: %d \r\n" % len(response_body)
response_header += "\r\n"
response = response_header.encode("utf-8") + response_body
new_socket.send(response)
def main():
"""用来完成整体的控制"""
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置当服务器先 close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定
tcp_server_socket.bind(("",7890))
# 变为监听套接字
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 将套接字变为非堵塞
# 创建一个 epoll 对象
epl = select.epoll()
# 将监听套接字对应的fd注册到epoll中:
# select.EPOLLIN(监测输入)
#
epl.register(tcp_server_socket.fileno(), select.EPOLLIN) #|SELECT.EPOLLET)
# 字典
fd_event_dict = dict()
while True:
fd_event_list = epl.pool() # 默认会堵塞,直到 os 检测到数据到来,通过事件通知方式 告诉这个程序,此时才会解堵塞
# [(fd,event),(套接字对应的文件描述符,这个文件描述符到底是什么事件 例如:可以调用 recv接收等)]
for fd,event in fd_event_list:
if fd == tcp_server_socket.fileno()
# 等待客户端链接
new_socket, client_addr = tcp_server_socket.accept()
epl.register(new_socket.fileno(), select.EPOLLIN)
fd_event_dict[new_socket.fileno()] = new_socket
elif event == select.EPOLLIN:
# 判断已经链接的客户端是否有数据发过来
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
if recv_data:
service_client(fd_event_dict[fd], recv_data)
else:
fd_event_dict[fd].close()
epl.unregister(fd) # 注销 epoll
del fd_event_dict[fd] # 字典的移除
tcp_server_socket.close()
if __name__ == "__main__":
main()
3-5 网络通信
- tcp-ip协议
^
|应用层:应用进程 应用进程 应用进程
|传输层:TCP UDP
|网络层:IP(原始套接字) ICMP(ping,IP,应用进程) IGMP(IP) ARP(网络接口) RARP((网络接口))
|链路层:网络接口
---网卡---网线
---- TCP/IP协议族中个协议之间的关系 ----
说明:
网际层也称为:网络层
网络接口层也称为:链路层
发送放包越来越大,接收方解包越来越小
- OSI 另外一套标准
应用层、表示层、会话层
传输层、
网络层、
数据链路层、物理层
- wireshark抓包工具
- 2台电脑通信、网络掩码
- 1.2台电脑的网络
192.168.1.1 ------ 192.168.1.2说明:1.如果两台电脑之间通过网线连接是可以直接通信的,但是需要提前设置好ip地址以及网络掩码;
2.并且IP地址需要控制在同一个网段内,例如一台为192.168.1.1,另外一台为192.168.1.2则可以通信。
网络掩码与IP 按位与操作:都为1则是1,有一个为0,则为0
- 1.2台电脑的网络
- 集线器、交换器组网、arp获取mac地址等
- 网线可以剪开,但是它传播的是电信号不是电流
- 使用集线器组成一个网络(广播)
1.当有多台电脑需要组成一个网时,那么可以通过集线器(Hub)将其链接在一起;
2.一般情况下集线器的接口较少;
3.集线器有个缺点,它是以广播的方式进行发送任何数据,即如果集线器接收到来自A电脑的数据本来是想转发给B电脑,如果此时它连接着另外两台电脑C,D,那么它会把数据给每个电脑都发一份,因此会导致网络拥堵。 - 使用交换机组成一个网络
1.克服了集线器以广播发送数据的缺点,当需要广播的时候发送广播,当需要单播的时候又能够以单播的方式进行发送;
2.它已经替代了之前的集线器;
3.企业中就是用交换机来完成多台电脑设备的链接成网络的 - arp 通过 IP 找到 Mac
arp -a
- arp 攻击(中间人攻击)
- 路由器链接多个网络、默认网关
路由器:链接两个以及以上的网为一个大网,使其可以通讯
默认网关:一般是路由器,电脑发送不同网络号;(不在同一网段,先发送给默认网关)
ip 不变,mac 地址变化 - 浏览器访问服务器的过程
# 较为复杂的通信过程,网络 www.baidu.com 1.解析域名 (dns服务器) 2.向tcp的服务器发送3次握手 3.发送http请求数据,等待服务器的应答 4.发送tcp的4次挥手
- ip不变、mac地址发生变化
域名服务器:由国家控制的!!!
4 Python 高级语法 V3.1
4-1 GIL锁(全局解释器锁)
htop | top
pass 是空语句的意思
- 问:描述GIL的概念,以及对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。
参考答案:
1.Python 语言和 GIL 并没有半毛钱关系,仅仅是由于历史原因在Cpyton虚拟机(解释器),难以移除GIL;
2.CIL 全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码;
3.线程释放GIL锁的情况:在IO操作等可能会引起阻塞的system call 之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python3.x使用计时器(执行时间到达阙值后,当前线程释放GIL)或 Python2.x tickets计数到达100;
4.Python使用多进程是可以李永多核的CPU资源的;
5.多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁。 - 适用类型程序
进程:计算密集型程序(无return,sleep)
线程/协程:IO密集型程序 (读写,网络)利用等待时间 - 解决方案:
- 换一个解释器
- 换一种语言
#include<stdio.h> # 编译型语言 void test() { print("--------test------"); } int main(int argc, char **argv) { print("hello world\n"); test(); return 0; } gcc test.c ./a.out
4-2 深拷贝和浅拷贝(继续深入查看练习)
- 案例
# 浅拷贝 >>> a = [11, 12] >>> b = a >>> a [11, 12] >>> b [11, 12] >>> id(a) 4310217344 >>> id(b) 4310217344 # 深拷贝 >>> import copy >>> c = copy.deepcopy(a) >>> id(a) 4310217344 >>> id(c) 4310274688 >>> a.append(33) >>> a [11, 12, 33] >>> c [11, 12] a = [11, 22] b = [33, 44] c = [a, b] d = c e = copy.copy(c) id(c) id(e) id(c[0]) id(e[0]) ==== - 当一个变量=xxx的时候,约定为 这个是指向了这个 xxx a = [11, 22] 完成浅拷贝 copy.copy 完成深拷贝 copy.deepcopy 特殊 : copy.copy 在拷贝元组的时候不会进行浅拷贝,仅仅是指向,原因:因为元组是不可变类型,那么意味着数据一定不能修改,因此用copy.copy的时候它会自动判断,如果是元组它就是指向了它 如果用copy.copy、copy.deepcopy 对一个全部都是不可变类型的数据进行拷贝,那么它们结果相同,都是引用指向 如果拷贝的是一个拥有不可变类型的数据,即使元组时最顶层,那么deepcopy依然是深拷贝,而copy.copy还是指向 # 切片: 浅拷贝 a = [11,22] b = [33,44] c = [a,b] d = c[:] # 字典:浅拷贝 d = dict(name="张三",age=27) co = d.copy() id(d) id(co) d = dict(name="张三",age=27, children_ages = [11, 22]) co = d.copy() d["children_ages"].append(9) d co
4-3 私有化、import、封装继承多态
-
私有化
- xx: 公有变量
- _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
- __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
- xx:双前后下划线,用户名字空间的魔法对象或属性。例如:init,__ 不要自己发明这样的名字
- xx_:单后置下划线,用于避免与Python关键词的冲突
通过name mangling(名字重整(目的就是以防子类意外重写基类的方法或者属性)如:_Class__object)机制就可以访问private了。
-
import
可以防止模块重新导入,
需要重新导入 from imp import reload (import aa 才能用) reload()- from xxx import yyy
- import xxx
- from xxx import *
- import xxx,yyy
-
多个模块import导入注意点
变量的使用
-
再议封装、继承、多态
4-4 方法解析顺序表MRO
- (重点)多继承中的MRO顺序
多继承:一个类可以有多个父类
重写:子类相同的方法覆盖父类 | 重载:不会有(不允许有相同的变量名)class Parent(object): def __init__(self,name): print('parent init 开始被调用') self.name= name print('parent init 结束被调用') class Son1(Parent): def __init__(self,name,age): print('Son1 init 开始被调用') self.age = age Parent.__init__(self, name) print('Son1 init 结束被调用') class Son2(Parent): def __init__(self,name,gender): print('Son2 init 开始被调用') self.gender = gender Parent.__init__(self, name) print('Son2 init 结束被调用') class Grandson(Son1, Son2): def __init__(self, name, age, gender): print('Grandson init 开始被调用') Son1.__init__(self, name, age) Son2.__init__(self, name, gender) print('Grandson init 结束被调用') gs = Grandson('grandson', 12, '男') print('姓名:',gs.name) print('年龄:',gs.age) print('性别:',gs.gender) ****** 多继承中 super 调用父类的所有方法 **** class Parent(object): def __init__(self,name): print('parent init 开始被调用') self.name= name print('parent init 结束被调用') class Son1(Parent): def __init__(self,name,age): print('Son1 init 开始被调用') self.age = age super().__init__(name) print('Son1 init 结束被调用') class Son2(Parent): def __init__(self,name,gender): print('Son2 init 开始被调用') self.gender = gender super().__init__(name) print('Son2 init 结束被调用') class Grandson(Son1, Son2): def __init__(self, name, age, gender): print('Grandson init 开始被调用') # 多继承时,相对于使用类名,__init__方法,要把每个父类全部写一遍 # 而super只用一句话,执行类全部父类的方法,这也是为何多继承需要全部传参的一个原因;可以指定父类的名字,super 调用下一个 # super(Grandson, self).__init__(name, age, gender) super().__init__(name, age, gender) print('Grandson init 结束被调用') print(Grandson.__mro__) ## 运行结果 :C3 算法实现的 ## xyhddeMac-mini-2:python zhangwei$ python3 test.py (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
- (重点)*args、**kwargs 的另外用处拆包
- 为了避免多继承报错,使用不定长参数
- 拆包:*拆分元组,**拆分字典
# args, kwargs def test2(a, b, *args, **kwargs): print(a) print(b) print(args) print(kwargs) def test(a, b, *args, **kwargs): print(a) print(b) print(args) print(kwargs) test2(a, b, args, kwargs) # 需要拆分字典和元组:相当于 test2(11,22,(33),{name="laowang",age=18}) test2(a, b, *args, kwargs) # 需要拆分元组:相当于 test2(11,22,33,{name="laowang",age=18}) test2(a, b, *args, **kwargs) # 需要拆分元组和字典:相当于 test2(11,22,33,name="laowang",age=18) test(11,22) test(11,22,33) test(11,22,33,name="laowang",age=18)
- 单继承中MRO、继承的面试题
- 单继承中的super
总结:
1 super().init 相对于类名.init,在单继承上用法基本无差
2 但在多继承上有区别,super 方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法执行多次,具体看上面案例中的输出结果
3 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
4 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
5 多继承时,相对于使用类名__init__方法,需要把每个父类全部写一遍,而使用 super 方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传餐的一个原因
4-5 类对象和实例对象访问属性的区别和property属性
- 类对象、实例对象、类方法、实例方法、类属性、实例属性、静态方法
- 类属性、实例属性
- 类属性在内存中只保留一份
- 实例属性在每个对象中都要保存一份
- [实例属性需要通过对象访问] [类属性通过类访问]
- new --> 创建对象,通俗点说:有个内存空间
- init --> 对刚刚申请的空间进行初始化
- 实例方法、静态方法和类方法
三种方法在内存中都归属于类,区别在于调用方式不同
- 实例方法:由对象调用;至少一个 self 参数;执行实例方法时,自动调用该方法的对象赋值给self;
- 类方法:由类调用;至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls;
- 静态方法:由类调用;无默认参数
# __class__指向类对象 # 类对象只能调用:类方法/静态方法 # 实例对象三种都能调用:实例方法/类方法/静态方法 class Foo(object): def __init__(self, name): self.name = name def ord_func(self): """实例方法,实例对象,至少一个self参数""" print("实例方法") @classmethod def class_fuc(cls): """类方法,类引用,至少一个cls参数""" print("类方法") @staticmethod def static_func(): """静态方法,无默认参数""" print("静态方法")
- 类属性、实例属性
- (重点)property属性
- 一种用起来像是使用的实例属性一样的特殊属性,可对应于某个方案
property属性定义和调用注意以下几点:
- 定义时候,在实例方法的基础上添加 @property 装饰器;并且仅有一个self 参数
- 调用时,无需括号
方法:foo_obj.func()
property属性:foo_obj.prop
class Goods: def func(self): pass @property def size(self): return 100 #### 调用 #### obj = Goods() ret = obj.size # 调用属性 print(ret)
- property属性的应用 起来更简单
- 创建property属性的方式
- 装饰器 python2.x 和 3.x 结果不同
## 经典类 ## @property def price(self): print('@property') ## 新式类 ## ### 获取 obj.xxx ### 设置 obj.xxx = xx ### 删除 del obj.xxx @property def price(self): print('@property') @price.setter def price(self, value): print('@price.setter') @price.deleter def price(self): print('@price.deleter')
- 类属性
property 有四个参数:property(get_bar, set_bar, del_bar, "“description …”)
class Goods(object): def __init__(self): self.original_price = 100 self.discount = 0.8 def get_price(self): new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self): del self.original_price PRICE = property(get_bar, set_price, del_price, "价格属性描述...") ### 调用 ### obj = Goods() result = obj.PRICE print(return)
- property属性的应用2
使用 property 取代 getter和setter方法
4-6 私有属性和名字重整、魔法属性和方法、上下文管理器
- 修改、查看私有属性、名字重整
定义类的时候,私有属性名称是被修改了!__dict__
- 魔法属性、方法
__doc__
:表示类的描述信息;“”“这是一段描述”“”__module__|__class__
:当前操作的对象在哪个模块 | 当前操作的对象的类是哪个__init__
:初始化方法,通过类创建对象时,自动触发__del__
:__call__
:
注意:__init__
方法的执行是由创建对象触发的,即:对象 = 类名();而对于__call__
方法的执行是由对象后加括号触发的,即:对象() 或 类()()__dict__
:类或对象中所有的属性__str__
:如果一个类定义了该方法,那么打印时默认输出该方法的返回值__getitem__|__setitem__|__delitem__
:用于索引操作,如字典,分别表示获取、设置删除数据__getslice__|__setslice__|__delslice__
:该方法用于分片操作,如:列表- 等等 …
- 面向对象设计
- 继承:是基于Python中的属性查找
- 多态:在X.method 方法中,method 的意义取决于X的类型
- 封装:方法和运算符实现行为,数据隐藏默认是一种惯例
- (重点)with、上下文管理器
- with:with表达式其实是try-finally的简写形式。但是又不是全相同。
- 1、文件操作。2、进程线程之间互斥对象。3、支持上下文其他对象
- 什么是上下文 context?
- 任何实现了 enter() 和 exit() 方法的对象都可以称之为上下文管理器,上下文管理器 with 关键字,显然, 文件 file 也实现了上下文
""" with 语句实质是上下文管理。 1、上下文管理协议。包含方法__enter__() 和 __exit__(),支持该协议对象要实现这两个方法。 2、上下文管理器,定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。 3、进入上下文的时候执行__enter__方法,如果设置as var语句,var变量接受__enter__()方法返回值。 4、如果运行时发生了异常,就退出上下文管理器。调用管理器__exit__方法。 """ ## 案例 ## class Mycontex(object): def __init__(self,name): self.name=name def __enter__(self): print("进入enter") return self def do_self(self): print(self.name) def __exit__(self,exc_type,exc_value,traceback): print("退出exit") print(exc_type,exc_value) if __name__ == '__main__': with Mycontex('test') as mc: mc.do_self()
- with:with表达式其实是try-finally的简写形式。但是又不是全相同。
vim | 快捷键 |
---|---|
撤销 | u |
反撤销 | ctrl+r |
选中 | 大V |
行尾 | A |
打开文件定位到指定的行 | vim 文件名 +37 |
全文修改 | :%s/修改/改成/g |