python语法基础-并发编程-进程-长期维护

Posted 技术改变命运Andy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python语法基础-并发编程-进程-长期维护相关的知识,希望对你有一定的参考价值。

###############    进程的启动方式1    ##############

"""
并发编程:

进程
1,运行中的程序,就是进程,程序是没有生命的实体,运行起来了就有生命了,
操作系统可以管理进程,进程是操作系统基本的执行单元,
2,每一个进程都有它自己的地址空间,进程之间是不会混的,比如qq不能访问微信的地址空间,
操作系统替你隔离开了,这也是操作系统引入进程这个概念的原因,

#######################################
进程的调度
1,先来先服务,有一个不好的,就是不利于短作业
2,短作业优先算法,但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
3,时间片轮转算法,就是轮流执行,已经很科学了,
4,多级反馈队列算法,有多个队列,有一个新任务来了放入第一个队列,这是优先级加上时间片轮转,第二个任务来了放入下一级,

#######################################
并发和并行:
进程的并行:这种只有在多核cpu才可以实现,
进程的并发:这是轮流执行,由于速度很快,看起来像是一起执行的,比如一遍听音乐,一遍写代码,

######################################
进程的三状态转换图:非常重要
1,进程一开始运行的时候,是就绪的状态,这是第一个状态,就是告诉cpu,我已经准备好可以运行了,进入排队了,
2,时间片轮转,轮到你了之后,你就运行了,这是第二个状态,
3,发生阻塞,这是第三个状态,比如你的程序让你输入内容,input方法, 这时候是阻塞的,你输入完毕了之后,就又畅通了,
这是等待I/O完成,input,sleep,文件的输入和输出,
事件处理之后,你还要进入就绪状态了,
全部处理完了,就结束了,

###########################################
同步和异步
1,同步,需要等待,需要排队,你什么也不能干,
2,异步,不需要等待,你可以去做其他事情,

###########################################
阻塞和非阻塞
1,阻塞,就是input,sleep这些,需要等待,这是阻塞,
2,非阻塞,就是跳过这些阻塞,但是程序中不可避免的需要阻塞,因为需要等待内容处理,

###########################################
同步异步和阻塞非阻塞:
同步阻塞,就是
同步非阻塞
异步阻塞
异步非阻塞,效率更高,

###############################################
多进程有一个内置的模块:
from multiprocessing import Process
要学习几个地方:
1,进程的注册
2,进程的开启
3,进程的异步,
4,进程的同步,join,
5,进程注册函数的参数,
6,启动多个子进程,



"""

import time
from multiprocessing import Process
import os

def f(args1,args2):
    print("*"*args1)
    time.sleep(1)
    print("*"*args2)

    # print("子进程号",os.getpid())
    # print("子进程的父进程号",os.getppid())
    # print(‘我是子进程‘)


if __name__ == ‘__main__‘:
    # p = Process(target=f, args=(‘bob‘,))
    # p = Process(target=f, args=(‘bob‘,"123"))  # 注册,p是一个进程,还没有启动,这是主进程,
    # args=(‘bob‘,),如果注册的函数是有参数的,就要传递参数,如果有一个参数括号内要有一个逗号,因为这是一个元组,
    # p.start()  # 启动进程,这是启动了一个子进程,
    # 现在子进程和主进程之间是异步的,如果我想在子进程结束之后再执行下面的代码,变成同步,怎么办?
    # p.join()  # 这个join就是在感知一个子进程的一个结束,将异步改成同步,
    # time.sleep(1)
    # print("父进程号",os.getpid())
    # print("父进程的父进程号",os.getppid())  # 这个就是pycharm的进程号,
    # print(‘执行主进程的内容了‘)  # 这一句的执行和子进程的执行内容是异步的,不是同步的,

    # 开启10个子进程
    p_list=[]
    for i in range(10):
        p = Process(target=f, args=(10*i, 20*i))
        p_list.append(p)
        p.start()
        # print("第%d轮"%(i+1))
    # p.join()
    [p.join() for p in p_list ]  # 保证前面的10个进程全部结束了,才会执行下面的代码,
    print("运行完了")
    # 这种开启了多进程,可以读多个进程去存文件,取文件内容,

