You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 03:14:51