如何在单独的文件中定义回调?(情节破折号)

np8*_*np8 8 python plotly-dash

背景

Dash Web 应用程序有一个 dash 应用程序实例,通常命名为app,并像这样启动:

app = dash.Dash(__name__)
Run Code Online (Sandbox Code Playgroud)

然后,使用callback装饰器将回调添加到应用程序中:

@app.callback(...)
def my_function(...):
    # do stuff.
Run Code Online (Sandbox Code Playgroud)

在您找到的大多数教程中,回调是使用app.py. 这当然只是 MWE 的做事方式。在实际应用程序中,将代码分离到模块和包将大大提高可读性和可维护性,但天真地将回调和布局分离只会导致循环导入。

app.py单页应用程序中分离回调和布局的正确方法是什么?

移动电源

这是问题的最小(非)工作示例

文件结构

.
??? my_dash_app
?   ??? app.py
?   ??? views
?       ??? first_view.py
?       ??? __init__.py
??? setup.py
Run Code Online (Sandbox Code Playgroud)

设置文件

import setuptools

setuptools.setup(
    name='dash-minimal-realworld',
    version='1.0.0',
    install_requires=['dash>=1.12.0'],
    packages=setuptools.find_packages(),
)
Run Code Online (Sandbox Code Playgroud)

应用程序

import dash

from my_dash_app.views.first_view import make_layout

app = dash.Dash(__name__)
app.layout = make_layout()


if __name__ == '__main__':
    app.run_server(debug=True)
Run Code Online (Sandbox Code Playgroud)

first_view.py

@app.callback(...)
def my_function(...):
    # do stuff.
Run Code Online (Sandbox Code Playgroud)

python ./my_dash_app/app.py结果运行到循环依赖中:

ImportError: cannot import name 'make_layout' from 'my_dash_app.views.first_view' (c:\tmp\dash_minimal_realworld\my_dash_app\views\first_view.py)
Run Code Online (Sandbox Code Playgroud)

San*_*nMu 26

A little late to the party but I have found this to be a straightforward way of doing it:

  1. You create a separate script called 'callbacks.py' (for example)
  2. Define a function within callbacks.py which takes a dash.Dash object (i.e. the app) as a parameter (you can of course pass more arguments if necessary) and within which you define all your callbacks as you normally would in the main script:
def get_callbacks(app):
    @app.callback([Output("figure1", "figure")],
                  [Input("child1", "value")])
    def callback1(figure):
        return

    @app.callback([Output("figure2", "figure")],
                  [Input("child2", "value")])
    def callback2(figure):
        return
Run Code Online (Sandbox Code Playgroud)
  1. Within the main script, simply import the function and call it after instantiating the dash.Dash object passing the same object into it:
import dash
from callbacks import get_callbacks
import layout

app = dash.Dash(__name__)
app.layout = layout.layout

get_callbacks(app)
Run Code Online (Sandbox Code Playgroud)

  • 无论如何,您知道如何使用 pytest 对导入的回调进行单元测试吗?我导入了 `get_callbacks(app)` 并尝试调用 `callback1.__wrapper__(figure)` 但没有运气 (3认同)

小智 13

我知道现在回答你的问题已经太晚了,但也许其他人会发现它很有用。

我希望能够在单独的文件中创建回调,但是我认为虽然从主仪表板模块导入应用程序效果很好,但对于阅读代码的其他人来说可能不清楚。

我创建了一个用于初始化回调的回调管理器。该管理器附加到主应用程序模块中的应用程序。

回调管理器.py

from dataclasses import dataclass, field
from typing import Callable, List, Union
from dash.dependencies import handle_callback_args
from dash.dependencies import Input, Output, State


@dataclass
class Callback:
    func: Callable
    outputs: Union[Output, List[Output]]
    inputs: Union[Input, List[Input]]
    states: Union[State, List[State]] = field(default_factory=list)
    kwargs: dict = field(default_factory=lambda: {"prevent_initial_call": False})


class CallbackManager:
    def __init__(self):
        self._callbacks = []

    def callback(self, *args, **kwargs):
        output, inputs, state, prevent_initial_call = handle_callback_args(
            args, kwargs
        )

        def wrapper(func):
            self._callbacks.append(
                Callback(
                    func,
                    output,
                    inputs,
                    state,
                    {"prevent_initial_call": prevent_initial_call}
                )
             )

        return wrapper

    def attach_to_app(self, app):
        for callback in self._callbacks:
            app.callback(
                callback.outputs, callback.inputs, callback.states, **callback.kwargs
            )(callback.func)
