如何在Python中的gui和non-gui应用程序中使用QThread?

时间:2017-11-16 10:59:20

标签: python-3.x pyqt5 qthread

我正在编写一个模糊搜索应用程序,即使单词有错误也能在文本中查找单词。我写了gui表格但由于计算量很大而冻结了。所以我创建了继承自QThread的类,并将信号从它发送到gui,以便进度条开始工作,gui形式不再冻结。但我也应该创建这个应用程序的控制台版本,我不需要gui表单,但我需要在QThread继承的类中编写的方法。但是如果没有使用在控制台版本中使用的奇怪的PyQT库,那是不可能的。所以我不知道如何解决这个问题。我的老师建议使用线程,但是我没有找到如何从QThread中的colud那样从Thread类发出信号。

这是一个QThread类

import text_methods
from PyQt5.QtCore import pyqtSignal, QThread


class FuzzySearch(QThread):
    sig_words_count = pyqtSignal(int)
    sig_step = pyqtSignal(int)
    sig_done = pyqtSignal(bool)
    sig_insertions = pyqtSignal(str)
    sig_insertions_indexes = pyqtSignal(list)

    def __init__(self, text, words, case_sensitive):
        super().__init__()
        self.text = text
        self.words = words
        self.case_sensitive = case_sensitive
        self.insertions_indexes = {}
        self.text_dict = {}

    def run(self):
        self.get_insertions_info(self.text, self.words)

    def find_insertions_of_word(self, word, word_number):
        word_insertions = {}
        for textword in self.text_dict.keys():
            if text_methods.is_optimal_distance(word, textword):
                word_insertions[textword] = self.text_dict[textword]
                for index in self.text_dict[textword]:
                    self.insertions_indexes[index] = index + len(textword)
        self.sig_step.emit(word_number)
        return word_insertions

    '''Get information about insertions of words in the text'''
    def find_insertions(self, text, words):
        word_number = 1
        insertions = {}
        self.text_dict = text_methods.transform_text_to_dict(text, self.case_sensitive)
        words_list = text_methods.transform_words_to_list(words, self.case_sensitive)
        self.sig_words_count.emit(len(words_list))
        for word in words_list:
            print(word_number)
            insertions[word] = self.find_insertions_of_word(word, word_number)
            word_number += 1
        self.insertions_indexes = sorted(self.insertions_indexes.items())
        return insertions

    '''Get information about insertions of words in the text in special format'''
    def get_insertions_info(self, text, words):
        insertions = self.find_insertions(text, words)
        insertions_info = ''
        for word in insertions.keys():
            insertions_info += 'Вы искали слово "' + word + '"\n'
            if len(insertions[word]) == 0:
                insertions_info += '  По этому запросу не было найдено слов\n'
            else:
                insertions_info += '  По этому запросу были найдены слова:\n'
                for textword in insertions[word].keys():
                    insertions_info += '   "' + textword + '" на позициях: '
                    insertions_info += ", ".join([str(i) for i in insertions[word][textword]])
                    insertions_info += '\n'
        self.sig_done.emit(True)
        self.sig_insertions.emit(insertions_info)
        self.sig_insertions_indexes.emit(self.insertions_indexes)
        self.quit()

如你所见,有很多发出的信号我转移到gui模块,它们连接到方法find_insertions中的类FindButton中的插槽:

from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QApplication, QWidget,\
                            QLabel, QPushButton, QTextEdit, QFileDialog,\
                            QMessageBox, QProgressBar, QCheckBox
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from fuzzysearch import FuzzySearch
import sys


class OpenButton(QPushButton):
    def __init__(self, name, font, textedit):
        super().__init__(name, font=font)
        self.textedit = textedit
        self.clicked.connect(self.open_dialog)

    def open_dialog(self):
        fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
        if fname[0]:
            with open(fname[0], 'r') as f:
                data = f.read()
                self.textedit.setText(data)


