I am writing the application "Fuzzy search", in which the input is text, which can be typos and a set of words that need to be found in it. At the exit information about what words are included (even if they are typos).

I wrote a gui class, but during long calculations it froze. Then I wrote a class inheriting from QThread and put these calculations into it, and also emitted signals from it that were caught in the classroom in order to move the progress bar.

Everything would be fine, but I still need to write a second version without a guish form, just give two arguments to the console - a file with text and a file with words and print the list of entries at the output. To do this, I need methods from a class inherited from QThread, but without QEventLoop, QThread does not work, and in the console version it would be strange to use the graphics library.

The teacher advised to use Thread from the threading module, but I can’t find out how to send the signals needed for graphics, as done in PyQt. Perhaps there is still some solution to this problem.

This is a class derived from 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() 

There are a lot of signals that are connected to the slots in the graphic module of the FindButton class and find_insertions method.

 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_()) 

And the graphics work fine, but the console version does not work without QEventLoop

 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) 

and in the main file I parse the arguments and select the version based on them

 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() 

what to do in this case?

  • Use QCoreApplication for the console version. - mkkik

0