Python JupyterDash无法访问回调修改的对象

2022-04-19 00:00:00 python plotly-dash jupyterdash

问题描述

让我们假设我想要在一个jupyter笔记本/实验室中从一个python类调用一个Dash app,做一些事情,最后访问修改后的对象。我没有完成最后一项任务,这里是一个最小的工作示例:

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  |

如您所见,我能够访问的存储数据帧包含所有列,即使在上面的调用中选择了一列之后也是如此。

假设我选择列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在这里可以很好地工作。另请查看DASHSharing Data Between Callbacks页面上的示例3和4。

根据您的设计计划,您可能还希望查看DASH的DataTable,以便在DASH应用程序中显示数据帧并与之交互。

相关文章