# 进程的生命周期,
# 主进程没有开启子进程,就是执行完他的代码就结束了了
# 子进程也是执行完自己的代码就结束了,
# 开启了子进程的主进程,主进程执行完了,要等待子进程结束之后,主进程才可以结束,

 

###############    进程的启动方式2       和       进程之间是数据隔离的    ##############

# 进程的启动方式2
# 第一点,创建一个类,继承process
# 第二点,类中必须实现run方法,这个run方法里面就是子进程要执行的内容,



import os
from multiprocessing import Process


class MyProcess(Process):  # 继承导入的process,
    def __init__(self,name):  # 为了进程能传递参数,
        super().__init__()  # 这是继承了父类所有的参数,
        self.name=name
    def run(self):
        # print(os.getpid())
        print("子进程号",self.pid)
        print("参数",self.name)  # print(os.getpid()) 这两句是一样的,


if __name__ == ‘__main__‘:
    # p1=MyProcess()  # 这是不传参数的
    p1=MyProcess("name1")  # 这是传参数的,这就是面向对象的实例化,
    p2=MyProcess(‘name2‘)
    p3=MyProcess(‘name3‘)

    p1.start() #start会自动调用run
    p2.start()
    # p2.run()
    p3.start()
    # 三个进程之间是异步的,

    p1.join()
    p2.join()
    p3.join()
    # 三个进程都结束了才会执行下面的内容,这是把异步,变成异步,
    print(‘主线程‘)


# 进程之间的数据隔离问题
# 进程和进程之间的数据是否是隔离的,比如qq和微信,之间的数据是隔离的,
# 几个进程之间,如果不通过特殊的手段,是不可能共享一个数据的,这个记住,没有什么可理解的,
# 下面是一个例子,证明主进程和子进程之间是没有

from multiprocessing import Process

def work():
    global n  # 声明了一个全局变量,
    n=0
    print(‘子进程内: ‘,n)


if __name__ == ‘__main__‘:
    n = 100
    p=Process(target=work)
    p.start()
    print(‘主进程内: ‘,n)

 

###############   守护进程  ##############

# 守护进程
# 子进程----守护进程


# 第一版:主进程结束了,子进程还没有结束,
# import time
# from multiprocessing import Process
#
# def func():
#     while True:
#         time.sleep(1)
#         print("我还活着")
#
#
# if __name__ == ‘__main__‘:
#     p=Process(target=func)
#     p.start()
#     i = 0
#     while i<10:
#         time.sleep(1)
#         i+=1
#     print("主进程结束")


# 守护进程,就是主进程代码结束了而结束,记住不是主进程彻底结束,而是代码结束,
import time
from multiprocessing import Process

def func():
    while True:
        time.sleep(1)
        print("我还活着")


if __name__ == ‘__main__‘:
    p=Process(target=func)
    p.daemon=True  # 设置子进程为守护进程,
    p.start()
    p2=Process(target=func)  # 这个子进程没有设置为守护进程所以这个进程还在进行中,
    p2.start()
    # time.sleep(3)
    # p2.is_alive()  # 判断一个进程是否活着
    # p2.terminate()  # 结束一个进程,
    i = 0
    while i<5:
        time.sleep(1)
        i+=1
    print("主进程代码结束")

 

###############   进程锁   ##############

# 进程锁
# 买票就是一个并发的过程,
# 文件db的内容为:{"ticket":1}
# 注意一定要用双引号,不然json无法识别
# 并发运行,效率高,但竞争写同一文件,数据写入错乱




# 买票不加锁,可能会有多个人买到票了。但是票只有一张,
# from multiprocessing import Process,Lock
# import time,json,random
#
# # 查询余票的函数
# def show(i):
#     with open("db") as f :
#         dic=json.load(f)
#     print(‘余票%s‘%dic[‘ticket‘])
#
# # 买票的函数
# def bug_ticket(i):
#     with open("db") as f :
#         dic=json.load(f)
#         time.sleep(0.1)  # 模拟读数据的网络延迟
#     if dic[‘ticket‘] >0:
#         dic[‘ticket‘]-=1
#         print(‘33[32m%s购票成功33[0m‘%i)
#     else:
#         print(‘33[31m%s没买到票33[0m‘%i)
#     time.sleep(0.1)
#     with open("db","w") as f:
#         json.dump(dic,f)
#
#
# if __name__ == ‘__main__‘:
#     for i in range(10):  # 模拟并发10个客户端查询票
#         p=Process(target=show,args=(i,))
#         p.start()
#     for i in range(10):
#         p = Process(target=bug_ticket, args=(i,))
#         p.start()


