Python支出管理器报错:'NoneType' object has no attribute 'update'求助
问题定位与修复方案
核心错误原因
出现AttributeError: 'NoneType' object has no attribute 'update'的直接原因是将字典update()方法的返回值(None)赋值给了self._data,导致后续self._data变成空值,无法调用update方法。同时代码还有其他逻辑错误,逐一修复如下:
1. fill_table方法的致命错误
原代码中:
self._data = self._data.update({desc: price})
字典的update()是原地修改字典,返回值为None,这行代码把self._data改成了None,后续任何对self._data的操作都会报错。
修复:
删除赋值操作,直接调用update即可:
self._data.update({desc: price})
2. add_element方法的逻辑错误
原代码中:
self._data.update(self.description.text()) self._data.update(self.price.text())
- 点击添加按钮后已清空输入框,
self.description.text()和self.price.text()都是空字符串,无法作为update的参数 update()需要传入字典或键值对,直接传字符串会触发迭代错误
修复:
用新添加的描述和价格组成字典传入update:
self._data.update({des: price})
3. plot_data方法的方法调用错误
原代码中:
number = float(self.table.item(i, 1).int())
QTableWidgetItem没有int()方法,应该先获取文本再转换类型:
修复:
number = float(self.table.item(i, 1).text())
4. CSV导入逻辑的设计缺陷
原代码用单个字典self._data存储多条支出记录,会导致后续记录覆盖之前的同名描述,应该用列表存储字典来保存多条记录,同时适配CSV的多行读写。
修复:
- 将
self._data初始化为列表:self._data = [] - 导入CSV时改为追加记录:
with open('aib_october.csv', encoding= 'utf-8-sig') as f: reader = csv.DictReader(f) for record in reader: self._data.append(record)
- 同步修改
fill_table、export_data等方法的逻辑,适配列表结构
完整修正后的代码
import csv import sys from PySide6.QtCore import Qt, Slot from PySide6.QtGui import QAction , QPainter from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) from PySide6.QtCharts import QChartView, QPieSeries, QChart class Widget(QWidget): def __init__(self): QWidget.__init__(self) self.items = 0 # 改为列表存储多条记录 self._data = [] with open('aib_october.csv', encoding= 'utf-8-sig') as f: reader = csv.DictReader(f) for record in reader: self._data.append(record) # Left Widget self.table = QTableWidget() self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(["Description", "Price"]) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # Chart self.chart_view = QChartView() self.chart_view.setRenderHint(QPainter.Antialiasing) # Right Widget self.description = QLineEdit() self.price = QLineEdit() self.add = QPushButton("Add") self.clear = QPushButton("Clear") self.quit = QPushButton("Quit") self.plot = QPushButton("Plot") self.export = QPushButton("export") # Disabling 'Add' button self.add.setEnabled(False) self.right = QVBoxLayout() self.right.addWidget(QLabel("Description")) self.right.addWidget(self.description) self.right.addWidget(QLabel("Price")) self.right.addWidget(self.price) self.right.addWidget(self.add) self.right.addWidget(self.plot) self.right.addWidget(self.chart_view) self.right.addWidget(self.export) self.right.addWidget(self.clear) self.right.addWidget(self.quit) # QWidget Layout self.layout = QHBoxLayout() self.layout.addWidget(self.table) self.layout.addLayout(self.right) # Set the layout to the QWidget self.setLayout(self.layout) # Signals and Slots self.add.clicked.connect(self.add_element) self.quit.clicked.connect(self.quit_application) self.plot.clicked.connect(self.plot_data) self.clear.clicked.connect(self.clear_table) self.export.clicked.connect(self.export_data) self.description.textChanged[str].connect(self.check_disable) self.price.textChanged[str].connect(self.check_disable) # Fill example data self.fill_table() @Slot() def export_data(self): if not self._data: return with open("aib_october.csv", 'w', newline='', encoding='utf-8-sig') as file: writer = csv.DictWriter(file, fieldnames=["Description", "Price"]) writer.writeheader() writer.writerows(self._data) def add_element(self): des = self.description.text().strip() price = self.price.text().strip() try: price_float = float(price) price_str = f"{price_float:.2f}" price_item = QTableWidgetItem(price_str) price_item.setTextAlignment(Qt.AlignRight) self.table.insertRow(self.items) description_item = QTableWidgetItem(des) self.table.setItem(self.items, 0, description_item) self.table.setItem(self.items, 1, price_item) self.description.setText("") self.price.setText("") self.items += 1 # 添加新记录到_data列表 self._data.append({"Description": des, "Price": price_str}) except ValueError: print(f"无效输入: {price},请输入合法的价格数字!") @Slot() def check_disable(self, x): if not self.description.text().strip() or not self.price.text().strip(): self.add.setEnabled(False) else: self.add.setEnabled(True) @Slot() def plot_data(self): # Get table information series = QPieSeries() for i in range(self.table.rowCount()): text = self.table.item(i, 0).text() # 修复获取价格的方式 number = float(self.table.item(i, 1).text()) series.append(text, number) chart = QChart() chart.addSeries(series) chart.legend().setAlignment(Qt.AlignLeft) self.chart_view.setChart(chart) @Slot() def quit_application(self): QApplication.quit() def fill_table(self, data=None): data = self._data if not data else data for record in data: desc = record["Description"] price = record["Price"] description_item = QTableWidgetItem(desc) price_item = QTableWidgetItem(price) price_item.setTextAlignment(Qt.AlignRight) self.table.insertRow(self.items) self.table.setItem(self.items, 0, description_item) self.table.setItem(self.items, 1, price_item) self.items += 1 @Slot() def clear_table(self): self.table.setRowCount(0) self.items = 0 self._data = [] class MainWindow(QMainWindow): def __init__(self, widget): QMainWindow.__init__(self) self.setWindowTitle("支出管理器") # Menu self.menu = self.menuBar() self.file_menu = self.menu.addMenu("文件") # Exit QAction exit_action = QAction("退出", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.exit_app) self.file_menu.addAction(exit_action) self.setCentralWidget(widget) @Slot() def exit_app(self, checked): QApplication.quit() if __name__ == "__main__": # Qt Application app = QApplication(sys.argv) # QWidget widget = Widget() # QMainWindow using QWidget as central widget window = MainWindow(widget) window.resize(800, 600) window.show() # Execute application sys.exit(app.exec())
额外说明
- 修正了CSV读写的逻辑,支持多条记录的存储和导出
- 对输入框内容做了去空格处理,避免空字符干扰
- 优化了错误提示的可读性
- 调整了窗口标题为中文,更符合使用习惯
内容的提问来源于stack exchange,提问作者BLAZE GAMING
相关产品推荐
相关产品推荐

