PyQt5筆記1:詞云小程序設(shè)計(jì)后記

詞云小程序設(shè)計(jì)并不難,但在封裝時(shí)卻碰到了一個(gè)大坑。歷經(jīng)一個(gè)多小時(shí)的搜索,問題才得以解決。做個(gè)后記,以備后用。

預(yù)期功能

  • 打開任意一個(gè)UTF-8編碼的文本文件和一張圖片,能按圖片輪廓生成詞云;
  • 生成詞云時(shí),能停用一些無用的關(guān)鍵詞。

開發(fā)工具

  • Python 3.8.6
  • PyCharm 2021.1.1
  • Qt Designer
  • PyUIC

需要加載的庫(kù)

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

UI界面的設(shè)計(jì)

  1. 使用Qt Designer進(jìn)行界面設(shè)計(jì)和布局。得到界面圖如下:


    UI界面圖
  1. 使用擴(kuò)展工具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", "酷炫詞云 程序設(shè)計(jì):王立武"))
        self.label.setText(_translate("MainWindow", "酷炫詞云"))
        self.label_2.setText(_translate("MainWindow", "程序設(shè)計(jì):王立武  2021年6月8日"))
        self.label_3.setText(_translate("MainWindow", "打開生成詞云文本"))
        self.lineEdit.setPlaceholderText(_translate("MainWindow", "請(qǐng)打開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", "注意:每個(gè)關(guān)鍵詞之間用空格隔開!"))
        self.pushButton_3.setText(_translate("MainWindow", "過濾文本"))
        self.pushButton_4.setText(_translate("MainWindow", "生成詞云"))
        self.label_5.setText(_translate("MainWindow", "詞云圖片展示區(qū)"))

主程序的建立

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

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

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

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

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

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

    def show_tuyu(self):  # 生成圖云
        pass
        
if __name__ == "__main__":
    app = QApplication(sys.argv)  # 實(shí)例化一個(gè)QApplication對(duì)象
    myWindow = MyWindow()  # 實(shí)例化自定義的窗口類
    myWindow.show()  # 展示窗口
    sys.exit(app.exec_())  # 啟動(dòng)事件循環(huán),并將退出碼傳遞給系統(tǒng)退出方法
  1. 添加信號(hào)和槽機(jī)制,代碼如下:
        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. 完善每個(gè)槽函數(shù)的功能設(shè)計(jì)。
    a. 打開文本文件功能設(shè)計(jì)(view_txt)。因常見的文本文件編碼格式有幾種方式,在項(xiàng)目中指定打開的編碼方式為UTF-8,因此在打開其他編碼(如:ANSI)時(shí),會(huì)報(bào)錯(cuò),故在代碼中加入異常處理,提醒要求打開的文本文件為UTF-8編碼。


    編碼格式不對(duì)時(shí)的提示對(duì)話框

具體代碼如下:

    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,'提示','請(qǐng)選擇一個(gè)文本文件!\n或者將文本文件的編碼改為utf-8')

b. 打開圖片文件功能設(shè)計(jì)(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. 過濾關(guān)鍵詞文本功能設(shè)計(jì)(filter_key)。具體代碼如下:

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

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

未打開文件時(shí)的異常處理

具體代碼如下:

    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ù)圖片繪制,則需要設(shè)置
                    font_path='simhei.ttf',  # 可以改成自己喜歡的字體
                    background_color='white',  # 詞云圖背景顏色可以換成自己喜歡的顏色
                )
            else:
                wd = wordcloud.WordCloud(
                    mask=img,  # 背景圖形,如果根據(jù)圖片繪制,則需要設(shè)置
                    font_path='simhei.ttf',  # 可以改成自己喜歡的字體
                    stopwords=stopwords,
                    background_color='white',  # 詞云圖背景顏色可以換成自己喜歡的顏色
                )
            wd.generate(word)
            plt.imshow(wd)
            plt.axis('off')  # 關(guān)閉顯示x軸、y軸下標(biāo)
            # 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, "提示", "請(qǐng)先打開文本文件和輪廓圖片文件后\n再運(yùn)行生成詞云!")

因?yàn)檫@只是一個(gè)練手的項(xiàng)目,所以沒有過多去考慮把生成的詞云圖片都保存下來的想法,只是簡(jiǎn)單的寫了一句保存圖片代碼:

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

實(shí)際上,在這里利用獲取生成圖片的當(dāng)前日期和時(shí)間為文件名,更為妥當(dāng)。這個(gè)代碼,這里就不給出了。

測(cè)試運(yùn)行

  • 效果圖如下:


    測(cè)試運(yùn)行效果圖

封裝小程序

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

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

封裝完成,沒有報(bào)錯(cuò),本以為大功告成了,但在運(yùn)行程序時(shí),卻報(bào)錯(cuò)了,如下圖:

封裝后報(bào)錯(cuò)信息

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

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

將這句代碼改為:

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

建議在做此步操作時(shí),對(duì)原文件做好備份,以防不測(cè)。完成這些操作后,再次進(jìn)行封裝,運(yùn)行,終于完成了。

最后編輯于
?著作權(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)容