PyQt5筆記1:詞云小程序設計后記

詞云小程序設計并不難埋酬,但在封裝時卻碰到了一個大坑厌漂。歷經(jīng)一個多小時的搜索,問題才得以解決斟珊。做個后記苇倡,以備后用富纸。

預期功能

  • 打開任意一個UTF-8編碼的文本文件和一張圖片,能按圖片輪廓生成詞云旨椒;
  • 生成詞云時晓褪,能停用一些無用的關鍵詞。

開發(fā)工具

  • Python 3.8.6
  • PyCharm 2021.1.1
  • Qt Designer
  • PyUIC

需要加載的庫

  • sys 系統(tǒng)內(nèi)置庫
  • OpenCV 一款開源的計算機視覺和機器學習軟件庫
  • matplotlib 一款 Python 的繪圖庫
  • jieba 一款優(yōu)秀的 Python 第三方中文分詞庫
  • wordcloud 一款優(yōu)秀的詞云展示第三方庫
  • PyQt5 Python中一款優(yōu)秀的GUI界面模塊

UI界面的設計

  1. 使用Qt Designer進行界面設計和布局综慎。得到界面圖如下:


    UI界面圖
  1. 使用擴展工具PyUIC將UI界面文件Txt_Image.ui轉(zhuǎn)為Txt_Image.py文件涣仿。代碼如下:
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'Txt_Image.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(958, 758)
        MainWindow.setMinimumSize(QtCore.QSize(800, 600))
        MainWindow.setMaximumSize(QtCore.QSize(16777215, 16777215))
        MainWindow.setStyleSheet("font: 10pt \"微軟雅黑\";")
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.formLayout = QtWidgets.QFormLayout(self.centralwidget)
        self.formLayout.setObjectName("formLayout")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout()
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout()
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setStyleSheet("font: 57 40pt \"李旭科書法 v1.4\";\n"
"color: rgb(85, 170, 0);")
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout_3.addWidget(self.label)
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setAlignment(QtCore.Qt.AlignCenter)
        self.label_2.setObjectName("label_2")
        self.verticalLayout_3.addWidget(self.label_2)
        self.verticalLayout_2.addLayout(self.verticalLayout_3)
        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox.setTitle("")
        self.groupBox.setAlignment(QtCore.Qt.AlignCenter)
        self.groupBox.setObjectName("groupBox")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.label_3 = QtWidgets.QLabel(self.groupBox)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
        self.lineEdit = QtWidgets.QLineEdit(self.groupBox)
        self.lineEdit.setObjectName("lineEdit")
        self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
        self.pushButton = QtWidgets.QPushButton(self.groupBox)
        self.pushButton.setObjectName("pushButton")
        self.gridLayout.addWidget(self.pushButton, 0, 2, 1, 1)
        self.label_4 = QtWidgets.QLabel(self.groupBox)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
        self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox)
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.gridLayout.addWidget(self.lineEdit_2, 1, 1, 1, 1)
        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton_2.setObjectName("pushButton_2")
        self.gridLayout.addWidget(self.pushButton_2, 1, 2, 1, 1)
        self.horizontalLayout_3.addLayout(self.gridLayout)
        self.verticalLayout_2.addWidget(self.groupBox)
        self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_2.setTitle("")
        self.groupBox_2.setObjectName("groupBox_2")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.textEdit = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit.setObjectName("textEdit")
        self.verticalLayout.addWidget(self.textEdit)
        self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit_2.setObjectName("textEdit_2")
        self.verticalLayout.addWidget(self.textEdit_2)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton_3 = QtWidgets.QPushButton(self.groupBox_2)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(self.groupBox_2)
        self.pushButton_4.setObjectName("pushButton_4")
        self.horizontalLayout.addWidget(self.pushButton_4)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.verticalLayout.setStretch(0, 4)
        self.verticalLayout.setStretch(1, 1)
        self.horizontalLayout_2.addLayout(self.verticalLayout)
        self.widget = QtWidgets.QWidget(self.groupBox_2)
        self.widget.setObjectName("widget")
        self.gridLayout_3 = QtWidgets.QGridLayout(self.widget)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.label_5 = QtWidgets.QLabel(self.widget)
        self.label_5.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.label_5.setScaledContents(False)
        self.label_5.setAlignment(QtCore.Qt.AlignCenter)
        self.label_5.setObjectName("label_5")
        self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
        self.horizontalLayout_2.addWidget(self.widget)
        self.horizontalLayout_2.setStretch(0, 1)
        self.horizontalLayout_2.setStretch(1, 2)
        self.gridLayout_2.addLayout(self.horizontalLayout_2, 0, 0, 1, 1)
        self.verticalLayout_2.addWidget(self.groupBox_2)
        self.formLayout.setLayout(0, QtWidgets.QFormLayout.SpanningRole, self.verticalLayout_2)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.label_3.setBuddy(self.lineEdit)
        self.label_4.setBuddy(self.lineEdit_2)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "酷炫詞云 程序設計:王立武"))
        self.label.setText(_translate("MainWindow", "酷炫詞云"))
        self.label_2.setText(_translate("MainWindow", "程序設計:王立武  2021年6月8日"))
        self.label_3.setText(_translate("MainWindow", "打開生成詞云文本"))
        self.lineEdit.setPlaceholderText(_translate("MainWindow", "請打開UTF-8編碼的TXT格式的文本文件"))
        self.pushButton.setText(_translate("MainWindow", "瀏覽"))
        self.label_4.setText(_translate("MainWindow", "打開詞云輪廓圖片"))
        self.lineEdit_2.setPlaceholderText(_translate("MainWindow", "建議使用白底的圖片以得至更好的輪廓詞云圖"))
        self.pushButton_2.setText(_translate("MainWindow", "瀏覽"))
        self.textEdit.setPlaceholderText(_translate("MainWindow", "在這里粘貼的文字不能生成詞云!"))
        self.textEdit_2.setPlaceholderText(_translate("MainWindow", "注意:每個關鍵詞之間用空格隔開示惊!"))
        self.pushButton_3.setText(_translate("MainWindow", "過濾文本"))
        self.pushButton_4.setText(_translate("MainWindow", "生成詞云"))
        self.label_5.setText(_translate("MainWindow", "詞云圖片展示區(qū)"))

