Gradio 提供了 blocks 功能,以便轻松布局应用程序。要使用此功能,您需要堆叠或嵌套布局组件,并使用它们创建层次结构。对于小型项目来说,这并不难实现和维护,但随着项目变得更加复杂,这种组件层次结构变得难以维护和重用。
在本指南中,我们将探讨如何包装布局类,以创建更易于维护和阅读的应用程序,同时不牺牲灵活性。
我们将按照这个Huggingface Space示例中的实现进行:
包装实用程序有两个重要的类。第一个是LayoutBase类,另一个是Application类。
我们将简要查看它们的render和attach_event函数。你可以从示例代码中查看完整的实现。
所以让我们从LayoutBase类开始。
渲染函数
让我们看看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函数。
附加事件函数
该函数未实现,因为它是特定于类的,因此每个类都必须实现其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应用程序。
附加事件函数
让我们看看如何将事件附加到组件上:
# 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语法,而不是自己调用该函数。
在本指南中,我们看到了
因为本指南中使用的类用于演示目的,它们可能仍未完全优化或模块化。但这会使指南变得更长!
我希望本指南能帮助您从另一个角度理解布局类,并为您提供如何根据需求使用它们的思路。查看我们示例的完整实现这里。