【发布时间】: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