Run Code Online (Sandbox Code Playgroud)

回调.py

import dash

from callback_manager import CallbackManager

callback_manager = CallbackManager()


@callback_manager.callback(
    dash.dependencies.Output('label', 'children'),
    [dash.dependencies.Input('call_btn', 'n_clicks')])
def update_label(n_clicks):
    if n_clicks > 0:
        return "Callback called!"
Run Code Online (Sandbox Code Playgroud)

应用程序.py

import dash
import dash_html_components as html

from callbacks import callback_manager

app = dash.Dash(__name__)
callback_manager.attach_to_app(app)

app.layout = html.Div([
    html.Div(id="label"),
    html.Button('Call callback', id='call_btn', n_clicks=0),
])
if __name__ == '__main__':
    app.run_server(debug=True)
Run Code Online (Sandbox Code Playgroud)

请注意,您可以拥有多个带有回调的文件,并使用关键字导入它们as

from callbacks1 import callback_manager as callback_manager1
from callbacks2 import callback_manager as callback_manager2

app = dash.Dash(__name__)
callback_manager1.attach_to_app(app)
callback_manager2.attach_to_app(app)
Run Code Online (Sandbox Code Playgroud)

我相信这样做更明确。


小智 12

您可以只使用装饰器@dash.callback而不是@app.callback. 然后删除该行from my_dash_app.app import appfirst_view.py您将摆脱循环依赖。

从文档来看:是Dash 2.0 中引入@dash.callback的替代方案。@app.callback (where app = dash.Dash())它允许您注册回调而无需定义或导入应用程序对象。调用签名是相同的,并且可以app.callback在所有情况下使用它来代替。


Tor*_*xed 10

我不认为(但我可能是错的)本身有一种正确的方法,但是你可以做的是maindash.py在你的启动代码周围有一个中央模块()app = dash.Dash(__name__),并且有不同的回调,只需appmy_dash_app.maindash. 这将在它们自己的单独模块中设置回调,但会为app实例重复使用该一个中央模块。

像这样显示它的概述是最简单的: 微博

app.py成为启动一切的主脚本。maindash.py负责创建主应用程序实例。first_view.py是定义装饰器以设置所有回调的地方。

结果如下: 在此处输入图片说明

.
??? my_dash_app
?   ??? app.py
?   ??? maindash.py
?   ??? views
?       ??? first_view.py
?       ??? __init__.py
??? setup.py
Run Code Online (Sandbox Code Playgroud)

由于在 Python 中重用了导入,因此from my_dash_app.maindash import app从不同的其他模块(例如事件处理程序和主脚本)执行多次操作并没有什么真正的危害。他们将共享相同的导入实例 - 因此也重新使用该dash.Dash()实例。

只要确保在设置处理程序之前导入中央模块,就可以了。

以下是为测试而分离的代码片段:

应用程序

.
??? my_dash_app
?   ??? app.py
?   ??? maindash.py
?   ??? views
?       ??? first_view.py
?       ??? __init__.py
??? setup.py
Run Code Online (Sandbox Code Playgroud)

maindash.py

from my_dash_app.maindash import app
from my_dash_app.views.first_view import make_layout

if __name__ == '__main__':
    app.layout = make_layout()
    app.run_server(debug=True)
Run Code Online (Sandbox Code Playgroud)

first_view.py

import dash
app = dash.Dash(__name__)
Run Code Online (Sandbox Code Playgroud)


maw*_*cks 5

这是对旧帖子的较晚回复,但这是一种非常简单的方法来完全分离布局、回调和应用程序,而不会引入额外的复杂性。

应用程序.py

from dash import Dash

from layouts import my_layout
from callbacks import my_callback, my_callback_inputs, my_callback_outputs

if __name__ == "__main__":
    app = Dash(__name__)

    app.layout = my_layout
    app.callback(my_callback_outputs, my_callback_inputs)(my_callback)

    app.run_server()
Run Code Online (Sandbox Code Playgroud)

它的工作原理是使用装饰器语法,装饰器语法应该是“语法糖”,以使事情变得更简单。然而,对于这个特定问题,许多提出的解决方案实际上增加了耦合性和复杂性,同时保留了装饰器语法。在这种情况下,不使用装饰器会更简单。

callbacks.py,中my_callback定义时没有任何装饰器:

回调.py

my_callback_inputs = []
my_callback_outputs = []


def my_callback():
    return
Run Code Online (Sandbox Code Playgroud)