1. 其他教程
  2. 包装布局

包装布局

介绍

Gradio 提供了 blocks 功能,以便轻松布局应用程序。要使用此功能,您需要堆叠或嵌套布局组件,并使用它们创建层次结构。对于小型项目来说,这并不难实现和维护,但随着项目变得更加复杂,这种组件层次结构变得难以维护和重用。

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

示例

我们将按照这个Huggingface Space示例中的实现进行:

实现

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

我们将简要查看它们的renderattach_event函数。你可以从示例代码中查看完整的实现。

所以让我们从LayoutBase类开始。

布局基础类

  1. 渲染函数

    让我们看看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")

现在,请注意文本框变量。这些变量的render参数默认是true。因此,当我们使用with语法并创建这些变量时,它们会在with语法下调用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语法下调用组件的渲染函数,我们实际上是在模拟默认实现。

所以现在让我们考虑两个嵌套的with,看看外部的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()

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

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

  1. 附加事件函数

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

    # other LayoutBase implementations

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

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

应用程序类

  1. 渲染函数
    # 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. 附加事件函数

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

    # 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语法,而不是自己调用该函数。

结论

在本指南中,我们看到了

  • How we wrap the layout
  • How components are rendered
  • 如何使用包装布局类来构建我们的应用程序

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

我希望本指南能帮助您从另一个角度理解布局类,并为您提供如何根据需求使用它们的思路。查看我们示例的完整实现这里