Python入門系列(十二)——GUI+多進(jìn)程

不好意思,拖堂了,此為終章,補(bǔ)充蠻重要的兩點(diǎn)
一、GUI
二、多進(jìn)程

一、GUI

話說,python做圖形界面并不明智,效率并不高。但在某些特殊需求下還是需要我們?nèi)ナ褂?,所以python擁有多個(gè)第三方庫(kù)用以實(shí)現(xiàn)GUI,本章我們使用python基本模塊tkinter進(jìn)行學(xué)習(xí),因?yàn)樾枨蟛⒉淮?,所以不做太多拓展?br> 繼續(xù)改寫上一章的IP查詢系統(tǒng)(= =,要玩爛了),首先略改下IpWhere.py以備調(diào)用~

import requests
import re
def ip_adr(target_ip):
    headers = {'user-agent': 'ceshi/0.0.1'}
    r = requests.get('http://ip.tool.chinaz.com/{}'.format(target_ip),headers=headers)
    find_adr = re.compile(r'<span class="Whwtdhalf w50-0">.*</span>')
    find_ip = re.compile(r'<span class="Whwtdhalf w15-0">.*</span>')
    res1=find_adr.findall(r.text)
    res2=find_ip.findall(r.text)
    adr=re.findall(r'>.*<',str(res1[1]))
    adr=str(adr)[3:]
    adr=str(adr)[:-3]
    ip=re.findall(r'>.*<',str(res2[4]))
    ip=str(ip)[3:]
    ip=str(ip)[:-3]
    return '目標(biāo)IP為:{}\n物理地址為:{}'.format(ip,adr)

if "__main__" == __name__ :
    print(ip_adr('baidu.com'))

然后使用tkinter模塊進(jìn)行圖形界面的實(shí)現(xiàn),調(diào)用預(yù)編譯的IpWhere模塊 :

from tkinter import *
import tkinter.messagebox as messagebox
from IpWhere import ip_adr

class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.nameInput = Entry(self)
        self.nameInput.pack()
        self.alertButton = Button(self, text='點(diǎn)擊查詢', command=self.ipis)
        self.alertButton.pack()

    def ipis(self):
        try:
            res = self.nameInput.get()
            messagebox.showinfo('查詢結(jié)果','{}' .format(ip_adr(res)))
        except:
            messagebox.showinfo('查詢結(jié)果', '乖,好好輸入')

app = Application()
app.master.title('IP查詢系統(tǒng)')
app.mainloop()

額,太丑了,但基本實(shí)現(xiàn)我們小小的需求,在以后的py學(xué)習(xí)中,我們?cè)偕婕捌渌牡谌侥K,此處就當(dāng)是入門了解吧。

正確噠
錯(cuò)誤噠

二、多進(jìn)程

十分抱歉把這么重要的內(nèi)容放在最后,要不是大佬指點(diǎn),此次學(xué)習(xí)可能就要錯(cuò)過多進(jìn)程的問題了。
Unix系統(tǒng)提供了forx,python可借助os模塊調(diào)用,從而實(shí)現(xiàn)多進(jìn)程,然而windows系統(tǒng)并不具備,所以我們選擇python內(nèi)置的multiprocessing多進(jìn)程模塊進(jìn)行學(xué)習(xí)。

友情提示:請(qǐng)先自行補(bǔ)充線程、進(jìn)程基本概念。

首先我們借助直接調(diào)用多進(jìn)程來改寫下我們?cè)诙嗑€程章節(jié)用到的例子!

import time
from multiprocessing import Process