# 加锁
from multiprocessing import Process,Lock
import time,json,random

# 查询余票的函数
def show(i):
    with open("db") as f :
        dic=json.load(f)
    print(‘余票%s‘%dic[‘ticket‘])

# 买票的函数
def bug_ticket(i,lock):
    lock.acquire()  # 这就是拿钥匙进门,有一个进程拿了钥匙,之后第二个进程进来就没有钥匙了,就会阻塞,
    with open("db") as f :
        dic=json.load(f)
        time.sleep(0.1)  # 模拟读数据的网络延迟
    if dic[‘ticket‘] >0:
        dic[‘ticket‘]-=1
        print(‘33[32m%s购票成功33[0m‘%i)
    else:
        print(‘33[31m%s没买到票33[0m‘%i)
    time.sleep(0.1)
    with open("db","w") as f:
        json.dump(dic,f)
    lock.release()  # 这是还钥匙,还了之后,别的进程就可以拿到了,就可以执行了,


if __name__ == ‘__main__‘:
    for i in range(10):  # 模拟并发10个客户端查询票
        p=Process(target=show,args=(i,))
        p.start()
    lock=Lock()
    
    for i in range(10):
        p = Process(target=bug_ticket, args=(i,lock))
        p.start()

 

###############    多进程的信号量    ##############

# 多进程的信号量

from multiprocessing import Process
import time,random
from multiprocessing import Semaphore
# ktv只有1个房间,1个房间只能装4个人,但是这样写就是20个人都进入到房间了,
# 假设ket门口有4把钥匙,一个进程来了那一把钥匙,然后关门,这样只有4个进程能拿到,剩下的之后1个进程出来了才可以继续其他的进程,
# 这个概念就叫做信号量,同一时间就只有四个人,
def ktv(i,sem):
    sem.acquire()  # 获取钥匙
    print("%d进入ktv"%i)
    time.sleep(random.randint(60,180))  # 这是每一个人唱歌1-3分钟
    print("%d走出ktv"%i)
    sem.release()  # 还钥匙

if __name__ == "__main__":
    sem = Semaphore(4)  # 这就是设置有多少把钥匙,  信号量的英文就是:Semaphore
    for i in range(20):
        p=Process(target=ktv,args=(i,sem))
        p.start()

 

###############    进程的事件    ##############

# 进程的事件


# 事件
import time
from multiprocessing import Event, Process


# 一个信号,可以使所有的进程都进入阻塞状态,也可以控制所有信号都解除阻塞,
# 一个事件创建之后,默认是阻塞状态,
# e = Event()  # 创建一个事件
# print(e.is_set())  # 查看一个事件是否是阻塞状态,
# print(123445)
# e.set()  # 这是把阻塞的状态改为true,
# print(e.is_set())
# e.wait()  # 根据e.is_set()的结果,如果是false,就会阻塞,如果是true就会不阻塞
# print(12344)
# e.clear()  # 这是把阻塞的状态改为false
# print(e.is_set())
# e.wait()  # 虽然阻塞了,但是一定要有这个wait,才会阻塞后面的代码,
# print(444444)


# 举一个例子,红绿灯

# 每一个进程表示一辆车,
def car(e,i):
    #e.is_set() 默认返回False 代表的是绿灯
    if not e.is_set():
        print("car%s在等待"%i)
        e.wait()
    print("car%s通行了"%i)

def light(e):
    while True:
        if e.is_set():
            e.clear()
            print(‘33[31m红灯亮了33[0m‘)
        else:
            e.set()
            print(‘33[32m绿灯亮了33[0m‘)
        time.sleep(2)


if __name__ == ‘__main__‘:
    e=Event()
    # 模拟启动交通灯
    p1=Process(target=light,args=(e,))
    p1.daemon=True
    p1.start()
    #模拟20辆小车
    for i in range(20):
        import random
        time.sleep(random.uniform(0,2))
        p2=Process(target=car,args=(e,i))
        p2.start()
    print("程序彻底结束!")

 

###############    进程间的通信---队列   ##############

# 进程间的通信,
# 队列和通道,

# 队列
# 之前学过一个模块是queue,现在进程间的通信不能使用这个
# import queue

# 基本的队列的方法:

# from multiprocessing import Queue
# q =Queue(5)  # 创建共享的进程队列,maxsize是队列中允许的最大项数,如果省略此参数,则无大小限制,容量是5
# q.put(1)  # 添加值,
# q.put(2)
# q.put(3)
# q.put(4)
# q.put(5)
# q.put(6)  # 队列满了,就不能放了,这个就会阻塞,
# print(q.full())  # 这个队列是否满了,如果q已满,返回为True
# print(q.get())  # 返回q中的一个项目。如果q为空,此方法将阻塞
# print(q.get())  # 获取值,
# print(q.get())  # 获取值,
# print(q.get())  # 获取值,
# print(q.get())  # 获取值,
# print(q.get())  # 获取值,没有值了,第六次的时候就会阻塞,
#
# print(q.empty())  # 如果调用此方法时 q为空,返回True
#
# while True:
#     try:
#         q.get_nowait()  # 如果有值就等
#     except:
#         print("队列已经空了")
#         import time
#         time.sleep(1)


# # 队列之间的通信
# from multiprocessing import Queue,Process
#
# # 生产数据的函数
# def produce(q):
#     q.put("hello")
#
# def consume(q):
#     print(q.get())
#
# if __name__ == ‘__main__‘:
#     q = Queue()
#     p = Process(target=produce,args=(q,))
#     p.start()
#     c = Process(target=consume,args=(q,))
#     c.start()
#
#     # 这就是两个子进程之间的通信,通过的队列,
#


# 队列的生产者和消费者模型
# 买包子的例子
# 有蒸包子的人,这就是生产者,有买包子的人,这就是消费者,
# 实际中,可能会有数据供需不平衡的问题,
# 就是数据生产的多了没有消费,所以我们要增加消费者,或者减少生产
# 数据消费的多了,我们要增加生产者,来解决这个问题,

# 我们把生产者作为一个进程,把消费者作为一个进程

from multiprocessing import Process,Queue,JoinableQueue
import time, random
def producer(name,food,q):  # 三个参数,就是谁生产,生产了什么,放到哪里
    for i in range(10):
        time.sleep(random.randint(1,3))  # 1-3秒生产1个,
        f = "%s生产了%s%s"%(name,food,i)
        print(f)
        q.put(f)
    q.join()  # 阻塞, 这是感知一个队列中的数据全部都处理完毕,
    # 这种相当于把生产的生命周期拉长了,就是说你是生产完了还没有结束,你还要等待消费者把你生产的所有的东西都消费了,才能结束,

def consumer(q,name):
    while True:
        food =q.get()
        if food is None:  # 问题:有多个消费者的时候,只有一个消费者拿到这个值,然后结束了,但是拿不到的消费者,就还没有结束,
            print("获取到一个空")
            break
        print("%s消费了%s"%(name,food))
        time.sleep(random.randint(1,3))  # 1-3秒消费1个,
        q.task_done()  # 队列的计数器 -1


if __name__ == ‘__main__‘:
    # q = Queue()

    q = JoinableQueue()

    p1 = Process(target=producer,args=("andy","包子",q))
    p1.start()

    p2 = Process(target=producer,args=("Lucy","油条",q))
    p2.start()

    c1 = Process(target=consumer,args=(q,"xiaoxiao"))
    c1.daemon =True
    # 意味着,主进程的代码执行结束之后,子进程就结束了,
    # 而主进程又是依赖两个生产者结束才结束的,
    # 而我在生产者的地方加了一个阻塞,直到消费者全都消费了之后才结束,
    # 所以这个设计是非常的巧妙的,
    c1.start()
    # 只有一个消费者,两个生产者, 所以会有供给过大,需要加一个消费者,

    c2 = Process(target=consumer,args=(q,"meimei"))
    c2.daemon =True
    c2.start()

    # 因为只会生产10个,所以怎么能够,没有生产了,但是消费的地方还在get,怎么办?
    p1.join()
    p2.join()
    # q.put(None)
    # q.put(None)
    # 为什么是两个none?
    # 问题:有多个消费者的时候,只有一个消费者拿到这个值,然后结束了,但是拿不到的消费者,就还没有结束,
    # 这种写法太麻烦了,如果有1000个还得了,怎么办?使用新的一个模块:JoinableQueue
    # 做了三件事;
    # 1,把c1,c2,改成守护进程
    # 2,把生产者加一个q.join(),直到消费者全部消费结束
    # 3,加了一个        q.task_done()  # 队列的计数器 -1

 

###############    进程间通信--管道(了解)    ##############

# 进程间通信
# 管道(管道只做了解)

from multiprocessing import Pipe,Process


# conn1,conn2 = Pipe()  # pipe是一个函数,有两个返回值,这个地方我们是使用两个参数来接收这两个返回值,
# conn1.send("123")
# print(conn2.recv())
# 这就是管道,这是一个双向通信的,
# 你调用这个,就会给你一个左边,一个右边,你从左边传入,就可以从右边传出,
# 你从右边传入,就可以从左边传出,


# 怎么使用管道是的在进程间通信?

# 也可以使用生产者和消费者模型

import time,random
def producter(con,pro,name,food):
    con.close()
    for i in range(4):
        time.sleep(random.random())
        f = "%s生产了%s%s" % (name, food, i)
        print(f)
        pro.send(f)

def consumer(con,pro,name):
    pro.close()
    while True:
        try:
            food = con.recv()
            print("%s消费了%s" % (name, food))
            time.sleep(random.random())
        except EOFError:
            con.close()
            break


if __name__ == ‘__main__‘:
    con, pro = Pipe()
    p= Process(target=producter,args=(con, pro,"andy","包子"))
    p.start()

    c1= Process(target=consumer,args=(con, pro,"li"))
    c1.start()
    c2= Process(target=consumer,args=(con, pro,"wang"))
    c2.start()

    con.close()
    pro.close()

# pipe有一个数据不安全性,一个放一个取没有问题,
# 但两个消费者的时候会有问题,会出现两个消费者抢资源的问题,
# 怎么解决这个问题?通过加锁
# 所以我们还是使用队列,队列就是基于管道加锁的,管道就是基于socket的,
# 使用队列就不会有数据不安全的问题了,
# 自己加锁需要考虑很多问题,所以我们还是使用队列,管道作为了解,
# 我们工作中顶多就会用到队列,

 

###############    进程之间的数据共享    ##############

# 进程之间的数据共享
# 通过Manager模块
from multiprocessing import Manager,Process,Lock

# 单个进程:
# def work(dic):
#     dic[‘count‘]-=1
#     print(dic)
#
# if __name__ == ‘__main__‘:
#     m = Manager()
#     dic=m.dict({‘count‘:100})
#     p_list=[]
#     p = Process(target=work, args=(dic,))
#     p_list.append(p)
#     p.start()
#     p.join()
#     print("主进程",dic)  # 主进程 {‘count‘: 99},这就是子进程的改变,影响到了主进程


# 多个进程,出现的问题:
# def work(dic):
#     dic[‘count‘]-=1
#     # 这个就是涉及到多个进程修改一个内容的情况,这种是不安全的,怎么办?加锁
#     # 按理说是50,可能会结果不是50
#
# if __name__ == ‘__main__‘:
#     m = Manager()
#     dic=m.dict({‘count‘:100})
#     p_list=[]
#     for i in range(50):  # 创建多个进程
#         p = Process(target=work, args=(dic,))
#         p_list.append(p)
#         p.start()
#     for i in p_list:
#         p.join()
#     print("主进程",dic)  # 主进程 {‘count‘: 99},这就是子进程的改变,影响到了主进程


# 多个进程加锁:
def work(dic,lock):
    lock.acquire()
    dic[‘count‘]-=1
    # 这个就是涉及到多个进程修改一个内容的情况,这种是不安全的,怎么办?加锁
    lock.release()

if __name__ == ‘__main__‘:
    m = Manager()
    lock = Lock()
    dic=m.dict({‘count‘:100})
    p_list=[]
    for i in range(50):  # 创建多个进程
        p = Process(target=work, args=(dic,lock))
        p_list.append(p)
        p.start()
    for i in p_list:
        p.join()
    print("主进程",dic)  # 主进程 {‘count‘: 99},这就是子进程的改变,影响到了主进程

# 这个数据共享,工作中也不会用到,
# 队列还有很多,kafak,rebbitmq,memcache

 

###############   进程池    ##############

"""

进程池的概念
为什么会有进程池?
1,因为没开启一个进程,都需要创建一个内存空间,这是耗时的
2,进程过多,操作熊的调度也会耗时,
所以会有非常大的性能问题,
所以我们不会让进程太大,我们会设计一个进程池,

进程池:
1,Python中先创建一个进程的池子,
2,这个进程池能存放多少个进程,比如有5个进程,
3,先把这些进程创建好,
4,比如有50个任务他们到进程池里面去找进程,找到的就执行,找不到的就等待,
5,进程执行结束之后,不会结束,而是返回进程池,等待下一个任务,
所以进程池,可以节省进程创建的时间,节省了操作系统的调度,而且进程不会过多的创建,

所以进程池和信号量有什么关系?
假设有200个任务,
信号量,信号量还是200个进程在排队,去拿钥匙,所以不能控制有多少进程,而是控制了同一时间有几个进程在执行,
也就是只允许5个进程让操作系统调度,节省了操作系统的调度时间,但是并没有节省进程的创建时间,
而进程池,是有200个任务去拿进程,所以进程池既是节省了操作系统的调度时间,也节省进程的创建时间,

更高级的进程池是比较智能的,
比如现在进程池有5个进程,就可以处理过来了,就不需要增加
但是如果处理等待的任务太多了,急需要往进程池里面加进程,一直到设置的进程池上限
如果任务减少了,就进程池里面减少,
这是比较智能的,

Python中没有高级的进程池,只有一个固定的进程数的进程池,没有弹性的那种,


"""
# from multiprocessing import Pool, Process
# def func(n):
#     print(n + 1)
# if __name__ == ‘__main__‘:
#     pool = Pool(5)  # 进程池里面有5个进程,约定就是cpu的内核+1
#     pool.map(func, range(100))  # 这是模拟100个任务,
# 这个map是一个异步的,而且自带close,和join功能,


# 上面是一个使用进程池的方法,还有其他的使用进程池的方法
# 使用了进程池之后,就不使用哪种创建进程的方式了,

from multiprocessing import Pool
import time,os
def func(n):
    print("start func%s"%n,os.getpid())
    time.sleep(1)
    print("end func%s"%n,os.getpid())

if __name__ == ‘__main__‘:
    p = Pool(5) # 不写就是默认cpu的核数,
    for i in range(10):
        # p.apply(func,args=(i,))  # p.apply这是同步,很慢,
        p.apply_async(func,args=(i,))  # p.apply_async这是异步,这个一定是和close和join同时使用的,
    p.close()  # 结束进程池接收任务
    p.join()  # 这是感知进程池中的任务执行结束,

 

###############   进程池的返回值   ##############

# 进程池的返回值,

from multiprocessing import Pool, Process
def func(i):
    return i
# if __name__ == ‘__main__‘:
#     pool = Pool(5)
#     res_list = []
#     for i in range(10):
#         # res = pool.apply(func,args=(i,))  # 所以这个结果接收,就是返回值,
#         res = pool.apply_async(func,args=(i,))  # 所以这个结果接收,就是返回值,
#         res_list.append(res)
#     for res in res_list:
#         print(res.get())  # get会阻塞等待结果

# 上面讲了apply和apply_async 的返回值的问题,
# 下面讲讲map的返回值的问题,比较简单

if __name__ == ‘__main__‘:
    pool = Pool(5)
    ret = pool.map(func,range(10))
    print(ret)  # 这是返回了一个列表,

# 使用的时候想用map,map搞不定就使用,apply_async

 

###############  进程池的回调函数    ##############

# 进程池的回调函数

from multiprocessing import Pool

def func1(n):
    print(111)
    return n

def func2(n):
    print(222)
    print(n*2)

if __name__ == ‘__main__‘:
    p = Pool(5)
    p.apply_async(func1,args=(10,),callback=func2)
    p.close()
    p.join()
    # 回调函数都是在主进程中执行的,

 

以上是关于python语法基础-并发编程-进程-长期维护的主要内容,如果未能解决你的问题,请参考以下文章

python语法基础-并发编程-协程-长期维护

python语法基础-网络编程-长期维护

Python之路--目录

python后端面试第二部分:网络编程--长期维护

python语法基础-异常操作-长期维护

python语法基础-函数-进阶-长期维护