Python 3.x 如何在Python的gui和非gui应用程序中使用QThread?

Python 3.x 如何在Python的gui和非gui应用程序中使用QThread?,python-3.x,pyqt5,qthread,Python 3.x,Pyqt5,Qthread,我正在编写一个模糊搜索应用程序,它可以在文本中查找单词,即使单词有错误。我写了gui表单,但由于计算量大,它冻结了。所以我创建了一个继承自QThread的类,并从中向gui发送信号,这样进度条就可以开始工作,gui表单就不再冻结。但我也应该创建这个应用程序的控制台版本,在这个版本中,我不需要gui表单,但需要在继承自QThread的类中编写的方法。但不使用PyQT库是不可能的,在控制台版本中使用PyQT库很奇怪。所以我不知道如何解决这个问题。我的老师建议使用线程,但我没有发现如何像在QThrea

我正在编写一个模糊搜索应用程序,它可以在文本中查找单词,即使单词有错误。我写了gui表单,但由于计算量大,它冻结了。所以我创建了一个继承自QThread的类,并从中向gui发送信号,这样进度条就可以开始工作,gui表单就不再冻结。但我也应该创建这个应用程序的控制台版本,在这个版本中,我不需要gui表单,但需要在继承自QThread的类中编写的方法。但不使用PyQT库是不可能的,在控制台版本中使用PyQT库很奇怪。所以我不知道如何解决这个问题。我的老师建议使用线程,但我没有发现如何像在QThread中那样从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模块,在该模块中,它们连接到方法findu 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()
和模块文本方法:

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
从重新导入拆分,子
def将文本转换为dict(文本,区分大小写):
text_dict={}
索引=0
如果区分大小写:
拆分文本=拆分([^'а-аА-ЯA-Za-z0-9а-],文本)
其他:
拆分的文本=拆分([^'а-аА-ЯA-Za-z0-9а-]),文本。下()
对于拆分文本中的元素:
如果元素不在文本目录中:
文本内容[元素]=[]
text_dict[元素].追加(索引)
索引+=len(元素)+1
返回文本
def将单词转换为单词列表(单词,区分大小写):
words=sub(“^\s+|\n |\r |\s+$”,“”,words)
如果区分大小写:
返回拆分(“*,*”,单词)
其他:
返回拆分(“*,*”,words.lower()
“Damerau-Levenstein”
def查找距离(word1:str,word2:str):
len1,len2=len(单词1),len(单词2)
如果len1>len2:
word1,word2=word2,word1
len1,len2=len2,len1
当前行=范围(len1+1)
上一行=范围(len1+1)
前一行=范围(len1+1)
对于范围(1,len2+1)中的i:
如果i==1:
上一行,当前行=当前行,[i]+[0]*len1
其他:
pre_previous_row,previous_row,current_row=previous_row,current_row,[i]+[0]*len1
对于范围(1,len1+1)内的j:
添加=上一行[j]+1
删除=当前_行[j-1]+1
更改=上一行[j-1]
如果单词1[j-1]!=字2[i-1]:
更改+=1
如果word1[j-1]==word2[i-2]和word1[j-2]==word2[i-1]:
转置=前一行[j-2]+1
当前_行[j]=min(添加、删除、更改、转置)
其他:
当前_行[j]=min(添加、删除、更改)
返回当前_行[len1]
def是最佳距离(word1:str,word2:str):
距离=查找距离(单词1,单词2)
l=min(len(单词1),len(单词2))

返回距离Qt,因此为了处理任务,始终需要为其内部创建一个循环,必须构造一个类型为
QCoreApplication
QGuiApplication
QApplication
的对象,例如,
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_())

在控制台版本中,它不能很好地工作,这意味着什么?您还可以演示如何使用ConsoleVersion类。但它在console版本中不起作用,因为如果没有QEventLoop,QThread将不起作用。我必须为您的代码提供什么类型的输入?你能给我举个例子吗?我无法测试你的代码,因为我缺少text\u methods模块,但我认为你的代码需要放置:
app=QCoreApplication(sys.argv)
在创建cv.Change
if args.console:cv=console\u version.consolevervion(args.console[0],args.console[1])cv.search\u words\u in\u text()
to
if args.console:app=QCoreApplication(sys.argv)cv=console\u version.consolevervion(args.console[0],args.console[1])cv.search\u words\u in\u text()sys.exit(app.exec\ux())