如何从内部函数访问外部函数变量并实现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]))
关键修改点:
- 新增全局变量
latest_df,用来存储每次筛选后的DataFrame - 在
filtra_dati中用global关键字声明使用这个全局变量,把筛选结果赋值给它 - 在
on_button_clicked中先判断latest_df是否存在,避免用户还没筛选就点击按钮报错 - 用
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
相关产品推荐
相关产品推荐

