【问题标题】:Wrong append in the PyQt app algorithm, works well while testing in different settingPyQt 应用程序算法中的错误附加,在不同设置下测试时效果很好
【发布时间】:2022-10-18 16:41:13
【问题描述】:

我正在编写一个通过 Outlook 发送自动电子邮件的应用程序。我创建了简单的算法来提取数据并将其组合到电子邮件正文中。如果有人想在电子邮件正文中放置一些变量,那么他们必须将复制的 self.data: pd.DataFrame 列名称括起来:<<variable_name>> 符号 - 正则表达式然后找到变量及其在文本中的位置,并在算法中用真实数据替换它们来自数据框。

但是我偶然发现了一个问题:该算法在 PyQt 应用程序之外运行良好,但在应用程序内却不行。问题在于,在应用程序算法之外创建的电子邮件消息与 DataFrame 中的行数一样多(按计划),但在应用程序中它只创建一条由第一行数据组成的消息。我已经尝试调试它,到目前为止,我可以确认循环按计划通过 DataFrame 的所有行,但由于我未知的原因,它只将第一条消息附加到列表中。我创建了一些虚拟数据来复制算法的工作:

import pandas as pd
# Dummy data for loop:
df = pd.DataFrame(data = {'Hello':['++++++','======'],'Welcome':[';;;;;;','------'],'Hi':['!!!!!!', '??????']})
content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin venenatis viverra odio, sagittis malesuada'
content_string = list(content)
positions = [[1, 4], [10, 14], [30, 36]]
variables = ['Hello', 'Welcome', 'Hi']

emails = []
# Composing email message for each user. One user == one row of pd.DataFrame
for x in range(len(df)):
    for num, (position, var) in enumerate(zip(positions, variables)):
        if var in df.columns:
            content_string[position[0]:position[1]] = str(df[var][x])
            email_composed = ''.join(content_string)
            if num + 1 == len(variables):
                emails.append(email_composed)
                content_string = list(content)
for i in emails:
    print(i)

输出:

L++++++m i;;;;;; dolor sit ame!!!!!!sectetur adipiscing elit. Proin venenatis viverra odio, sagittis malesuada

L======m i------ dolor sit ame??????sectetur adipiscing elit. Proin venenatis viverra odio, sagittis malesuada

这是 PyQt5 应用程序中的同一段代码:

功能部分:文件outlook.py

import re
import sys
import traceback
from typing import Any

import pandas as pd
from PyQt5.QtWidgets import QFileDialog, QTableWidget, QTableWidgetItem, QMainWindow, QMessageBox
from PyQt5 import QtWidgets

from outlook_window import Ui_MainWindow


