Python入门系列(十二)——GUI+多进程

不好意思,拖堂了,此为终章,补充蛮重要的两点
一、GUI
二、多进程

一、GUI

话说,python做图形界面并不明智,效率并不高。但在某些特殊需求下还是需要我们去使用,所以python拥有多个第三方库用以实现GUI,本章我们使用python基本模块tkinter进行学习,因为需求并不大,所以不做太多拓展。
继续改写上一章的IP查询系统(= =,要玩烂了),首先略改下IpWhere.py以备调用~

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 '目标IP为:{}\n物理地址为:{}'.format(ip,adr)

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

然后使用tkinter模块进行图形界面的实现,调用预编译的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='点击查询', command=self.ipis)
        self.alertButton.pack()

    def ipis(self):
        try:
            res = self.nameInput.get()
            messagebox.showinfo('查询结果','{}' .format(ip_adr(res)))
        except:
            messagebox.showinfo('查询结果', '乖,好好输入')

app = Application()
app.master.title('IP查询系统')
app.mainloop()

额,太丑了,但基本实现我们小小的需求,在以后的py学习中,我们再涉及其他的第三方模块,此处就当是入门了解吧。

《Python入门系列(十二)——GUI+多进程》 正确哒
《Python入门系列(十二)——GUI+多进程》 错误哒

二、多进程

十分抱歉把这么重要的内容放在最后,要不是大佬指点,此次学习可能就要错过多进程的问题了。
Unix系统提供了forx,python可借助os模块调用,从而实现多进程,然而windows系统并不具备,所以我们选择python内置的multiprocessing多进程模块进行学习。

友情提示:请先自行补充线程、进程基本概念。

首先我们借助直接调用多进程来改写下我们在多线程章节用到的例子!

import time
from multiprocessing import Process

