python 学习

python 学习

开发常用的网站

  • 博客园
  • csdn
  • 51cto
  • 开源中国
  • github
  • 知乎
  • 简书

1 网络编程

1-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 用于回路测试

  1. Linux、Windows查看网卡信息
  • Linux
    • sudo ifconfig ens40 up/down
  • Windows
    • ipconfig
      lo:本地网卡
      ens40:外网网卡
  1. 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用

类型网络号网络号
A0*******..********
B10******.********.
C110*****..********
  • ipv6
    fe80::4a3:4ee2:d0cd:f011
  1. (重点)端口
  • 给哪个进程? port
  • 端口是通过端口号标记的,端口号是标记一个程序的
  1. 端口分类:知名端口、动态端口
    端口号不是随意使用的,而是按照一定的规定进行分配的。
  • 知名端口:0~1023
  • 动态端口:1024~65535
  1. 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.关闭套接字
  • (重点)端口绑定的问题
    1. 同一个端口不允许同一时刻,被用两次
    2. 发送方可以不绑定端口,接收方必须先绑定端口
  • 输入对方 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: 打电话的模型,比较安全;比较稳定; 采用发送应答机制
对比

UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
适用场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输
udptcp
客户端流程socket\bind\sendto/recvfrom\closesocket\connect\send/recv\close
服务端流程socket\bind\listen\accept\recv/send\close

tcp 总结

  1. tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
  2. tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
  3. tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
  4. 当客户端需要链接服务器时,就需要使用connect进行连接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
  5. 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
  6. listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
  7. 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信
  8. 关闭accept返回的套接字意味着这个客户端已经服务完毕
  9. 当客户端的套接字调用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 线程

  1. 线程的运行是没有先后顺序的
  2. 查看程序中所有线程数量:threading.enumerate()
  3. 当调用Thread的时候,不会创建线程;调用start才会运行子线程
  4. 延时的话可以保证先运行哪个程序,再运行哪个程序

线程创建的两种方法:

  • 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()

  • 创建线程时指定传递的参数、多线程共享全局变量的问题
    1. 资源竞争
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()

  • 死锁、银行家算法
    • 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,
      就会照成死锁
      尽管死锁很好发生,但是一旦发生就会照成程序停止响应。
      解决方案:
      1. 程序设计时要尽量避免(银行家算法)
      2. 添加超时时间
  • 多线程版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:
        1. 判断 xxx_obj 是否可以迭代
        1. 在第一步成立的前提下,调用 iter 函数,得到 xxx_obj 对象 iter 方法的返回值
        1. iter 方法的返回值 是一个迭代器
        1. 类中有 iternext
    • 如果一个程序需要很多的值?能实现一个数据,还不需要很多代码
    • 迭代器应用
      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)
  • 迭代器使用的其他方式
      1. for
      1. list()
      1. 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()

进程、线程、协程对比

  1. 进程是资源分配的单位
  2. 线程是操作系统的调度单位
  3. 进程切换需要的的资源最大,效率很低
  4. 线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下)
  5. 协程切换任务资源很小,效率高
  6. 多进程.多线程根据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 思想
  • 长链接、短链接
      1. 短链接:HTTP/1.0
        操作步骤:建立链接–数据传输–关闭链接___建立链接–数据传输–关闭链接
      1. 长链接:HTTP/1.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

  • 集线器、交换器组网、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密集型程序 (读写,网络)利用等待时间
  • 解决方案:
    1. 换一个解释器
    2. 换一种语言
    #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属性

  • 类对象、实例对象、类方法、实例方法、类属性、实例属性、静态方法
    • 类属性、实例属性
      • 类属性在内存中只保留一份
      • 实例属性在每个对象中都要保存一份
      • [实例属性需要通过对象访问] [类属性通过类访问]
      1. new --> 创建对象,通俗点说:有个内存空间
      2. 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属性定义和调用注意以下几点:

    1. 定义时候,在实例方法的基础上添加 @property 装饰器;并且仅有一个self 参数
    2. 调用时,无需括号
      方法: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()
      
    

vim快捷键
撤销u
反撤销ctrl+r
选中大V
行尾A
打开文件定位到指定的行vim 文件名 +37
全文修改:%s/修改/改成/g

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值