主程序的建立

  1. 導入相關的庫好港,代碼如下:
import sys
import cv2  # 導入圖片處理模塊 需安裝庫OpenCV。
import matplotlib.pyplot as plt
import jieba  # 導入jieba分詞模塊
import wordcloud  # 導入詞云圖模塊
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import * 
from Txt_Image import Ui_MainWindow  # 加載UI界面Txt_Image 米罚,實現(xiàn)UI界面和代碼分離

使用 from Txt_Image import Ui_MainWindow 的目的是為了實現(xiàn)UI界面和代碼的分離钧汹,以便后續(xù)的修改和維護。

  1. 搭建主程序基本框架录择,相關代碼如下:
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):  # 分離時拔莱,要加載UI界面類
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)  # 調(diào)用父類的構造方法
        self.setupUi(self)  # 調(diào)用構建子控件的方法

    def view_txt(self):  # 打開文本文件
        pass

    def view_image(self):  # 打開背景輪廓圖片文件
        pass

    def filter_key(self):  # 過濾不生成圖云的關鍵詞
        psss

    def show_tuyu(self):  # 生成圖云
        pass
        
if __name__ == "__main__":
    app = QApplication(sys.argv)  # 實例化一個QApplication對象
    myWindow = MyWindow()  # 實例化自定義的窗口類
    myWindow.show()  # 展示窗口
    sys.exit(app.exec_())  # 啟動事件循環(huán),并將退出碼傳遞給系統(tǒng)退出方法
  1. 添加信號和槽機制,代碼如下:
        self.pushButton.clicked.connect(self.view_txt)
        self.pushButton_2.clicked.connect(self.view_image)
        self.pushButton_3.clicked.connect(self.filter_key)
        self.pushButton_4.clicked.connect(self.show_tuyu)
  1. 完善每個槽函數(shù)的功能設計隘竭。
    a. 打開文本文件功能設計(view_txt)塘秦。因常見的文本文件編碼格式有幾種方式,在項目中指定打開的編碼方式為UTF-8,因此在打開其他編碼(如:ANSI)時动看,會報錯尊剔,故在代碼中加入異常處理,提醒要求打開的文本文件為UTF-8編碼弧圆。


    編碼格式不對時的提示對話框

具體代碼如下:

    def view_txt(self):  # 打開文本文件
        try:
            global file_content
            file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "選取文件", "./", "*.txt")
            self.lineEdit.setText(file_name)
            file_content = open(file_name, "r",encoding='utf-8').read()
            self.textEdit.setText(file_content)
            # print(file_content)
        except:
            QMessageBox.critical(self,'提示','請選擇一個文本文件赋兵!\n或者將文本文件的編碼改為utf-8')

b. 打開圖片文件功能設計(view_image)。具體代碼如下:

    def view_image(self):  # 打開背景輪廓圖片文件
        global image_name
        image_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "選取文件", "./", "*.png;;*.jpg;;*.jpeg")
        self.lineEdit_2.setText(image_name)

c. 過濾關鍵詞文本功能設計(filter_key)搔预。具體代碼如下:

    def filter_key(self):  # 過濾不生成圖云的關鍵詞
        global stopwords
        stopwords = set('')
        filter_values = self.textEdit_2.toPlainText()
        update_key = filter_values.split(" ")
        # print(update_key)
        stopwords.update(update_key)

這個槽函數(shù)中 stopwords 的實際為以后封裝失敗埋下了根源霹期,后文再說。
代碼 update_key = filter_values.split(" ") 的目的拯田,是將用戶在文本編輯框中輸入的停用關鍵詞历造,利用空格作為分隔符生成一個列表,以供在生成詞云時調(diào)用船庇。
d. 生成詞云圖功能設計(show_tuyu)吭产。因用戶有可能在沒有打開文本文件或圖像文件時,程序會報錯鸭轮,所以在這里也加入了異常處理臣淤,以提醒使用者。如圖

