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

如何从内部函数访问外部函数变量并实现DataFrame下载功能

解决按钮点击函数无法访问筛选后DataFrame的问题

你的问题很典型:df是filtra_dati函数内部的局部变量,当这个函数执行完毕后,局部变量就会被销毁,所以独立的on_button_clicked函数根本找不到它,才会抛出NameError。下面给你两种可行的解决方案,从简单到优雅:

方案一:使用全局变量保存最新DataFrame

这是最直接的修改方式,我们声明一个全局变量来存储每次筛选后的DataFrame,让按钮点击函数能直接访问到它:

import ipywidgets as widgets
import pandas as pd
import os

# 全局变量:保存最新生成的DataFrame
latest_df = None

kpi = [k for k in canali_weekly.columns]
dropdown_canali = widgets.Dropdown(options = crea_opzioni_con_ALL(canali_weekly.index.get_level_values(level=0)), description='Canali:')
select_metriche = widgets.SelectMultiple(options=kpi, value= kpi, rows=5, description='Metriche:')
download_dataframe = widgets.Button(description='Scarica i dati', icon='check')
output = widgets.Output()  # 补上你之前定义的Output组件

def filtra_dati(selezione, canale, metrica):
    global latest_df  # 声明使用全局变量
    output.clear_output()
    if canale == "ALL":
        latest_df = canali_weekly.loc[(slice(None), slice(selezione[0],selezione[1])), list(metrica)]
    else:
        latest_df = canali_weekly.loc[(canale, slice(selezione[0],selezione[1])), list(metrica)]
    with output:
        pd.set_option('display.max_columns', None)
        pd.set_option('display.max_rows', None)
        display(latest_df)

def on_button_clicked(b):
    global latest_df
    with output:
        if latest_df is not None:
            print("Button clicked. Saving DataFrame...")
            # 用os.path.expanduser处理~/路径,确保跨系统兼容
            latest_df.to_excel(os.path.expanduser("~/Downloads/df.xlsx"))
            print("DataFrame salvato con successo!")
        else:
            print("Errore: Nessun dato disponibile per il download! Effettua prima una selezione.")

# 事件绑定部分保持不变
def week_eventhandler(change):
    filtra_dati(change.new, dropdown_canali.value, select_metriche.value)

def canali_eventhandler(change):
    filtra_dati(intRange_week.value, change.new, select_metriche.value)

def metriche_eventhandler(change):
    filtra_dati(intRange_week.value, dropdown_canali.value, change.new)

intRange_week.observe(week_eventhandler, names='value')
dropdown_canali.observe(canali_eventhandler, names='value')
select_metriche.observe(metriche_eventhandler, names='value')
download_dataframe.on_click(on_button_clicked)

# 显示所有组件
display(widgets.VBox([intRange_week, dropdown_canali, select_metriche, download_dataframe, output]))

关键修改点:

  1. 新增全局变量latest_df,用来存储每次筛选后的DataFrame
  2. 在filtra_dati中用global关键字声明使用这个全局变量,把筛选结果赋值给它
  3. 在on_button_clicked中先判断latest_df是否存在,避免用户还没筛选就点击按钮报错
  4. 用os.path.expanduser处理~/路径,确保在Windows/macOS/Linux下都能正确识别下载目录

方案二:用类封装仪表盘状态(更优雅的长期方案)

如果你的仪表盘后续会增加更多交互功能,全局变量容易导致状态混乱。用类来封装所有UI组件、状态和逻辑会让代码更清晰、更易维护:

import ipywidgets as widgets
import pandas as pd
import os

class DataDashboard:
    def __init__(self, canali_weekly):
        # 初始化数据源和状态
        self.canali_weekly = canali_weekly
        self.latest_df = None
        
        # 创建UI组件
        self.kpi = [k for k in canali_weekly.columns]
        self.intRange_week = widgets.IntRangeSlider(
            value=[1, 52], min=1, max=52, description='Settimane:'
        )
        self.dropdown_canali = widgets.Dropdown(
            options=self._crea_opzioni_con_ALL(canali_weekly.index.get_level_values(0)),
            description='Canali:'
        )
        self.select_metriche = widgets.SelectMultiple(
            options=self.kpi, value=self.kpi, rows=5, description='Metriche:'
        )
        self.download_btn = widgets.Button(description='Scarica i dati', icon='check')
        self.output = widgets.Output()
        
        # 绑定事件
        self._bind_events()
        
        # 组装UI
        self.ui = widgets.VBox([
            self.intRange_week,
            self.dropdown_canali,
            self.select_metriche,
            self.download_btn,
            self.output
        ])
    
    def _crea_opzioni_con_ALL(self, canali):
        # 私有方法:生成包含ALL的选项列表
        return ['ALL'] + list(canali.unique())
    
    def _filtra_dati(self, selezione, canale, metrica):
        self.output.clear_output()
        if canale == "ALL":
            self.latest_df = self.canali_weekly.loc[
                (slice(None), slice(selezione[0], selezione[1])), list(metrica)
            ]
        else:
            self.latest_df = self.canali_weekly.loc[
                (canale, slice(selezione[0], selezione[1])), list(metrica)
            ]
        with self.output:
            pd.set_option('display.max_columns', None)
            pd.set_option('display.max_rows', None)
            display(self.latest_df)
    
    def _on_download_click(self, b):
        with self.output:
            if self.latest_df is not None:
                print("Salvataggio in corso...")
                save_path = os.path.expanduser("~/Downloads/df.xlsx")
                self.latest_df.to_excel(save_path)
                print(f"DataFrame salvato in: {save_path}")
            else:
                print("Errore: Nessun dato disponibile! Effettua prima una selezione.")
    
    # 事件绑定逻辑
    def _bind_events(self):
        def week_handler(change):
            self._filtra_dati(change.new, self.dropdown_canali.value, self.select_metriche.value)
        
        def canale_handler(change):
            self._filtra_dati(self.intRange_week.value, change.new, self.select_metriche.value)
        
        def metrica_handler(change):
            self._filtra_dati(self.intRange_week.value, self.dropdown_canali.value, change.new)
        
        self.intRange_week.observe(week_handler, names='value')
        self.dropdown_canali.observe(canale_handler, names='value')
        self.select_metriche.observe(metrica_handler, names='value')
        self.download_btn.on_click(self._on_download_click)

# 初始化并显示仪表盘
dashboard = DataDashboard(canali_weekly)
display(dashboard.ui)

为什么推荐这个方案?

  • 所有状态(比如latest_df)都作为类的实例变量存在,避免了全局变量的污染
  • UI组件和逻辑都封装在一起,后续修改或新增功能时更清晰
  • 用私有方法(下划线开头)区分内部逻辑和外部接口,代码结构更规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 20:52:49