Python中線程threading.Thread的使用詳解
1. 線程的概念
線程,有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源。
2. threading.thread()的簡(jiǎn)單使用
2.1 添加線程可以是程序運(yùn)行更快
python的thread模塊是比較底層的模塊,python的threading模塊是對(duì)thread做了一些包裝的,可以更加方便的被使用
有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源
import threading import time def saySorry(): print("親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?") time.sleep(5) if __name__ == "__main__": start_time1 = time.time() for i in range(5): t = threading.Thread(target=saySorry) t.start() #啟動(dòng)線程,即讓線程開(kāi)始執(zhí)行 end_time1 = time.time() print(end_time1 - start_time1) start_time2 = time.time() for i in range(5): t = saySorry() end_time2 = time.time() print(end_time2 - start_time2)
輸出為:
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
0.001995086669921875
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
親愛(ài)的,我錯(cuò)了,我能吃飯了嗎?
25.001766204833984
2.2 主線程會(huì)等待所有的子線程結(jié)束后才結(jié)束
import threading from time import sleep,ctime def sing(): for i in range(3): print("正在唱歌...%d"%i) sleep(1) def dance(): for i in range(3): print("正在跳舞...%d"%i) sleep(1) if __name__ == '__main__': print('---開(kāi)始---:%s'%ctime()) t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() # sleep(5) # 屏蔽此行代碼,試試看,程序是否會(huì)立馬結(jié)束? print('---結(jié)束---:%s'%ctime())
輸出為:
---開(kāi)始---:Mon Sep 28 14:42:09 2020
正在唱歌...0
正在跳舞...0---結(jié)束---:Mon Sep 28 14:42:09 2020
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2
如果釋放‘ sleep(5) ’,輸出為:
---開(kāi)始---:Mon Sep 28 14:43:36 2020
正在唱歌...0
正在跳舞...0
正在跳舞...1
正在唱歌...1
正在唱歌...2正在跳舞...2
---結(jié)束---:Mon Sep 28 14:43:41 2020
3.查看線程數(shù)量
import threading from time import sleep,ctime def sing(): for i in range(3): print("正在唱歌...%d"%i) sleep(1) def dance(): for i in range(3): print("正在跳舞...%d"%i) sleep(1) if __name__ == '__main__': print('---開(kāi)始---:%s'%ctime()) t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() while True: length = len(threading.enumerate()) print('當(dāng)前運(yùn)行的線程數(shù)為:%d'%length) if length<=1: break sleep(0.5)
輸出為:
---開(kāi)始---:Mon Sep 28 14:46:16 2020
正在唱歌...0
正在跳舞...0
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌...1
正在跳舞...1當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌...2
正在跳舞...2
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:1
4.線程參數(shù)及順序
4.1 傳遞參數(shù)的方法
使用args 傳遞參數(shù) threading.Thread(target=sing, args=(10, 100, 100))
使用kwargs傳遞參數(shù) threading.Thread(target=sing, kwargs={“a”: 10, “b”:100, “c”: 100})
同時(shí)使用 args 和 kwargs 傳遞參數(shù) threading.Thread(target=sing, args=(10, ), kwargs={“b”: 100,“c”: 100})
4.2 線程的執(zhí)行順序
import threading import time def sing(): for i in range(5): print("我是sing") time.sleep(1) def dance(): for i in range(5): print("我是dance") time.sleep(1) if __name__ == '__main__': # 創(chuàng)建兩個(gè)子線程 t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) # 啟動(dòng)子線程 t1.start() t2.start()
輸出為:
我是sing
我是dance
我是sing
我是dance
我是dance
我是sing
我是dance我是sing
我是sing
我是dance
說(shuō)明:
從代碼和執(zhí)行結(jié)果我們可以看出,多線程程序的執(zhí)行順序是不確定的。當(dāng)執(zhí)行到sleep語(yǔ)句時(shí),線程將被阻塞(Blocked),到sleep結(jié)束后,線程進(jìn)入就緒(Runnable)狀態(tài),等待調(diào)度。而線程調(diào)度將自行選擇一個(gè)線程執(zhí)行。上面的代碼中只能保證每個(gè)線程都運(yùn)行完整個(gè)run函數(shù),但是線程的啟動(dòng)順序、run函數(shù)中每次循環(huán)的執(zhí)行順序都不能確定。
5. 守護(hù)線程
守護(hù)線程:如果在程序中將子線程設(shè)置為守護(hù)線程,則該子線程會(huì)在主線程結(jié)束時(shí)自動(dòng)退出,設(shè)置方式為thread.setDaemon(True),要在thread.start()之前設(shè)置,默認(rèn)是false的,也就是主線程結(jié)束時(shí),子線程依然在執(zhí)行。
5.1 如下代碼,主線程已經(jīng)exit() 【其實(shí)并沒(méi)有真正結(jié)束】,子線程還在繼續(xù)執(zhí)行
import threading import time def test(): for i in range(7): print("test is run:", i) time.sleep(1) if __name__ == '__main__': # 創(chuàng)建子線程 t1 = threading.Thread(target=test) # 啟動(dòng)子線程 t1.start() # 休眠2秒 time.sleep(2) print("我 OVER 了") # 退出 exit()
輸出為:
test is run: 0
test is run: 1
我 OVER 了
test is run: 2
test is run: 3
test is run: 4
test is run: 5
test is run: 6
5.2 設(shè)置守護(hù)線程
為線程設(shè)置守護(hù),如果主線程結(jié)束,子線程也隨之結(jié)束。
import threading import time def test(): for i in range(7): print("test is run:", i) time.sleep(1) if __name__ == '__main__': # 創(chuàng)建子線程 t1 = threading.Thread(target=test) #設(shè)置線程保護(hù) t1.setDaemon(True) # 啟動(dòng)子線程 t1.start() # 休眠2秒 time.sleep(2) print("我 OVER 了") # 退出 exit()
輸出為:
test is run: 0
test is run: 1
我 OVER 了
參考代碼
import threading from threading import Lock,Thread import time,os ''' python多線程詳解 什么是線程? 線程也叫輕量級(jí)進(jìn)程,是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包涵在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。 線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其他線程共享進(jìn)程所 擁有的全部資源。一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程,同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行 ''' ''' 為什么要使用多線程? 線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行流。與分隔的進(jìn)程相比,進(jìn)程中線程之間的隔離程度要小,它們共享內(nèi)存、文件句柄 和其他進(jìn)程應(yīng)有的狀態(tài)。 因?yàn)榫€程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。進(jìn)程在執(zhí)行過(guò)程之中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享 內(nèi)存,從而極大的提升了程序的運(yùn)行效率。 線程比進(jìn)程具有更高的性能,這是由于同一個(gè)進(jìn)程中的線程都有共性,多個(gè)線程共享一個(gè)進(jìn)程的虛擬空間。線程的共享環(huán)境 包括進(jìn)程代碼段、進(jìn)程的共有數(shù)據(jù)等,利用這些共享的數(shù)據(jù),線程之間很容易實(shí)現(xiàn)通信。 操作系統(tǒng)在創(chuàng)建進(jìn)程時(shí),必須為改進(jìn)程分配獨(dú)立的內(nèi)存空間,并分配大量的相關(guān)資源,但創(chuàng)建線程則簡(jiǎn)單得多。因此,使用多線程 來(lái)實(shí)現(xiàn)并發(fā)比使用多進(jìn)程的性能高得要多。 ''' ''' 總結(jié)起來(lái),使用多線程編程具有如下幾個(gè)優(yōu)點(diǎn): 進(jìn)程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。 操作系統(tǒng)在創(chuàng)建進(jìn)程時(shí),需要為該進(jìn)程重新分配系統(tǒng)資源,但創(chuàng)建線程的代價(jià)則小得多。因此使用多線程來(lái)實(shí)現(xiàn)多任務(wù)并發(fā)執(zhí)行比使用多進(jìn)程的效率高 python語(yǔ)言內(nèi)置了多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡(jiǎn)化了python的多線程編程。 ''' ''' 普通創(chuàng)建方式 ''' # def run(n): # print('task',n) # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # time.sleep(1) # print('0s') # time.sleep(1) # # if __name__ == '__main__': # t1 = threading.Thread(target=run,args=('t1',)) # target是要執(zhí)行的函數(shù)名(不是函數(shù)),args是函數(shù)對(duì)應(yīng)的參數(shù),以元組的形式存在 # t2 = threading.Thread(target=run,args=('t2',)) # t1.start() # t2.start() ''' 自定義線程:繼承threading.Thread來(lái)定義線程類,其本質(zhì)是重構(gòu)Thread類中的run方法 ''' # class MyThread(threading.Thread): # def __init__(self,n): # super(MyThread,self).__init__() #重構(gòu)run函數(shù)必須寫 # self.n = n # # def run(self): # print('task',self.n) # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # time.sleep(1) # print('0s') # time.sleep(1) # # if __name__ == '__main__': # t1 = MyThread('t1') # t2 = MyThread('t2') # t1.start() # t2.start() ''' 守護(hù)線程 下面這個(gè)例子,這里使用setDaemon(True)把所有的子線程都變成了主線程的守護(hù)線程, 因此當(dāng)主線程結(jié)束后,子線程也會(huì)隨之結(jié)束,所以當(dāng)主線程結(jié)束后,整個(gè)程序就退出了。 所謂'線程守護(hù)',就是主線程不管該線程的執(zhí)行情況,只要是其他子線程結(jié)束且主線程執(zhí)行完畢,主線程都會(huì)關(guān)閉。也就是說(shuō):主線程不等待該守護(hù)線程的執(zhí)行完再去關(guān)閉。 ''' # def run(n): # print('task',n) # time.sleep(1) # print('3s') # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # # if __name__ == '__main__': # t=threading.Thread(target=run,args=('t1',)) # t.setDaemon(True) # t.start() # print('end') ''' 通過(guò)執(zhí)行結(jié)果可以看出,設(shè)置守護(hù)線程之后,當(dāng)主線程結(jié)束時(shí),子線程也將立即結(jié)束,不再執(zhí)行 ''' ''' 主線程等待子線程結(jié)束 為了讓守護(hù)線程執(zhí)行結(jié)束之后,主線程再結(jié)束,我們可以使用join方法,讓主線程等待子線程執(zhí)行 ''' # def run(n): # print('task',n) # time.sleep(2) # print('5s') # time.sleep(2) # print('3s') # time.sleep(2) # print('1s') # if __name__ == '__main__': # t=threading.Thread(target=run,args=('t1',)) # t.setDaemon(True) #把子線程設(shè)置為守護(hù)線程,必須在start()之前設(shè)置 # t.start() # t.join() #設(shè)置主線程等待子線程結(jié)束 # print('end') ''' 多線程共享全局變量 線程時(shí)進(jìn)程的執(zhí)行單元,進(jìn)程時(shí)系統(tǒng)分配資源的最小執(zhí)行單位,所以在同一個(gè)進(jìn)程中的多線程是共享資源的 ''' # g_num = 100 # def work1(): # global g_num # for i in range(3): # g_num+=1 # print('in work1 g_num is : %d' % g_num) # # def work2(): # global g_num # print('in work2 g_num is : %d' % g_num) # # if __name__ == '__main__': # t1 = threading.Thread(target=work1) # t1.start() # time.sleep(1) # t2=threading.Thread(target=work2) # t2.start() ''' 由于線程之間是進(jìn)行隨機(jī)調(diào)度,并且每個(gè)線程可能只執(zhí)行n條執(zhí)行之后,當(dāng)多個(gè)線程同時(shí)修改同一條數(shù)據(jù)時(shí)可能會(huì)出現(xiàn)臟數(shù)據(jù), 所以出現(xiàn)了線程鎖,即同一時(shí)刻允許一個(gè)線程執(zhí)行操作。線程鎖用于鎖定資源,可以定義多個(gè)鎖,像下面的代碼,當(dāng)需要獨(dú)占 某一個(gè)資源時(shí),任何一個(gè)鎖都可以鎖定這個(gè)資源,就好比你用不同的鎖都可以把這個(gè)相同的門鎖住一樣。 由于線程之間是進(jìn)行隨機(jī)調(diào)度的,如果有多個(gè)線程同時(shí)操作一個(gè)對(duì)象,如果沒(méi)有很好地保護(hù)該對(duì)象,會(huì)造成程序結(jié)果的不可預(yù)期, 我們因此也稱為“線程不安全”。 為了防止上面情況的發(fā)生,就出現(xiàn)了互斥鎖(Lock) ''' # def work(): # global n # lock.acquire() # temp = n # time.sleep(0.1) # n = temp-1 # lock.release() # # # if __name__ == '__main__': # lock = Lock() # n = 100 # l = [] # for i in range(100): # p = Thread(target=work) # l.append(p) # p.start() # for p in l: # p.join() ''' 遞歸鎖:RLcok類的用法和Lock類一模一樣,但它支持嵌套,在多個(gè)鎖沒(méi)有釋放的時(shí)候一般會(huì)使用RLock類 ''' # def func(lock): # global gl_num # lock.acquire() # gl_num += 1 # time.sleep(1) # print(gl_num) # lock.release() # # # if __name__ == '__main__': # gl_num = 0 # lock = threading.RLock() # for i in range(10): # t = threading.Thread(target=func,args=(lock,)) # t.start() ''' 信號(hào)量(BoundedSemaphore類) 互斥鎖同時(shí)只允許一個(gè)線程更改數(shù)據(jù),而Semaphore是同時(shí)允許一定數(shù)量的線程更改數(shù)據(jù),比如廁所有3個(gè)坑, 那最多只允許3個(gè)人上廁所,后面的人只能等里面有人出來(lái)了才能再進(jìn)去 ''' # def run(n,semaphore): # semaphore.acquire() #加鎖 # time.sleep(3) # print('run the thread:%s\n' % n) # semaphore.release() #釋放 # # # if __name__== '__main__': # num=0 # semaphore = threading.BoundedSemaphore(5) #最多允許5個(gè)線程同時(shí)運(yùn)行 # for i in range(22): # t = threading.Thread(target=run,args=('t-%s' % i,semaphore)) # t.start() # while threading.active_count() !=1: # pass # else: # print('----------all threads done-----------') ''' python線程的事件用于主線程控制其他線程的執(zhí)行,事件是一個(gè)簡(jiǎn)單的線程同步對(duì)象,其主要提供以下的幾個(gè)方法: clear將flag設(shè)置為 False set將flag設(shè)置為 True is_set判斷是否設(shè)置了flag wait會(huì)一直監(jiān)聽(tīng)flag,如果沒(méi)有檢測(cè)到flag就一直處于阻塞狀態(tài) 事件處理的機(jī)制:全局定義了一個(gè)Flag,當(dāng)Flag的值為False,那么event.wait()就會(huì)阻塞,當(dāng)flag值為True, 那么event.wait()便不再阻塞 ''' event = threading.Event() def lighter(): count = 0 event.set() #初始者為綠燈 while True: if 5 < count <=10: event.clear() #紅燈,清除標(biāo)志位 print("\33[41;lmred light is on...\033[0m]") elif count > 10: event.set() #綠燈,設(shè)置標(biāo)志位 count = 0 else: print('\33[42;lmgreen light is on...\033[0m') time.sleep(1) count += 1 def car(name): while True: if event.is_set(): #判斷是否設(shè)置了標(biāo)志位 print('[%s] running.....'%name) time.sleep(1) else: print('[%s] sees red light,waiting...'%name) event.wait() print('[%s] green light is on,start going...'%name) # startTime = time.time() light = threading.Thread(target=lighter,) light.start() car = threading.Thread(target=car,args=('MINT',)) car.start() endTime = time.time() # print('用時(shí):',endTime-startTime) ''' GIL 全局解釋器 在非python環(huán)境中,單核情況下,同時(shí)只能有一個(gè)任務(wù)執(zhí)行。多核時(shí)可以支持多個(gè)線程同時(shí)執(zhí)行。但是在python中,無(wú)論有多少個(gè)核 同時(shí)只能執(zhí)行一個(gè)線程。究其原因,這就是由于GIL的存在導(dǎo)致的。 GIL的全程是全局解釋器,來(lái)源是python設(shè)計(jì)之初的考慮,為了數(shù)據(jù)安全所做的決定。某個(gè)線程想要執(zhí)行,必須先拿到GIL,我們可以 把GIL看做是“通行證”,并且在一個(gè)python進(jìn)程之中,GIL只有一個(gè)。拿不到線程的通行證,并且在一個(gè)python進(jìn)程中,GIL只有一個(gè), 拿不到通行證的線程,就不允許進(jìn)入CPU執(zhí)行。GIL只在cpython中才有,因?yàn)閏python調(diào)用的是c語(yǔ)言的原生線程,所以他不能直接操 作cpu,而只能利用GIL保證同一時(shí)間只能有一個(gè)線程拿到數(shù)據(jù)。而在pypy和jpython中是沒(méi)有GIL的 python在使用多線程的時(shí)候,調(diào)用的是c語(yǔ)言的原生過(guò)程。 ''' ''' python針對(duì)不同類型的代碼執(zhí)行效率也是不同的 1、CPU密集型代碼(各種循環(huán)處理、計(jì)算等),在這種情況下,由于計(jì)算工作多,ticks技術(shù)很快就會(huì)達(dá)到閥值,然后出發(fā)GIL的 釋放與再競(jìng)爭(zhēng)(多個(gè)線程來(lái)回切換當(dāng)然是需要消耗資源的),所以python下的多線程對(duì)CPU密集型代碼并不友好。 2、IO密集型代碼(文件處理、網(wǎng)絡(luò)爬蟲(chóng)等設(shè)計(jì)文件讀寫操作),多線程能夠有效提升效率(單線程下有IO操作會(huì)進(jìn)行IO等待, 造成不必要的時(shí)間浪費(fèi),而開(kāi)啟多線程能在線程A等待時(shí),自動(dòng)切換到線程B,可以不浪費(fèi)CPU的資源,從而能提升程序的執(zhí)行 效率)。所以python的多線程對(duì)IO密集型代碼比較友好。 ''' ''' 主要要看任務(wù)的類型,我們把任務(wù)分為I/O密集型和計(jì)算密集型,而多線程在切換中又分為I/O切換和時(shí)間切換。如果任務(wù)屬于是I/O密集型, 若不采用多線程,我們?cè)谶M(jìn)行I/O操作時(shí),勢(shì)必要等待前面一個(gè)I/O任務(wù)完成后面的I/O任務(wù)才能進(jìn)行,在這個(gè)等待的過(guò)程中,CPU處于等待 狀態(tài),這時(shí)如果采用多線程的話,剛好可以切換到進(jìn)行另一個(gè)I/O任務(wù)。這樣就剛好可以充分利用CPU避免CPU處于閑置狀態(tài),提高效率。但是 如果多線程任務(wù)都是計(jì)算型,CPU會(huì)一直在進(jìn)行工作,直到一定的時(shí)間后采取多線程時(shí)間切換的方式進(jìn)行切換線程,此時(shí)CPU一直處于工作狀態(tài), 此種情況下并不能提高性能,相反在切換多線程任務(wù)時(shí),可能還會(huì)造成時(shí)間和資源的浪費(fèi),導(dǎo)致效能下降。這就是造成上面兩種多線程結(jié)果不能的解釋。 結(jié)論:I/O密集型任務(wù),建議采取多線程,還可以采用多進(jìn)程+協(xié)程的方式(例如:爬蟲(chóng)多采用多線程處理爬取的數(shù)據(jù));對(duì)于計(jì)算密集型任務(wù),python此時(shí)就不適用了。 '''
以上就是Python中線程threading.Thread的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Python threading.Thread的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python使用百度或高德地圖獲取地理位置并轉(zhuǎn)換
用python處理地理位置是非常常見(jiàn)的需求,下面這篇文章主要給大家介紹了關(guān)于python使用百度或高德地圖獲取地理位置并轉(zhuǎn)換的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07django 配置阿里云OSS存儲(chǔ)media文件的例子
今天小編就為大家分享一篇django 配置阿里云OSS存儲(chǔ)media文件的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08在Python的Django框架上部署ORM庫(kù)的教程
這篇文章主要介紹了在Python的Django框架上部署ORM庫(kù)的教程,文中還給出了幾個(gè)ORM庫(kù)之間的對(duì)比,需要的朋友可以參考下2015-04-04解決Django transaction進(jìn)行事務(wù)管理踩過(guò)的坑
這篇文章主要介紹了解決Django transaction進(jìn)行事務(wù)管理踩過(guò)的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04python操作mongodb根據(jù)_id查詢數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了python操作mongodb根據(jù)_id查詢數(shù)據(jù)的實(shí)現(xiàn)方法,實(shí)例分析了Python根據(jù)pymongo不同版本操作ObjectId的技巧,需要的朋友可以參考下2015-05-05celery在python爬蟲(chóng)中定時(shí)操作實(shí)例講解
在本篇文章里小編給大家整理了一篇關(guān)于celery在python爬蟲(chóng)中定時(shí)操作實(shí)例講解內(nèi)容,需要的朋友們可以參考下。2020-11-11python實(shí)現(xiàn)人人自動(dòng)回復(fù)、搶沙發(fā)功能
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)人人自動(dòng)回復(fù)、搶沙發(fā)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06