未打開文件時的異常處理

具體代碼如下:

    def show_tuyu(self):  # 生成圖云
        try:
            cut_text = jieba.cut(file_content)
            word = ' '.join(cut_text)
            img = cv2.imread(image_name)  # 讀取數(shù)據(jù)
            if self.textEdit_2.toPlainText() == "":
                wd = wordcloud.WordCloud(
                    mask=img,  # 背景圖形,如果根據(jù)圖片繪制窃爷,則需要設置
                    font_path='simhei.ttf',  # 可以改成自己喜歡的字體
                    background_color='white',  # 詞云圖背景顏色可以換成自己喜歡的顏色
                )
            else:
                wd = wordcloud.WordCloud(
                    mask=img,  # 背景圖形,如果根據(jù)圖片繪制邑蒋,則需要設置
                    font_path='simhei.ttf',  # 可以改成自己喜歡的字體
                    stopwords=stopwords,
                    background_color='white',  # 詞云圖背景顏色可以換成自己喜歡的顏色
                )
            wd.generate(word)
            plt.imshow(wd)
            plt.axis('off')  # 關閉顯示x軸姓蜂、y軸下標
            # plt.show()
            plt.savefig('.\images\chiyu.png', dpi=100)
            jpg = QtGui.QPixmap("./images/chiyu.png")
            self.label_5.setScaledContents(True)
            self.label_5.setPixmap(jpg)
        except:
            QMessageBox.critical(self, "提示", "請先打開文本文件和輪廓圖片文件后\n再運行生成詞云!")

因為這只是一個練手的項目医吊,所以沒有過多去考慮把生成的詞云圖片都保存下來的想法钱慢,只是簡單的寫了一句保存圖片代碼:

plt.savefig('.\images\chiyu.png', dpi=100)

實際上,在這里利用獲取生成圖片的當前日期和時間為文件名卿堂,更為妥當束莫。這個代碼,這里就不給出了草描。

測試運行

  • 效果圖如下:


    測試運行效果圖

封裝小程序

前面的工作都很順利览绿,沒有遇到什么麻煩,但封裝時卻碰到了一個大問題陶珠。
回到命令行環(huán)境挟裂,并轉(zhuǎn)到相應的路徑下,運行如下代碼開始封裝:

pyinstaller -F -w Tuyun.py  # Tuyun.py為主程序文件名

封裝完成揍诽,沒有報錯诀蓉,本以為大功告成了,但在運行程序時暑脆,卻報錯了渠啤,如下圖:

封裝后報錯信息

出這種錯的原因,大多是外加的資源文件沒有添加到生成EXE文件的目錄中添吗,但此程序沒有加入圖標等資源文件沥曹。原因到底在哪呢?
通過某娘搜索得知出在詞云庫 wordcloud 中碟联,解決辦法:

  1. 找到詞云庫 wordcloud 的安裝目錄妓美,將文件夾中的 stopwords 文件拷貝到生成EXE的文件夾中。
  2. 找到詞云庫 wordcloud 的安裝目錄鲤孵,打開文件夾中的 wordcloud.py 文件壶栋,找到第33行:
FILE = os.path.dirname(__file__)

將這句代碼改為:

FILE = os.path.dirname(sys.executable)

建議在做此步操作時,對原文件做好備份普监,以防不測贵试。完成這些操作后,再次進行封裝凯正,運行毙玻,終于完成了。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廊散,一起剝皮案震驚了整個濱河市桑滩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌允睹,老刑警劉巖运准,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件往声,死亡現(xiàn)場離奇詭異,居然都是意外死亡戳吝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門贯涎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來听哭,“玉大人,你說我怎么就攤上這事塘雳÷脚蹋” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵败明,是天一觀的道長隘马。 經(jīng)常有香客問我,道長妻顶,這世上最難降的妖魔是什么酸员? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讳嘱,結果婚禮上幔嗦,老公的妹妹穿的比我還像新娘。我一直安慰自己沥潭,他們只是感情好邀泉,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钝鸽,像睡著了一般汇恤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拔恰,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天因谎,我揣著相機與錄音,去河邊找鬼仁连。 笑死蓝角,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饭冬。 我是一名探鬼主播使鹅,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昌抠!你這毒婦竟也來了患朱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤炊苫,失蹤者是張志新(化名)和其女友劉穎裁厅,沒想到半個月后冰沙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡执虹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年拓挥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袋励。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡侥啤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茬故,到底是詐尸還是另有隱情盖灸,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布磺芭,位于F島的核電站赁炎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钾腺。R本人自食惡果不足惜徙垫,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望放棒。 院中可真熱鬧松邪,春花似錦、人聲如沸哨查。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寒亥。三九已至邮府,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溉奕,已是汗流浹背褂傀。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留加勤,地道東北人仙辟。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鳄梅,于是被迫代替她去往敵國和親叠国。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容