class FindButton(QPushButton):
    def __init__(self, name, font, text, words, result, window):
        super().__init__(name, font=font)
        self.window = window
        self.textedit = text
        self.wordsedit = words
        self.resultedit = result
        self.checkbox = window.case_sensitive_checkbox
        self.clicked.connect(self.find_insertions)

    def find_insertions(self):
        text = self.textedit.toPlainText()
        words = self.wordsedit.toPlainText()
        if text == '':
            QMessageBox.information(self, 'Нет текста',
                                    'Текст не был введен. \nВведите текст.')
        elif words == '':
            QMessageBox.information(self, 'Нет слов',
                                    'Слова не были введены. \nВведите слова через запятую.')
        else:
            self.setDisabled(True)
            self.text_editor = TextEditor(text, self.textedit)
            self.fuzzy_search = FuzzySearch(text, words, self.checkbox.checkState())
            self.fuzzy_search.sig_words_count.connect(self.window.progress_bar.setMaximum)
            self.fuzzy_search.sig_step.connect(self.window.progress_bar.setValue)
            self.fuzzy_search.sig_done.connect(self.setEnabled)
            self.fuzzy_search.sig_insertions.connect(self.resultedit.setText)
            self.fuzzy_search.sig_insertions_indexes.connect(self.text_editor.mark)
            self.fuzzy_search.start()


class TextEditor:
    def __init__(self, text, textedit):
        self.text = text
        self.textedit = textedit

    def mark(self, to_mark):
        self.textedit.clear()
        current_index = 0
        for item in to_mark:
            self.write_not_marked_text(self.text[current_index:item[0]])
            self.write_marked_text(self.text[item[0]:item[1]])
            current_index = item[1]
        self.write_not_marked_text(self.text[current_index:])

    def write_not_marked_text(self, text):
        font = QFont("Times", 10)
        font.setItalic(False)
        font.setBold(False)
        self.textedit.setCurrentFont(font)
        self.textedit.setTextColor(Qt.black)
        self.textedit.insertPlainText(text)

    def write_marked_text(self, text):
        font = QFont("Times", 10)
        font.setItalic(True)
        font.setBold(True)
        self.textedit.setCurrentFont(font)
        self.textedit.setTextColor(Qt.red)
        self.textedit.insertPlainText(text)


class Window(QWidget):
    def __init__(self, font):
        super().__init__()
        self.standard_font = font
        self.text_edit_font = QFont("Times", 10)

        text_label = QLabel("Введите или откройте текст",
                            font=self.standard_font)
        words_label = QLabel("Введите или откройте слова (через запятую)",
                             font=self.standard_font)
        result_label = QLabel("Результат",
                              font=self.standard_font)

        text_edit = QTextEdit(font=self.text_edit_font)
        words_edit = QTextEdit(font=self.text_edit_font)
        result_edit = QTextEdit(font=self.text_edit_font)

        self.case_sensitive_checkbox = QCheckBox('Учитывать регистр')
        self.case_sensitive_checkbox.setFont(self.standard_font)

        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        open_btn1 = OpenButton("Открыть", self.standard_font, text_edit)
        open_btn2 = OpenButton("Открыть", self.standard_font, words_edit)
        find_btn = FindButton("Найти слова в тексте", self.standard_font,
                              text_edit, words_edit, result_edit, self)


        text_label_box = QHBoxLayout()
        text_label_box.addWidget(text_label, alignment=Qt.AlignLeft)
        text_label_box.addWidget(open_btn1, alignment=Qt.AlignRight)

        words_label_box = QHBoxLayout()
        words_label_box.addWidget(words_label, alignment=Qt.AlignLeft)
        words_label_box.addWidget(open_btn2, alignment=Qt.AlignRight)

        words_box = QVBoxLayout()
        words_box.addLayout(words_label_box)
        words_box.addWidget(words_edit)

        result_box = QVBoxLayout()
        result_box.addWidget(result_label, alignment=Qt.AlignLeft)
        result_box.addWidget(result_edit)

        bottom_box = QHBoxLayout()
        bottom_box.addLayout(words_box)
        bottom_box.addLayout(result_box)

        find_and_progress_box = QHBoxLayout()
        find_and_progress_box.addWidget(find_btn, alignment=Qt.AlignLeft)
        find_and_progress_box.addWidget(self.case_sensitive_checkbox)
        find_and_progress_box.addWidget(self.progress_bar)

        main_box = QVBoxLayout()
        main_box.addLayout(text_label_box)
        main_box.addWidget(text_edit)
        main_box.addLayout(bottom_box)
        main_box.addLayout(find_and_progress_box)

        self.setLayout(main_box)

        self.setGeometry(300, 300, 1100, 700)
        self.setWindowTitle('Нечеткий поиск')
        self.show()


