Gradio 月活用户达到 100 万!
阅读更多Gradio 月活用户达到 100 万!
阅读更多Gradio 具有 blocks 功能,可以轻松地布局应用程序。要使用此功能,您需要堆叠或嵌套布局组件,并使用它们创建层次结构。对于小型项目,这并不难实现和维护,但是当项目变得更加复杂时,此组件层次结构将变得难以维护和重用。
在本指南中,我们将探讨如何包裹布局类,以创建更易于维护和易于阅读的应用程序,而又不牺牲灵活性。
我们将遵循此 Huggingface Space 示例中的实现
包裹实用程序具有两个重要的类。第一个是 LayoutBase
类,另一个是 Application
类。
为了简洁起见,我们将查看它们的 render
和 attach_event
函数。您可以从 示例代码 中查看完整的实现。
那么让我们从 LayoutBase
类开始。
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
函数。
Attach Event 函数
该函数被保留为未实现,因为它特定于类,因此每个类都必须实现其 attach_event
函数。
# other LayoutBase implementations
def attach_event(self, block_dict: Dict[str, Block]) -> None:
raise NotImplementedError
查看 Application
类的 attach_event
函数中的 block_dict
变量。
# 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 应用程序。
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。