def eating():
    print('吃饭时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来吃饭
    print('吃饱啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def sleeping():
    print('睡觉时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来睡觉
    print('醒啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def hitting():
    print('打豆豆时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来打豆豆
    print('打出翔了!当前时间:{}'.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()

#输出:
吃饭时间到!当前时间:2019-01-29 00:16:32
吃饱啦!当前时间:2019-01-29 00:16:34
睡觉时间到!当前时间:2019-01-29 00:16:34
醒啦!当前时间:2019-01-29 00:16:36
打豆豆时间到!当前时间:2019-01-29 00:16:36
打出翔了!当前时间:2019-01-29 00:16:38

显然,这么写实在太蠢了,如果我们的任务量巨大,这并不合适。所以我们引入了进程池的概念,使用进程池进行改写:

import time
from multiprocessing import Pool


def eating():
    print('吃饭时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来吃饭
    print('吃饱啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def sleeping():
    print('睡觉时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来睡觉
    print('醒啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
def hitting():
    print('打豆豆时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    time.sleep(2)   #休眠两秒钟用来打豆豆
    print('打出翔了!当前时间:{}'.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()

#输出:
吃饭时间到!当前时间:2019-01-29 00:23:02
睡觉时间到!当前时间:2019-01-29 00:23:02
打豆豆时间到!当前时间:2019-01-29 00:23:02
吃饱啦!当前时间:2019-01-29 00:23:04
醒啦!当前时间:2019-01-29 00:23:04
打出翔了!当前时间:2019-01-29 00:23:04

在此,我们可以看到所有进程是并发执行的,同样,我们在多线程章节就讲过,主进程的结束意味着程序退出,所以我们需要借助join()方法堵塞进程。

进程间通信

我们知道线程共享内存空间,而进程的内存是独立的,同一个进程的线程之间可以直接交流,也就带来了线程同步的苦恼,这个我们在多线程章节已经讲过了;而两个进程想通信,则必须通过一个中间代理来实现,即我们接下来的内容:进程间通信。

进程之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。我们接下来就以Queue的方式进行学习。

Queue.Queue是进程内非阻塞队列,multiprocess.Queue是跨进程通信队列,前者是各自私有,后者是各子进程共有。

还有一个在后者基础上进行封装的multiprocess.Manager.Queue()方法,如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到一条如下的错误信息:RuntimeError: Queue objects should only be shared between processes through inheritance.

接下来我们就借助进程池来进行多线程操作的改写,感谢大佬一路辅导。

import time
import os
from multiprocessing import Manager,Pool

def eating(q):
    print('吃饭时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠两秒钟用来吃饭
    print('吃饱啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    q.get()
def sleeping(q):
    print('睡觉时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠两秒钟用来睡觉
    print('醒啦!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    q.get()
def hitting(q):
    print('打豆豆时间到!当前时间:{}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
    print('Process to running: %s' % os.getpid())
    time.sleep(2)   #休眠两秒钟用来打豆豆
    print('打出翔了!当前时间:{}'.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)                   #创建进程池
    q=Manager().Queue(2)        #创建队列,为了演示更清晰,我们故意设两个队列
    for i in ["eating","sleeping","hitting"]:
        q.put(i)                #添加队列
        print("队列添加成功")
        p.apply_async(working, args=(i,q))

    #p.close()
    #p.join()
    while True:                 #用上方注释的jion()亦可,此处使用循环堵塞队列
        if q.empty():           #如队列为空,break,循环结束,主线程结束
            break

if __name__=='__main__':
    main()

#输出:
队列添加成功
队列添加成功
睡觉时间到!当前时间:2019-01-29 15:25:36
Process to running: 11872
吃饭时间到!当前时间:2019-01-29 15:25:36
Process to running: 25284
吃饱啦!当前时间:2019-01-29 15:25:38
醒啦!当前时间:2019-01-29 15:25:38
队列添加成功
打豆豆时间到!当前时间:2019-01-29 15:25:38
Process to running: 18536
打出翔了!当前时间:2019-01-29 15:25:40

Process finished with exit code 0

我们可以看到两个子线程先执行,然后一个子线程单独执行,此处有意而为之,让大家更清晰的了解队列的使用。期间有一处我们放弃使用jion()方法堵塞,而是自己写了个循环堵塞,大家根据自己习惯来就好。

实例拓展

话说,真的没人吐槽么?上面的例子从需求上来讲,完全就不需要多线程好不好!emmmm,我们来点实力拓展,写一个有智商的多线程脚本,顺便结合上一节的web来一个综合篇,随便找个现实需求吧!

emmm,比如我们来到当当网买书,搜一下我们想要的书籍,发现!!太多了!!真J2乱!!看不过来!!不想翻页!!直接告诉我哪个便宜、哪个牛逼好不好!!

《Python入门系列(十二)——GUI+多进程》 当当网

简单看下这个url:
http://search.dangdang.com/?key=渗透测试&ddsale=1&page_index=2
其中ddsale参数代表当当自营,page_index代表页数,key代表搜索内容,我们本次的变量只有页数。

所以我们构造请求的url为:
'http://search.dangdang.com/?key=渗透测试&ddsale=1&page_index='+str(page)
如果修改的内容不使用str字符串转化,会收到如下报错:
TypeError: can only concatenate str (not "int") to str
然后我们看一下页面内容的分布情况,本次我们关心卖什么书,卖多少钱?

《Python入门系列(十二)——GUI+多进程》 网站源码

对应的编写我们的正则匹配规则,当然了,有更简便的第三方库可以帮我们处理,但为了更好的形成流程性认识,我们这里依然使用正则。
我们对应我们需要的书籍名称和当前价格匹配如下:
<a title=" (.*?)" ddclick=
<span class="search_now_price">&yen;(.*?)</span>
那么,思路理清了,我们就开始使用多线程来写我们的小系统~

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))              #将列表转为字典

#文件存储模块
def saveinfo(page,yourkey,q):
    fw = open('DangDang.txt', 'a')
    di=getInfo(page,yourkey)                #新建字典接收返回结果
    for i in di:                            #整理格式写入文件
        fw.write('书籍名称:'+i+'\t'+'当前价格'+di[i]+'\n')
    fw.close()
    q.put(page)

#进程池管理模块
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请查看当前路径下文件:DangDang.txt')

#助手函数,输入判断
def is_number(s):                           #当了个助手函数用来判断用户输入内容
    try:
        float(s)
        return True
    except ValueError:
        pass
    try:
        import unicodedata
        unicodedata.numeric(s)
        return True
    except (TypeError, ValueError):
        pass
    return False

#主函数,实现界面
def main():
    print('''
            ============================================
             ||  【欢迎来到史上最屌的当当网查询系统】  ||
             ||                (输入exit退出)      ||
            ============================================
                                    ''')
    while True:
        try:
            yourkey=input('请输入您要查询的内容:')
            if yourkey=='exit':
                break
            pa=input('请输入希望检索的页数:\n(默认为3)\n')
            if pa=='exit':
                break
            if is_number(pa)==False:        #使用助手函数判断输入是否为数字,如否,使用默认值3
                pa=3
            print('读取ing~>>>>>>\n数据量较大,请耐心等待>>>>>')
            poolmana(int(pa),str(yourkey))
        except:
            print("请规范您的输入!")

if "__main__" == __name__ :
    main()

#输出:

            ============================================
             ||  【欢迎来到史上最屌的当当网查询系统】  ||
             ||                 (输入exit退出)     ||
            ============================================
                                    
请输入您要查询的内容:渗透测试
请输入希望检索的页数:
(默认为3)

读取ing~>>>>>>
数据量较大,请耐心等待>>>>>
读取完成>>>>>
请查看当前路径下文件:DangDang.txt
请输入您要查询的内容:exit

Process finished with exit code 0

然后我们去查看一下我们的结果文件~

《Python入门系列(十二)——GUI+多进程》 DangDang.txt

现在这个小系统具备的功能就是根据用户需要选择要检索的书籍,然后整理下名称和价格,开了10个线程,如果小伙伴pc给力的话可以继续加。简单的异常处理机制和界面交互,基本满足日常所需。

emmmmm,py入门系列到此结束!当初立的flag并没有垮掉!

《Python入门系列(十二)——GUI+多进程》 flag

小结一下,本次py学习应该是从1.11号开始的,完全0基础开始,砍去出差、周末时间,工作之余零零散散的学习时间应该在十天左右,所以十天的时间用点心的话,自学python应该是没有问题的。

关于学习材料的话,廖雪峰老师的教程配上菜鸟教程的基本理论,再来几个可以帮你解答疑惑的py大佬,足够了。虽然较专业开发与框架运用还有很大差距,但,py的征途已经开始了~

    原文作者:RabbitMask
    原文地址: https://www.jianshu.com/p/fcfe1a140b15
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