Python如何在GUI和非GUI应用程序中使用QThread?
How to use QThread in both gui and non-gui application in Python?
我正在编写一个模糊搜索应用程序,它可以在文本中查找单词,即使单词有错误。我写了 gui 形式,但由于计算量大而冻结了。所以我创建了 class 继承自 QThread 并从它发出信号到 gui 以便进度条开始工作并且 gui 表单不再冻结。但是我还应该创建这个应用程序的控制台版本,我不需要 gui 形式,但我需要从 QThread 继承的 class 中编写的方法。但是如果不使用在控制台版本中使用很奇怪的 PyQT 库是不可能的。所以我不知道如何解决这个问题。我的老师建议使用线程,但我没有找到如何从 Thread class 发出信号,就像我在 QThread 中所做的那样。
那是一个 QThread class
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:
中 class 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
那你能给我什么建议?
Qt 以便处理任务总是需要一个在内部为其创建的循环,必须构造一个 QCoreApplication
、QGuiApplication
或 QApplication
类型的对象,这需要例如,对于 QThread
这种情况,它不是一个线程,而是一个线程处理程序,它正在监视线程的状态,如果您不放置,应用程序将立即关闭,因为 运行 方法是没有在主线程中执行。
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_())
我正在编写一个模糊搜索应用程序,它可以在文本中查找单词,即使单词有错误。我写了 gui 形式,但由于计算量大而冻结了。所以我创建了 class 继承自 QThread 并从它发出信号到 gui 以便进度条开始工作并且 gui 表单不再冻结。但是我还应该创建这个应用程序的控制台版本,我不需要 gui 形式,但我需要从 QThread 继承的 class 中编写的方法。但是如果不使用在控制台版本中使用很奇怪的 PyQT 库是不可能的。所以我不知道如何解决这个问题。我的老师建议使用线程,但我没有找到如何从 Thread class 发出信号,就像我在 QThread 中所做的那样。
那是一个 QThread class
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:
中 class 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
那你能给我什么建议?
Qt 以便处理任务总是需要一个在内部为其创建的循环,必须构造一个 QCoreApplication
、QGuiApplication
或 QApplication
类型的对象,这需要例如,对于 QThread
这种情况,它不是一个线程,而是一个线程处理程序,它正在监视线程的状态,如果您不放置,应用程序将立即关闭,因为 运行 方法是没有在主线程中执行。
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_())