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

Jupyter Notebook中ipywidgets按钮无响应问题求助

交互式费用管理系统按钮无响应问题排查与修复

问题描述

我在Jupyter Notebook中搭建了一个交互式费用管理系统,使用ipywidgets构建界面、pandas处理数据。设置了添加、删除、显示费用的按钮,但点击“Add Expense”按钮时无任何反应——费用未添加到数据集,也无错误提示,其他按钮情况相同。尝试添加print语句验证函数是否被调用,但推测函数并未触发。

原代码

import pandas as pd
import ipywidgets as widgets
from IPython.display import display
from matplotlib import pyplot as plt
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

file_path = 'витрати_10000.csv'
data = pd.read_csv(file_path)

print(data.head())

class ExpenseManager:
    def __init__(self, data):
        self.data = data

    def add_expense(self, date, category, amount):
        new_expense = pd.DataFrame({"Date": [date], "Category": [category], "Amount": [amount]})
        self.data = pd.concat([self.data, new_expense], ignore_index=True)

    def remove_expense(self, index):
        if index in self.data.index:
            self.data = self.data.drop(index)
        else:
            print("Expense not found.")

    def analyze_expenses(self, period='Month'):
        if period == 'Day':
            return self.data.groupby(self.data['Date']).sum()
        elif period == 'Week':
            self.data['Week'] = pd.to_datetime(self.data['Date']).dt.isocalendar().week
            return self.data.groupby('Week').sum()
        elif period == 'Month':
            self.data['Month'] = pd.to_datetime(self.data['Date']).dt.to_period('M')
            return self.data.groupby('Month').sum()
        else:
            print("Invalid period.")

em = ExpenseManager(data)

date_input = widgets.DatePicker(description='Date:')
category_input = widgets.Text(description='Category:')
amount_input = widgets.FloatText(description='Amount:')
add_button = widgets.Button(description='Add Expense')
remove_index_input = widgets.IntText(description='Index:')
remove_button = widgets.Button(description='Remove Expense')
display_button = widgets.Button(description='Display Data')

def add_expense(b):
    em.add_expense(date_input.value, category_input.value, amount_input.value)
    print(f"Added expense: {date_input.value}, {category_input.value}, {amount_input.value}")

def remove_expense(b):
    em.remove_expense(remove_index_input.value)
    print(f"Removed expense at index: {remove_index_input.value}")

def display_data(b):
    display(em.data)

add_button.on_click(add_expense)
remove_button.on_click(remove_expense)
display_button.on_click(display_data)

display(date_input, category_input, amount_input, add_button, remove_index_input, remove_button, display_button)

period_input = widgets.Dropdown(options=['Day', 'Week', 'Month'], description='Period:')
analyze_button = widgets.Button(description='Analyze Expenses')

def analyze_expenses(b):
    period = period_input.value
    analysis = em.analyze_expenses(period)
    display(analysis)
    analysis.plot(kind='bar')
    plt.show()

analyze_button.on_click(analyze_expenses)

display(period_input, analyze_button)

def generate_pdf_report(data, filename='expense_report.pdf'):
    c = canvas.Canvas(filename, pagesize=letter)
    width, height = letter
    c.drawString(30, height - 40, "Expense Report")
    
    y = height - 80
    for i, row in data.iterrows():
        c.drawString(30, y, f"{row['Date']} - {row['Category']} - ${row['Amount']}")
        y -= 20
        if y < 40:
            c.showPage()
            y = height - 40
    
    c.save()

pdf_button = widgets.Button(description='Generate PDF Report')

def create_pdf_report(b):
    generate_pdf_report(em.data)
    print("PDF report generated.")

pdf_button.on_click(create_pdf_report)

display(pdf_button)

解决方案

1. 检查ipywidgets环境配置

Jupyter中ipywidgets需要正确启用才能实现交互,执行以下命令(根据你的Jupyter版本选择):

  • 经典Notebook:
    jupyter nbextension enable --py widgetsnbextension
    
  • JupyterLab:
    jupyter labextension install @jupyter-widgets/jupyterlab-manager
    

执行后重启Jupyter内核,再测试按钮功能。

2. 修复数据类型不匹配问题

DatePicker返回的是datetime.date对象,而原数据集的Date列是字符串格式,直接传入会导致数据不兼容。修改add_expense函数,将日期转为字符串:

def add_expense(b):
    # 检查输入是否完整
    if not date_input.value or not category_input.value or not amount_input.value:
        print("请填写所有必填字段")
        return
    # 将datetime.date对象转为字符串格式(匹配原数据格式)
    date_str = date_input.value.strftime('%Y-%m-%d')
    em.add_expense(date_str, category_input.value, amount_input.value)
    print(f"已添加费用: {date_str}, {category_input.value}, {amount_input.value}")

3. 验证函数触发与作用域

在按钮绑定的函数开头添加调试print语句,确认函数是否被触发:

def add_expense(b):
    print("add_expense函数已触发")
    # 后续逻辑...

如果没有输出,说明按钮绑定可能存在作用域问题——确保所有widget定义、函数定义和按钮绑定代码在同一个单元格运行,不要拆分到多个单元格。

4. 优化ExpenseManager类的数据更新逻辑

当前类中add_expense方法逻辑正确,但可以改为更高效的方式(避免重复创建DataFrame):

def add_expense(self, date, category, amount):
    self.data.loc[len(self.data)] = [date, category, amount]

这种方式直接在原DataFrame末尾添加行,避免pd.concat的额外开销。

内容的提问来源于stack exchange,提问作者pamquale

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:48:09