class OutlookForm(QMainWindow, QTableWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.actionOpen_data_file.triggered.connect(self.load_data)
        self.ui.list_widget_columns.itemClicked.connect(self.get_clicked_item_from_list)
        self.ui.list_selected_variables.itemClicked.connect(self.get_clicked_item_from_list_of_variables)
        self.ui.push_button_add_variable.clicked.connect(self.add_data_to_listed_variables)
        self.ui.push_button_clean_list.clicked.connect(self.clear_list_of_selected_items)
        self.ui.push_button_send.clicked.connect(self.send_email)
        self.data = None
        self.separator: str = ';'
        self.show()

    def load_data(self) -> None:
        try:
            file, _ = QFileDialog.getOpenFileName(self, "Open file", "", "All files (*);;CSV files (*.csv)")
            if file:
                if len(self.separator) != 0:
                    self.data = pd.read_csv(file, sep=str(self.separator))
                else:
                    self.data = pd.read_csv(file)
                self.clean_data_from_data_frame()
                self.ui.table_widget_data_from_data_frame.setColumnCount(self.data.shape[1])
                self.ui.table_widget_data_from_data_frame.setRowCount(self.data.shape[0])
                for column, key in enumerate(self.data.columns):
                    for row, item in enumerate(self.data[key]):
                        new_item = QTableWidgetItem(item)
                        self.ui.table_widget_data_from_data_frame.setItem(row, column, new_item)
                self.ui.table_widget_data_from_data_frame.setHorizontalHeaderLabels(self.data.columns)
                self.load_columns_to_list_of_variables()
                QMessageBox.information(self, 'Info', 'Database successfully loaded!')
        except FileNotFoundError:
            QMessageBox.critical(self, 'Error', f'Something went wrong: {traceback.format_exc()}')

    def clean_data_from_data_frame(self) -> None:
        self.data = self.data.dropna(axis=1)
        self.data.columns = self.data.columns.str.rstrip()
        for column, data_type in zip(self.data.columns, self.data.dtypes):
            if data_type == 'object' or data_type == 'str':
                self.data[column] = self.data[column].str.strip()

    def load_columns_to_list_of_variables(self) -> None:
        if isinstance(self.data, pd.DataFrame):
            self.ui.list_widget_columns.clear()
            for column in self.data.columns:
                self.ui.list_widget_columns.addItem(column)

    def get_clicked_item_from_list(self, item) -> Any:
        item_from_list = self.ui.list_widget_columns.currentItem().text()
        return item_from_list

    def get_clicked_item_from_list_of_variables(self, item) -> Any:
        item_from_list = self.ui.list_selected_variables.currentRow()
        return item_from_list

    def add_data_to_listed_variables(self, item) -> None:
        item_from_list = self.get_clicked_item_from_list(item)
        self.ui.list_selected_variables.addItem(item_from_list)

    def clear_list_of_selected_items(self) -> None:
        self.ui.list_selected_variables.clear()

    def get_variables_from_list(self) -> list:
        variables_from_list = []
        for i in range(self.ui.list_selected_variables.count()):
            variables_from_list.append(self.ui.list_selected_variables.item(i).text())
        return variables_from_list

    def get_data_from_dataframe(self) -> pd.DataFrame:
        variables_from_list = self.get_variables_from_list()
        columns_to_slice_from_df = []
        for variable in variables_from_list:
            if variable in self.data.columns:
                columns_to_slice_from_df.append(variable)
        sliced_df = self.data[columns_to_slice_from_df]
        return sliced_df

    @property
    def find_matching_patterns_from_text(self) -> Any:
        sequence = r'<<(.*?)>>'
        pattern = re.compile(pattern=sequence)
        email_body = self.ui.text_edit_email_body.toPlainText()
        variables = re.findall(pattern, email_body)
        positions = pattern.finditer(email_body)
        return variables, positions

    def create_list_of_mails_messages(self):
        list_of_mails = []
        sliced_data_frame = self.get_data_from_dataframe()
        variables_from_list, positions = self.find_matching_patterns_from_text
        email_body = self.ui.text_edit_email_body.toPlainText()
        email_body_list = list(email_body)
        for row in range(len(sliced_data_frame)):
            for num, (position, variable) in enumerate(zip(positions, variables_from_list)):
                if variable in sliced_data_frame.columns:
                    email_body_list[position.start():position.end()] = str(sliced_data_frame[variable][row])
                    email_composed = ''.join(email_body_list)
                    if num + 1 == len(variables_from_list):
                        list_of_mails.append(email_composed)
                        email_body_list = list(email_body)
        return list_of_mails

    def get_email_addresses(self) -> list:
        column_with_addresses = self.ui.line_edit_addresses.text()
        list_of_addresses = self.data[column_with_addresses].to_list()
        return list_of_addresses

    def send_email(self):
        list_of_emails = self.create_list_of_mails_messages()
        list_of_addresses = self.get_email_addresses()
        for address in list_of_addresses:
            print(address)
        for mail in list_of_emails:
            print(mail)

    #        Here are other actions responsible for connecting to outlook -
    #        - not necessary for understanding the issue in MRE


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = OutlookForm()
    w.show()
    sys.exit(app.exec_())


布局部分:文件:outlook_window.py

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(869, 897)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("../static/img/email_icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout_6 = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout_6.setObjectName("gridLayout_6")
        self.table_widget_data_from_data_frame = QtWidgets.QTableWidget(self.centralwidget)
        self.table_widget_data_from_data_frame.setMinimumSize(QtCore.QSize(0, 300))
        self.table_widget_data_from_data_frame.setMaximumSize(QtCore.QSize(850, 300))
        self.table_widget_data_from_data_frame.setObjectName("table_widget_data_from_data_frame")
        self.table_widget_data_from_data_frame.setColumnCount(0)
        self.table_widget_data_from_data_frame.setRowCount(0)
        self.gridLayout_6.addWidget(self.table_widget_data_from_data_frame, 0, 0, 1, 2)
        self.gridLayout_5 = QtWidgets.QGridLayout()
        self.gridLayout_5.setObjectName("gridLayout_5")
        self.text_edit_email_body = QtWidgets.QTextEdit(self.centralwidget)
        self.text_edit_email_body.setObjectName("text_edit_email_body")
        self.gridLayout_5.addWidget(self.text_edit_email_body, 0, 1, 1, 1)
        self.gridLayout_6.addLayout(self.gridLayout_5, 9, 0, 1, 1)
        self.gridLayout_3 = QtWidgets.QGridLayout()
        self.gridLayout_3.setObjectName("gridLayout_3")
        spacerItem = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_3.addItem(spacerItem, 0, 0, 1, 1)
        self.push_button_send = QtWidgets.QPushButton(self.centralwidget)
        self.push_button_send.setMaximumSize(QtCore.QSize(70, 16777215))
        self.push_button_send.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.push_button_send.setObjectName("push_button_send")
        self.gridLayout_3.addWidget(self.push_button_send, 0, 1, 1, 1)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_3.addItem(spacerItem1, 0, 2, 1, 1)
        self.gridLayout_6.addLayout(self.gridLayout_3, 10, 0, 1, 1)
        self.gridLayout_2 = QtWidgets.QGridLayout()
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.push_button_clean_list = QtWidgets.QPushButton(self.centralwidget)
        self.push_button_clean_list.setMinimumSize(QtCore.QSize(100, 0))
        self.push_button_clean_list.setMaximumSize(QtCore.QSize(50, 16777215))
        self.push_button_clean_list.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.push_button_clean_list.setObjectName("push_button_clean_list")
        self.gridLayout.addWidget(self.push_button_clean_list, 1, 0, 1, 1)
        self.push_button_add_variable = QtWidgets.QPushButton(self.centralwidget)
        self.push_button_add_variable.setMinimumSize(QtCore.QSize(100, 0))
        self.push_button_add_variable.setMaximumSize(QtCore.QSize(50, 16777215))
        self.push_button_add_variable.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.push_button_add_variable.setObjectName("push_button_add_variable")
        self.gridLayout.addWidget(self.push_button_add_variable, 0, 0, 1, 1, QtCore.Qt.AlignHCenter)
        self.gridLayout_2.addLayout(self.gridLayout, 1, 1, 1, 1)
        self.list_widget_columns = QtWidgets.QListWidget(self.centralwidget)
        self.list_widget_columns.setMaximumSize(QtCore.QSize(250, 16777215))
        self.list_widget_columns.setObjectName("list_widget_columns")
        self.gridLayout_2.addWidget(self.list_widget_columns, 1, 0, 1, 1)
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setMaximumSize(QtCore.QSize(70, 16777215))
        self.label.setObjectName("label")
        self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setMaximumSize(QtCore.QSize(150, 16777215))
        self.label_2.setObjectName("label_2")
        self.gridLayout_2.addWidget(self.label_2, 0, 2, 1, 1)
        self.list_selected_variables = QtWidgets.QListWidget(self.centralwidget)
        self.list_selected_variables.setMaximumSize(QtCore.QSize(250, 16777215))
        self.list_selected_variables.setObjectName("list_selected_variables")
        self.gridLayout_2.addWidget(self.list_selected_variables, 1, 2, 1, 1)
        self.gridLayout_6.addLayout(self.gridLayout_2, 5, 0, 1, 1)
        self.gridLayout_4 = QtWidgets.QGridLayout()
        self.gridLayout_4.setObjectName("gridLayout_4")
        self.line_edit_addresses = QtWidgets.QLineEdit(self.centralwidget)
        self.line_edit_addresses.setMaximumSize(QtCore.QSize(150, 16777215))
        self.line_edit_addresses.setObjectName("line_edit_addresses")
        self.gridLayout_4.addWidget(self.line_edit_addresses, 0, 1, 1, 1)
        spacerItem2 = QtWidgets.QSpacerItem(150, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_4.addItem(spacerItem2, 2, 6, 1, 3)
        self.label_addresses = QtWidgets.QLabel(self.centralwidget)
        self.label_addresses.setObjectName("label_addresses")
        self.gridLayout_4.addWidget(self.label_addresses, 0, 0, 1, 1)
        spacerItem3 = QtWidgets.QSpacerItem(5, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_4.addItem(spacerItem3, 2, 2, 1, 1)
        self.gridLayout_6.addLayout(self.gridLayout_4, 7, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 869, 21))
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionOpen_data_file = QtWidgets.QAction(MainWindow)
        self.actionOpen_data_file.setObjectName("actionOpen_data_file")
        self.menuFile.addAction(self.actionOpen_data_file)
        self.menubar.addAction(self.menuFile.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.push_button_send.setText(_translate("MainWindow", "Send "))
        self.push_button_clean_list.setText(_translate("MainWindow", "Clean"))
        self.push_button_add_variable.setText(_translate("MainWindow", "Add"))
        self.label.setText(_translate("MainWindow", "Columns"))
        self.label_2.setText(_translate("MainWindow", "Selected variables"))
        self.list_selected_variables.setToolTip(_translate("MainWindow", "<html><head/><body><p><br/></p></body></html>"))
        self.label_addresses.setText(_translate("MainWindow", "Column with addresses:"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.actionOpen_data_file.setText(_translate("MainWindow", "Open data file"))
        self.actionOpen_data_file.setShortcut(_translate("MainWindow", "Ctrl+N"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

这是一个 .csv 文件的链接,其中包含一些要加载到表格小部件的虚拟数据,以及一个带有“Lorem ipsum”电子邮件消息的 .txt 文件,其中 <<>> 符号之间的文本中已经包含来自 .csv 数据的变量:

https://github.com/youngTouring/outlook-data.git

为了重现该行为,您必须:

  • 启动outlook.py应用程序
  • 使用左上角的 ctrl + N 快捷方式或操作加载MOCK_DATA.csv(“打开数据文件”)
  • 使用“添加”按钮将 3 个变量添加到右侧列表(“选定变量”):我已经输入了 <<>> <<>> 和 <<性别>> 在 .txt 文件中,所以最好的选择是选择这 3 个
  • 在标签附近的行编辑中键入“电子邮件”,其中显示“地址列:”
  • 将电子邮件从email_body.txt复制并粘贴到应用程序底部的纯文本小部件
  • 单击“发送”按钮并在 python 控制台中观察输出 - 当前行为只是将一封电子邮件附加到列表中。正确的行为是列表中的所有邮件。

所以输出只是附加到列表的第一条消息。我不确定我是否包含了理解问题所需的所有代码。如果有人怀疑这种行为的原因可能是什么,我会很感激一些建议。

【问题讨论】:

  • find_matching_patterns_from_text 不是缺少括号(和参数)吗?相反,如果它是正确的,我们需要知道它返回什么以及get_data_from_dataframe 做了什么,所以请提供一个有效的minimal reproducible example
  • 是的,find_matching_patterns_from_text 缺少括号,因为它是 @property,对不起,我忘了包括那个。我编辑了代码并添加了find_matching_patterns_from_text get_data_from_dataframe
  • 请花点时间提供完整的 MRE,包括示例 csv 数据。它应该是一些我们可以轻松复制、粘贴和运行的代码,可能无需进行大量修改。我们不应该专注于试图理解如何重现问题,而应该专注于找到解决方案。
  • 我已经重新安排了这个问题 - 我包含了 MRE 的所有代码。我还添加了一些虚拟数据的链接——包括 email_message 和一些可以加载到表格小部件的 .csv 数据。我希望它运行良好,我已经测试了这个“最小”版本并且运行良好。

标签: python pandas dataframe algorithm pyqt


【解决方案1】:

好的,所以我发现了问题:问题是由正则表达式及其文本中的 iter 位置引起的。我不确定为什么,但是在算法中创建电子邮件消息期间,从正则表达式中提取的开始和结束位置以错误的方式迭代。我已经更改了脚本并添加了将正则表达式 iter 位置转换为 python 列表的函数。当算法迭代列表时,它工作正常。我不知道为什么 for 循环对正则表达式位置的行为如此奇怪,但用 list 替换它们效果很好,所以我认为它可以解决这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-22
    • 1970-01-01
    • 2022-01-12
    • 2017-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多