def start_application():
    app = QApplication(sys.argv)
    w = Window(QFont("Times", 12))
    sys.exit(app.exec_())

它完美无缺。但它在控制台版本中不起作用,因为没有QEventLoop QThread将无法工作:

import fuzzysearch


class ConsoleVersion():
    def __init__(self, text, words):
        self.text = text
        self.words = words

    def search_words_in_text(self):
        with self.text:
            with self.words:
                self.f = fuzzysearch.FuzzySearch(self.text.read(), self.words.read(), False)
                self.f.sig_insertions.connect(self.get_insertions)
                self.f.start()


    def get_insertions(self, insertions):
        print(insertions)

在主文件中我写了解析参数并在两个版本之间选择

import argparse
import gui
import console_version

def parse_args():
    parser = argparse.ArgumentParser(description='Fuzzy search in text')
    parser.add_argument('-g', '--graphics', help='graphical version', action='store_true')
    parser.add_argument('-c', '--console', help='console version', nargs=2, type=argparse.FileType('r'), metavar=('TEXTFILE', 'WORDSFILE'))
    return parser.parse_args()


if __name__ == '__main__':
    args = parse_args()
    if args.graphics:
        gui.start_application()
    if args.console:
        cv = console_version.ConsoleVersion(args.console[0], args.console[1])
        cv.search_words_in_text()

和模块text_methods:

from re import split, sub


def transform_text_to_dict(text, case_sensitive):
    text_dict = {}
    index = 0
    if case_sensitive:
        splitted_text = split("[^'а-яА-ЯA-Za-z0-9_-]", text)
    else:
        splitted_text = split("[^'а-яА-ЯA-Za-z0-9_-]", text.lower())
    for element in splitted_text:
        if element not in text_dict:
            text_dict[element] = []
        text_dict[element].append(index)
        index += len(element) + 1
    return text_dict


def transform_words_to_list(words, case_sensitive):
    words = sub("^\s+|\n|\r|\s+$", '', words)
    if case_sensitive:
        return split(' *, *', words)
    else:
        return split(' *, *', words.lower())


'''Damerau-Levenstein'''
def find_distance(word1: str, word2: str):
    len1, len2 = len(word1), len(word2)
    if len1 > len2:
        word1, word2 = word2, word1
        len1, len2 = len2, len1
    current_row = range(len1 + 1)
    previous_row = range(len1 + 1)
    pre_previous_row = range(len1 + 1)
    for i in range(1, len2 + 1):
        if i == 1:
            previous_row, current_row = current_row, [i] + [0] * len1
        else:
            pre_previous_row, previous_row, current_row = previous_row, current_row, [i] + [0] * len1
        for j in range(1, len1 + 1):
            add = previous_row[j] + 1
            delete = current_row[j - 1] + 1
            change = previous_row[j - 1]
            if word1[j - 1] != word2[i - 1]:
                change += 1
            if word1[j - 1] == word2[i - 2] and word1[j - 2] == word2[i - 1]:
                transpose = pre_previous_row[j - 2] + 1
                current_row[j] = min(add, delete, change, transpose)
            else:
                current_row[j] = min(add, delete, change)
    return current_row[len1]


def is_optimal_distance(word1 : str, word2 : str):
    distance = find_distance(word1, word2)
    l = min(len(word1), len(word2))
    return distance <= l // 4

所以你能告诉我什么?

1 个答案:

答案 0 :(得分:0)

Qt所以要处理任务总是需要一个内部为它创建的循环,必须构造一个QCoreApplicationQGuiApplicationQApplication类型的对象,这需要它例如,QThread的情况不是一个线程但是一个线程处理程序正在监视线程的状态,如果你没有放置应用程序立即关闭,因为run方法没有在主线程中执行

if args.console: 
    app = QCoreApplication(sys.argv) 
    cv = console_version.ConsoleVersion(args.console[0], args.console[1]) 
    cv.search_words_in_text() 
    sys.exit(app.exec_())