Python JupyterDash 无法访问回调修改对象
Python JupyterDash unable to access callback modified objects
假设我想在 jupyter notebook/lab 中从 python class 调用 Dash 应用程序,做一些事情并最终访问修改后的对象。我没有完成最后一项任务,这里是一个最小的工作示例:
import pandas as pd
import numpy as np
from flask import Flask
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash import html, dcc
from jupyter_dash import JupyterDash
# Initialize flask server and dash app
server = Flask(__name__)
app = JupyterDash(
__name__,
server=server,
external_stylesheets=[
dbc.themes.BOOTSTRAP,
],
)
class DropCol:
def __init__(self, server, app, mode="inline", debug=True):
self.mode = mode
self.debug = debug
self.app = app
self.server = server
self.callbacks(self.app)
def __call__(self, df: pd.DataFrame):
col_options = [{"label": c, "value": c} for c in df.columns]
data_store = dcc.Store(id="data-store", data=df.to_json(date_format="iso", orient="split"))
dropdown = dcc.Dropdown(id="cols", options=col_options)
self.app.layout = html.Div(
id="layout",
children=[
data_store,
dropdown
],
)
self.app.run_server(mode=self.mode, debug=self.debug, port="8000")
def callbacks(self, app):
"""Initialize app callbacks"""
@app.callback(
Output("data-store", "data"),
Input("cols", "value"),
State("data-store", "data"),
prevent_initial_call=True,
)
def on_col_selection(col_name, df_jsonified):
df = (pd.read_json(df_jsonified, orient="split")
.drop(col_name, axis=1)
)
return df.to_json(date_format="iso", orient="split")
@property
def get_data_frame(self):
"""property to retrieve dataframe from data-store"""
df_json = self.app.layout['data-store'].data
return pd.read_json(df_json, orient="split")
# Sample dataframe
df = pd.DataFrame({
"a": np.arange(10),
"b": np.random.randn(10)
})
col_dropper = DropCol(server, app, mode="inline")
col_dropper(df)
# Select one of the column in this cell, then access the data in the next cell
col_dropper.get_data_frame.head(3)
| | a | b |
|---:|----:|----------:|
| 0 | 0 | 1.0964 |
| 1 | 1 | -0.30562 |
| 2 | 2 | 1.34761 |
如您所见,我能够访问的存储数据框包含所有列,即使在我 select 上述调用中的一个列之后也是如此。
假设我select列b
,那么预期的输出是:
col_dropper.get_data_frame.head(3)
| | a |
|---:|----:|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
我使用的包版本是:dash==2.0.0, dash_bootstrap_components==1.0.1, flask==2.0.1, jupyter_dash==0.4.0
据我所知,jupyterdash 扩展只是从 jupyter 中生成一个服务器。所以我相信您 运行 反对 dash 处理服务器状态的方式,类似于普通的旧 dash 应用程序。因此,您实质上是在尝试访问服务器上下文之外的更新组件值,这是不可能的 (see answer here)。即使你是来自 jupyter 的 运行,它仍然是一个独立的服务器,客户端(在下一个 jupyter 单元中是你)无法动态访问,所以你拥有它,你的 get_data_frame
只会永远能够访问您实例化的df
。
为了解决这个问题,您需要以应用程序外部可用的某种持久形式存储更新后的数据框。你如何做到这一点取决于你的用例,但基本上每次你的 on_col_selection
被触发时,你都需要将你的数据框写入应用程序之外的东西。例如,以下将重现您正在寻找的行为:
def callbacks(self, app):
"""Initialize app callbacks"""
@app.callback(
Output("data-store", "data"),
Input("cols", "value"),
State("data-store", "data"),
prevent_initial_call=True,
)
def on_col_selection(col_name, df_jsonified):
df = (pd.read_json(df_jsonified, orient="split")
.drop(col_name, axis=1)
)
df.to_csv("/path/to/some_hidden_file_somewhere.csv", index=False)
return df.to_json(date_format="iso", orient="split")
@property
def get_data_frame(self):
"""property to retrieve dataframe from data-store"""
return pd.read_csv("/path/to/some_hidden_file_somewhere.csv")
如果您要与其他人共享您的代码,您可能需要更复杂的东西来跟踪依赖于用户的文件。例如,磁盘上的 flask cache could work well here. Also check out Examples 3 and 4 on dash's Sharing Data Between Callbacks 页。
根据您的设计计划,您可能还想查看 dash 的 DataTable,以便在 dash 应用程序中显示数据框并与之交互。
假设我想在 jupyter notebook/lab 中从 python class 调用 Dash 应用程序,做一些事情并最终访问修改后的对象。我没有完成最后一项任务,这里是一个最小的工作示例:
import pandas as pd
import numpy as np
from flask import Flask
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash import html, dcc
from jupyter_dash import JupyterDash
# Initialize flask server and dash app
server = Flask(__name__)
app = JupyterDash(
__name__,
server=server,
external_stylesheets=[
dbc.themes.BOOTSTRAP,
],
)
class DropCol:
def __init__(self, server, app, mode="inline", debug=True):
self.mode = mode
self.debug = debug
self.app = app
self.server = server
self.callbacks(self.app)
def __call__(self, df: pd.DataFrame):
col_options = [{"label": c, "value": c} for c in df.columns]
data_store = dcc.Store(id="data-store", data=df.to_json(date_format="iso", orient="split"))
dropdown = dcc.Dropdown(id="cols", options=col_options)
self.app.layout = html.Div(
id="layout",
children=[
data_store,
dropdown
],
)
self.app.run_server(mode=self.mode, debug=self.debug, port="8000")
def callbacks(self, app):
"""Initialize app callbacks"""
@app.callback(
Output("data-store", "data"),
Input("cols", "value"),
State("data-store", "data"),
prevent_initial_call=True,
)
def on_col_selection(col_name, df_jsonified):
df = (pd.read_json(df_jsonified, orient="split")
.drop(col_name, axis=1)
)
return df.to_json(date_format="iso", orient="split")
@property
def get_data_frame(self):
"""property to retrieve dataframe from data-store"""
df_json = self.app.layout['data-store'].data
return pd.read_json(df_json, orient="split")
# Sample dataframe
df = pd.DataFrame({
"a": np.arange(10),
"b": np.random.randn(10)
})
col_dropper = DropCol(server, app, mode="inline")
col_dropper(df)
# Select one of the column in this cell, then access the data in the next cell
col_dropper.get_data_frame.head(3)
| | a | b |
|---:|----:|----------:|
| 0 | 0 | 1.0964 |
| 1 | 1 | -0.30562 |
| 2 | 2 | 1.34761 |
如您所见,我能够访问的存储数据框包含所有列,即使在我 select 上述调用中的一个列之后也是如此。
假设我select列b
,那么预期的输出是:
col_dropper.get_data_frame.head(3)
| | a |
|---:|----:|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
我使用的包版本是:dash==2.0.0, dash_bootstrap_components==1.0.1, flask==2.0.1, jupyter_dash==0.4.0
据我所知,jupyterdash 扩展只是从 jupyter 中生成一个服务器。所以我相信您 运行 反对 dash 处理服务器状态的方式,类似于普通的旧 dash 应用程序。因此,您实质上是在尝试访问服务器上下文之外的更新组件值,这是不可能的 (see answer here)。即使你是来自 jupyter 的 运行,它仍然是一个独立的服务器,客户端(在下一个 jupyter 单元中是你)无法动态访问,所以你拥有它,你的 get_data_frame
只会永远能够访问您实例化的df
。
为了解决这个问题,您需要以应用程序外部可用的某种持久形式存储更新后的数据框。你如何做到这一点取决于你的用例,但基本上每次你的 on_col_selection
被触发时,你都需要将你的数据框写入应用程序之外的东西。例如,以下将重现您正在寻找的行为:
def callbacks(self, app):
"""Initialize app callbacks"""
@app.callback(
Output("data-store", "data"),
Input("cols", "value"),
State("data-store", "data"),
prevent_initial_call=True,
)
def on_col_selection(col_name, df_jsonified):
df = (pd.read_json(df_jsonified, orient="split")
.drop(col_name, axis=1)
)
df.to_csv("/path/to/some_hidden_file_somewhere.csv", index=False)
return df.to_json(date_format="iso", orient="split")
@property
def get_data_frame(self):
"""property to retrieve dataframe from data-store"""
return pd.read_csv("/path/to/some_hidden_file_somewhere.csv")
如果您要与其他人共享您的代码,您可能需要更复杂的东西来跟踪依赖于用户的文件。例如,磁盘上的 flask cache could work well here. Also check out Examples 3 and 4 on dash's Sharing Data Between Callbacks 页。
根据您的设计计划,您可能还想查看 dash 的 DataTable,以便在 dash 应用程序中显示数据框并与之交互。