def eating():
    print('吃飯時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來吃飯
    print('吃飽啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def sleeping():
    print('睡覺時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來睡覺
    print('醒啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def hitting():
    print('打豆豆時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來打豆豆
    print('打出翔了!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

if __name__=='__main__':
    p=Process(target=eating())
    p.start()
    p.join()
    p=Process(target=sleeping())
    p.start()
    p.join()
    p=Process(target=hitting())
    p.start()
    p.join()

#輸出:
吃飯時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:16:32
吃飽啦!當(dāng)前時(shí)間:2019-01-29 00:16:34
睡覺時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:16:34
醒啦!當(dāng)前時(shí)間:2019-01-29 00:16:36
打豆豆時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:16:36
打出翔了!當(dāng)前時(shí)間:2019-01-29 00:16:38

顯然,這么寫實(shí)在太蠢了,如果我們的任務(wù)量巨大,這并不合適。所以我們引入了進(jìn)程池的概念,使用進(jìn)程池進(jìn)行改寫:

import time
from multiprocessing import Pool


def eating():
    print('吃飯時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來吃飯
    print('吃飽啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def sleeping():
    print('睡覺時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來睡覺
    print('醒啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def hitting():
    print('打豆豆時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠兩秒鐘用來打豆豆
    print('打出翔了!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

if __name__=='__main__':
    p = Pool(3)
    for i in [eating(),sleeping(),hitting()]:
        p.apply_async(i)
    p.close()
    p.join()

#輸出:
吃飯時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:23:02
睡覺時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:23:02
打豆豆時(shí)間到!當(dāng)前時(shí)間:2019-01-29 00:23:02
吃飽啦!當(dāng)前時(shí)間:2019-01-29 00:23:04
醒啦!當(dāng)前時(shí)間:2019-01-29 00:23:04
打出翔了!當(dāng)前時(shí)間:2019-01-29 00:23:04

在此,我們可以看到所有進(jìn)程是并發(fā)執(zhí)行的,同樣,我們?cè)诙嗑€程章節(jié)就講過,主進(jìn)程的結(jié)束意味著程序退出,所以我們需要借助join()方法堵塞進(jìn)程。

進(jìn)程間通信

我們知道線程共享內(nèi)存空間,而進(jìn)程的內(nèi)存是獨(dú)立的,同一個(gè)進(jìn)程的線程之間可以直接交流,也就帶來了線程同步的苦惱,這個(gè)我們?cè)诙嗑€程章節(jié)已經(jīng)講過了;而兩個(gè)進(jìn)程想通信,則必須通過一個(gè)中間代理來實(shí)現(xiàn),即我們接下來的內(nèi)容:進(jìn)程間通信。

進(jìn)程之間肯定是需要通信的,操作系統(tǒng)提供了很多機(jī)制來實(shí)現(xiàn)進(jìn)程間的通信。Python的multiprocessing模塊包裝了底層的機(jī)制,提供了Queue、Pipes等多種方式來交換數(shù)據(jù)。我們接下來就以Queue的方式進(jìn)行學(xué)習(xí)。

Queue.Queue是進(jìn)程內(nèi)非阻塞隊(duì)列,multiprocess.Queue是跨進(jìn)程通信隊(duì)列,前者是各自私有,后者是各子進(jìn)程共有。

還有一個(gè)在后者基礎(chǔ)上進(jìn)行封裝的multiprocess.Manager.Queue()方法,如果要使用Pool創(chuàng)建進(jìn)程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否則會(huì)得到一條如下的錯(cuò)誤信息:RuntimeError: Queue objects should only be shared between processes through inheritance.

接下來我們就借助進(jìn)程池來進(jìn)行多進(jìn)程操作的改寫,感謝大佬一路輔導(dǎo)。

import time
import os
from multiprocessing import Manager,Pool

def eating(q):
    print('吃飯時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠兩秒鐘用來吃飯
    print('吃飽啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    q.get()
def sleeping(q):
    print('睡覺時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠兩秒鐘用來睡覺
    print('醒啦!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    q.get()
def hitting(q):
    print('打豆豆時(shí)間到!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠兩秒鐘用來打豆豆
    print('打出翔了!當(dāng)前時(shí)間:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    q.get()
def working(funcName,q):
    if funcName == "eating":
        eating(q)
    if funcName == "hitting":
        hitting(q)
    if funcName == "sleeping":
        sleeping(q)

def main():
    p=Pool(3)                   #創(chuàng)建進(jìn)程池
    q=Manager().Queue(2)        #創(chuàng)建隊(duì)列,為了演示更清晰,我們故意設(shè)兩個(gè)隊(duì)列
    for i in ["eating","sleeping","hitting"]:
        q.put(i)                #添加隊(duì)列
        print("隊(duì)列添加成功")
        p.apply_async(working, args=(i,q))

    #p.close()
    #p.join()
    while True:                 #用上方注釋的jion()亦可,此處使用循環(huán)堵塞隊(duì)列
        if q.empty():           #如隊(duì)列為空,break,循環(huán)結(jié)束,主線程結(jié)束
            break

if __name__=='__main__':
    main()

#輸出:
隊(duì)列添加成功
隊(duì)列添加成功
睡覺時(shí)間到!當(dāng)前時(shí)間:2019-01-29 15:25:36
Process to running: 11872
吃飯時(shí)間到!當(dāng)前時(shí)間:2019-01-29 15:25:36
Process to running: 25284
吃飽啦!當(dāng)前時(shí)間:2019-01-29 15:25:38
醒啦!當(dāng)前時(shí)間:2019-01-29 15:25:38
隊(duì)列添加成功
打豆豆時(shí)間到!當(dāng)前時(shí)間:2019-01-29 15:25:38
Process to running: 18536
打出翔了!當(dāng)前時(shí)間:2019-01-29 15:25:40

Process finished with exit code 0

我們可以看到兩個(gè)子線程先執(zhí)行,然后一個(gè)子線程單獨(dú)執(zhí)行,此處有意而為之,讓大家更清晰的了解隊(duì)列的使用。期間有一處我們放棄使用jion()方法堵塞,而是自己寫了個(gè)循環(huán)堵塞,大家根據(jù)自己習(xí)慣來就好。

實(shí)例拓展

話說,真的沒人吐槽么?上面的例子從需求上來講,完全就不需要多線程好不好!emmmm,我們來點(diǎn)實(shí)力拓展,寫一個(gè)有智商的多線程腳本,順便結(jié)合上一節(jié)的web來一個(gè)綜合篇,隨便找個(gè)現(xiàn)實(shí)需求吧!

emmm,比如我們來到當(dāng)當(dāng)網(wǎng)買書,搜一下我們想要的書籍,發(fā)現(xiàn)?。√嗔耍?!真J2亂!!看不過來!!不想翻頁(yè)!!直接告訴我哪個(gè)便宜、哪個(gè)牛逼好不好!!

當(dāng)當(dāng)網(wǎng)

簡(jiǎn)單看下這個(gè)url:
http://search.dangdang.com/?key=滲透測(cè)試&ddsale=1&page_index=2
其中ddsale參數(shù)代表當(dāng)當(dāng)自營(yíng),page_index代表頁(yè)數(shù),key代表搜索內(nèi)容,我們本次的變量只有頁(yè)數(shù)。

所以我們構(gòu)造請(qǐng)求的url為:
'http://search.dangdang.com/?key=滲透測(cè)試&ddsale=1&page_index='+str(page)
如果修改的內(nèi)容不使用str字符串轉(zhuǎn)化,會(huì)收到如下報(bào)錯(cuò):
TypeError: can only concatenate str (not "int") to str
然后我們看一下頁(yè)面內(nèi)容的分布情況,本次我們關(guān)心賣什么書,賣多少錢?

網(wǎng)站源碼

對(duì)應(yīng)的編寫我們的正則匹配規(guī)則,當(dāng)然了,有更簡(jiǎn)便的第三方庫(kù)可以幫我們處理,但為了更好的形成流程性認(rèn)識(shí),我們這里依然使用正則。
我們對(duì)應(yīng)我們需要的書籍名稱和當(dāng)前價(jià)格匹配如下:
<a title=" (.*?)" ddclick=
<span class="search_now_price">&yen;(.*?)</span>
那么,思路理清了,我們就開始使用多線程來寫我們的小系統(tǒng)~

import requests
import re
from multiprocessing import Pool, Manager

headers = {'user-agent': 'ceshi/0.0.1'}

#信息爬取模塊
def getInfo(page,yourkey):
    r = requests.get('http://search.dangdang.com/?key='+str(yourkey)+'&ddsale=1&page_index='+str(page) , headers=headers)
    r1=re.compile(r'<a title=" (.*?)"  ddclick=')
    r2=re.compile(r'<span class="search_now_price">&yen;(.*?)</span>')
    re1 = r1.findall(r.text)
    re2 = r2.findall(r.text)
    return dict(zip(re1, re2))              #將列表轉(zhuǎn)為字典

#文件存儲(chǔ)模塊
def saveinfo(page,yourkey,q):
    fw = open('DangDang.txt', 'a')
    di=getInfo(page,yourkey)                #新建字典接收返回結(jié)果
    for i in di:                            #整理格式寫入文件
        fw.write('書籍名稱:'+i+'\t'+'當(dāng)前價(jià)格'+di[i]+'\n')
    fw.close()
    q.put(page)

#進(jìn)程池管理模塊
def poolmana(pages,yourkey):
    p = Pool(10)
    q = Manager().Queue()
    for i in range(pages):
        p.apply_async(saveinfo, args=(i+1, yourkey,q))
    p.close()
    p.join()
    print('讀取完成>>>>>\n請(qǐng)查看當(dāng)前路徑下文件:DangDang.txt')

#助手函數(shù),輸入判斷
def is_number(s):                           #當(dāng)了個(gè)助手函數(shù)用來判斷用戶輸入內(nèi)容
    try:
        float(s)
        return True
    except ValueError:
        pass
    try:
        import unicodedata
        unicodedata.numeric(s)
        return True
    except (TypeError, ValueError):
        pass
    return False

#主函數(shù),實(shí)現(xiàn)界面
def main():
    print('''
            ============================================
             ||  【歡迎來到史上最屌的當(dāng)當(dāng)網(wǎng)查詢系統(tǒng)】  ||
             ||                (輸入exit退出)      ||
            ============================================
                                    ''')
    while True:
        try:
            yourkey=input('請(qǐng)輸入您要查詢的內(nèi)容:')
            if yourkey=='exit':
                break
            pa=input('請(qǐng)輸入希望檢索的頁(yè)數(shù):\n(默認(rèn)為3)\n')
            if pa=='exit':
                break
            if is_number(pa)==False:        #使用助手函數(shù)判斷輸入是否為數(shù)字,如否,使用默認(rèn)值3
                pa=3
            print('讀取ing~>>>>>>\n數(shù)據(jù)量較大,請(qǐng)耐心等待>>>>>')
            poolmana(int(pa),str(yourkey))
        except:
            print("請(qǐng)規(guī)范您的輸入!")

if "__main__" == __name__ :
    main()

#輸出:

            ============================================
             ||  【歡迎來到史上最屌的當(dāng)當(dāng)網(wǎng)查詢系統(tǒng)】  ||
             ||                 (輸入exit退出)     ||
            ============================================
                                    
請(qǐng)輸入您要查詢的內(nèi)容:滲透測(cè)試
請(qǐng)輸入希望檢索的頁(yè)數(shù):
(默認(rèn)為3)

讀取ing~>>>>>>
數(shù)據(jù)量較大,請(qǐng)耐心等待>>>>>
讀取完成>>>>>
請(qǐng)查看當(dāng)前路徑下文件:DangDang.txt
請(qǐng)輸入您要查詢的內(nèi)容:exit

Process finished with exit code 0

然后我們?nèi)ゲ榭匆幌挛覀兊慕Y(jié)果文件~

DangDang.txt

現(xiàn)在這個(gè)小系統(tǒng)具備的功能就是根據(jù)用戶需要選擇要檢索的書籍,然后整理下名稱和價(jià)格,開了10個(gè)線程,如果小伙伴pc給力的話可以繼續(xù)加。簡(jiǎn)單的異常處理機(jī)制和界面交互,基本滿足日常所需。

emmmmm,py入門系列到此結(jié)束!當(dāng)初立的flag并沒有垮掉!
flag

小結(jié)一下,本次py學(xué)習(xí)應(yīng)該是從1.11號(hào)開始的,完全0基礎(chǔ)開始,砍去出差、周末時(shí)間,工作之余零零散散的學(xué)習(xí)時(shí)間應(yīng)該在十天左右,所以十天的時(shí)間用點(diǎn)心的話,自學(xué)python應(yīng)該是沒有問題的。

關(guān)于學(xué)習(xí)材料的話,廖雪峰老師的教程配上菜鳥教程的基本理論,再來幾個(gè)可以幫你解答疑惑的py大佬,足夠了。雖然較專業(yè)開發(fā)與框架運(yùn)用還有很大差距,但,py的征途已經(jīng)開始了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容