Gradio 月活用户达到 100 万!

阅读更多
Gradio logo
  1. 其他教程
  2. 布局包裹

布局包裹

简介

Gradio 具有 blocks 功能,可以轻松地布局应用程序。要使用此功能,您需要堆叠或嵌套布局组件,并使用它们创建层次结构。对于小型项目,这并不难实现和维护,但是当项目变得更加复杂时,此组件层次结构将变得难以维护和重用。

在本指南中,我们将探讨如何包裹布局类,以创建更易于维护和易于阅读的应用程序,而又不牺牲灵活性。

示例

我们将遵循此 Huggingface Space 示例中的实现

实现

包裹实用程序具有两个重要的类。第一个是 LayoutBase 类,另一个是 Application 类。

为了简洁起见,我们将查看它们的 renderattach_event 函数。您可以从 示例代码 中查看完整的实现。

那么让我们从 LayoutBase 类开始。

LayoutBase 类

  1. Render 函数

    让我们看一下 LayoutBase 类中的 render 函数

# other LayoutBase implementations

def render(self) -> None:
    with self.main_layout:
        for renderable in self.renderables:
            renderable.render()

    self.main_layout.render()

乍一看这有点令人困惑,但是如果您考虑默认实现,则可以轻松理解它。让我们看一个例子

在默认实现中,我们正在做的是

with Row():
    left_textbox = Textbox(value="left_textbox")
    right_textbox = Textbox(value="right_textbox")

现在,请注意 Textbox 变量。这些变量的 render 参数默认设置为 true。因此,当我们使用 with 语法并创建这些变量时,它们会在 with 语法下调用 render 函数。

我们知道 render 函数在构造函数中被调用,其实现来自 gradio.blocks.Block

class Block:
    # constructor parameters are omitted for brevity
    def __init__(self, ...):
        # other assign functions 

        if render:
            self.render()

因此,我们的实现看起来像这样

# self.main_layout -> Row()
with self.main_layout:
    left_textbox.render()
    right_textbox.render()

这意味着通过在 with 语法下调用组件的 render 函数,我们实际上是在模拟默认实现。

所以现在让我们考虑两个嵌套的 with,看看外部的那个是如何工作的。为此,让我们使用 Tab 组件扩展我们的示例

with Tab():
    with Row():
        first_textbox = Textbox(value="first_textbox")
        second_textbox = Textbox(value="second_textbox")

这次请注意 Row 和 Tab 组件。我们已经在上面创建了 Textbox 变量,并使用 with 语法将它们添加到 Row 中。现在我们需要将 Row 组件添加到 Tab 组件。您可以看到 Row 组件是使用默认参数创建的,因此其 render 参数为 true,这就是为什么 render 函数将在 Tab 组件的 with 语法下执行。

为了模仿此实现,我们需要在 main_layout 变量的 with 语法之后调用 main_layout 变量的 render 函数。

因此,实现看起来像这样

with tab_main_layout:
    with row_main_layout:
        first_textbox.render()
        second_textbox.render()

    row_main_layout.render()

tab_main_layout.render()

默认实现和我们的实现是相同的,但是我们自己使用了 render 函数。因此,这需要一些工作。

现在,让我们看一下 attach_event 函数。

  1. Attach Event 函数

    该函数被保留为未实现,因为它特定于类,因此每个类都必须实现其 attach_event 函数。

    # other LayoutBase implementations

    def attach_event(self, block_dict: Dict[str, Block]) -> None:
        raise NotImplementedError

查看 Application 类的 attach_event 函数中的 block_dict 变量。

Application 类

  1. Render 函数
    # other Application implementations

    def _render(self):
        with self.app:
            for child in self.children:
                child.render()

        self.app.render()

LayoutBase 类的 render 函数的解释中,我们可以理解 child.render 部分。

因此,让我们看一下底部部分,为什么我们要调用 app 变量的 render 函数?调用此函数很重要,因为如果我们查看 gradio.blocks.Blocks 类中的实现,我们可以看到它正在将组件和事件函数添加到根组件中。换句话说,它正在创建和构建 gradio 应用程序。

  1. Attach Event 函数

    让我们看看如何将事件附加到组件

    # other Application implementations

    def _attach_event(self):
        block_dict: Dict[str, Block] = {}

        for child in self.children:
            block_dict.update(child.global_children_dict)

        with self.app:
            for child in self.children:
                try:
                    child.attach_event(block_dict=block_dict)
                except NotImplementedError:
                    print(f"{child.name}'s attach_event is not implemented")

您可以从示例代码中看到为什么在 LayoutBase 类中使用 global_children_list。这样,应用程序中的所有组件都收集到一个字典中,因此组件可以使用其名称访问所有组件。

这里再次使用 with 语法将事件附加到组件。如果我们查看 gradio.blocks.Blocks 类中的 __exit__ 函数,我们可以看到它正在调用 attach_load_events 函数,该函数用于为组件设置事件触发器。因此,我们必须使用 with 语法来触发 __exit__ 函数。

当然,我们可以不使用 with 语法来调用 attach_load_events,但是该函数需要一个 Context.root_block,并且它在 __enter__ 函数中设置。因此,我们在此处使用了 with 语法,而不是自己调用该函数。

结论

在本指南中,我们看到了

  • 我们如何包裹布局
  • 组件是如何渲染的
  • 我们如何使用包裹的布局类来构建我们的应用程序

由于本指南中使用的类用于演示目的,因此它们可能仍然没有完全优化或模块化。但这会使指南变得更长!

我希望本指南可以帮助您获得对布局类的另一种看法,并为您提供关于如何将它们用于您的需求的想法。在此处查看我们的示例的完整实现 here