# Gradio Blocks 简介 [[Gradio Blocks 简介]]

在之前的章节中，我们已经探索并使用 `Interface` 类创建了一些演示。在本章中，我们将介绍我们新开发的低级 API，名为 `gradio.Blocks` 。

那么，`Interface` 和 `Blocks` 之间有什么区别呢？

- ⚡ `Interface` ：一个高级 API，你只需提供输入和输出列表即可创建完整的机器学习演示。

- 🧱 `Blocks` ：一个低级的 API，你可以使用它来完全控制你的应用程序的数据流和布局。你可以使用 `Blocks` （类似于“构建的砖块”）构建非常复杂的多步骤应用程序。

### 为什么要使用 Blocks 🧱？[[ 为什么要使用 Blocks 🧱]]

正如我们在前几节中看到的，通过 `Interface` 类可以仅仅使用几行代码轻松创建成熟的机器学习 demo。 `Interface` API 非常易于使用，但灵活性不如 `Blocks` API。例如，你可能想要：

- 将相关演示组合为一个 web 应用程序中的多个选项卡
- 更改 demo 的布局，例如指定输入和输出的位置
- 创建多步骤界面，其中一个模型的输出成为下一个模型的输入，或者通常具有更灵活的数据流
- 根据用户输入更改组件的属性 （例如，下拉列表中的选项） 或隐藏/显示部分组件

我们将在下面探讨所有这些需求。

### 使用块创建简单 demo [[使用块创建简单demo]]

安装 Gradio 后，然后在 Python 脚本、Jupyter 笔记本或 Colab 笔记本运行下面的代码，就可以使用 Block 翻转字符串。

```py
import gradio as gr

def flip_text(x):
    return x[::-1]

demo = gr.Blocks()

with demo:
    gr.Markdown(
        """
    # Flip Text!
    Start typing below to see the output.
    """
    )
    input = gr.Textbox(placeholder="Flip this text")
    output = gr.Textbox()

    input.change(fn=flip_text, inputs=input, outputs=output)

demo.launch()
```

上面简单示例介绍了 Blocks 的 4 个基本概念：

1. 通过在 `with gradio.Blocks` 上下文中实例化 Python 对象，Blocks 支持构建组合了 markdown、HTML、按钮和交互式组件的 Web 网页。
> [!TIP]
> 🙋如果你不熟悉 Python 中的 `with` 语句，我们建议你先查看优秀的 [realpython 教程](https://realpython.com/python-with-statement) 后再回来查看🤗。
实例化组件的顺序很重要，因为每个元素都按照创建的顺序渲染到 Web 网页中。（更复杂的布局将在下面讨论）

2. 你可以在代码中的任何位置定义常规 Python 函数，并指定 `Blocks` 在用户输入的情况下运行它们。在我们的示例中，我们使用了一个可以“翻转”输入的文本简单的函数，这个函数可以是任意的Python 函数，从简单的计算到处理来自机器学习模型的预测等。

3. 你可以将事件指定给任何 `Blocks` 组件。这些事件可以支持在组件被单击、更改等情况下运行函数。当你分配一个事件时，你需要传入三个参数： `fn` ：应该被调用的函数， `inputs` ：输入组件（列表），以及 `outputs` ：应该被调用的输出组件（列表）。

   在上面的示例中，当名为 `input` 的 `Textbox` 中的值发生变化时，会自动触发事件运行 `flip_text()` 函数。该事件读取第一个名为 `input` 的 `Textbox` 中的值，将其作为参数传递给 `flip_text()` ，然后 `flip_text()`  会返回一个值，该值会被分配给我们的第二个名为 `output` 的 `Textbox` 。

   要查看每个组件所支持的事件列表，请参阅 Gradio [文档](https://www.gradio.app/docs) 。

4. Blocks 会根据你定义的事件触发器自动确定组件是否是交互式的 （可以接受用户输入）。在我们的示例中，第一个文本框是交互式的，因为它的值能够被 `flip_text()` 函数使用。第二个文本框不是交互式的，因为它的值从未用作输入。在某些情况下，你可能想要覆盖自动的判断，你可以给组件的 `interactive` 参数传递一个布尔值来覆盖自动的判断。（例如 `gr.Textbox(placeholder="Flip this text", interactive=True)` ）。

### 自定义演示的布局 [[自定义演示的布局]]

我们如何使用 `Blocks` 来定制我们的演示的布局？默认情况下， `Blocks` 在一列中垂直排列创建的组件。你可以通过使用 `with gradio.Column():` 创建一列或使用 `with gradio.Row():` 创建一行，并且还可以在这些列或者行的上下文中创建其他的组件来更改布局。

你应该记住：在 `Column` 下创建的任何组件（这也是默认设置） 都将垂直排列。在 `Row` 下创建的任何组件都将水平排列，类似于 [Web 开发中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) 。

最后，你还可以使用 `with gradio.Tabs()` 上下文管理器为你的 demo 创建选项卡。在做个饭上下文中，你可以通过使用 `gradio.TabItem(name_of_tab):` 指定来创建多个选项卡。在 `gradio.TabItem(name_of_tab):` 中创建的任何组件都会排列在该选项卡中。

现在让我们在 demo 中添加一个 `flip_image()` 函数并添加一个翻转图像的新选项卡。下面是具有 2 个选项卡并使用了一个 Row 的示例：

```py
import numpy as np
import gradio as gr

demo = gr.Blocks()

def flip_text(x):
    return x[::-1]

def flip_image(x):
    return np.fliplr(x)

with demo:
    gr.Markdown("Flip text or image files using this demo.")
    with gr.Tabs():
        with gr.TabItem("Flip Text"):
            with gr.Row():
                text_input = gr.Textbox()
                text_output = gr.Textbox()
            text_button = gr.Button("Flip")
        with gr.TabItem("Flip Image"):
            with gr.Row():
                image_input = gr.Image()
                image_output = gr.Image()
            image_button = gr.Button("Flip")

    text_button.click(flip_text, inputs=text_input, outputs=text_output)
    image_button.click(flip_image, inputs=image_input, outputs=image_output)

demo.launch()
```

你会注意到，在本示例中，我们在每个选项卡中都创建了一个 `Button` 组件，并且为每个按钮分配了一个点击事件，点击后运行事件对应的函数。

### 探索事件和状态 [[探索事件和状态]]

使用 `Blocks` 不仅可以控制布局，还可以让你对触发函数调用的事件进行细粒度控制。每个组件和许多布局都有它们支持的特定事件。

例如， `Textbox` 组件有两个事件： `change()` （当文本框内的值发生变化时），和 `submit()` （当用户在文本框上输入并按下回车键时）。更复杂的组件可以有更多的事件：例如， `Audio` 组件还具有播放音频文件、清除音频文件、暂停等各种独立事件。更多的独立事件请参阅每个组件支持的事件的文档。

你可以使用事件触发器将要运行的函数附加到一个、多个或全部事件中。例如，你可以通过在组件实例中调用事件名称作为函数来创建一个事件触发器 —— 例如 `textbox.change(...)` 或 `btn.click(...)` 。如前所述，该函数接受三个参数：

- `fn` ：要运行的函数
- `inputs` ：应作为函数的输入参数提供的组件（列表）。每个组件的值按顺序映射到相应的函数参数。如果函数不带任何参数，则此参数可以为 None。
- `outputs` ：应根据函数返回的值更新的组件（列表）。函数的每个返回值都按照顺序赋值给相应组件。如果函数不返回任何内容，则此参数可以为 None。

你甚至可以使输入和输出组件设置为同一个组件，就像我们在这个使用 GPT 模型进行文本补全的示例中所做的那样：

```py
import gradio as gr

api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")

def complete_with_gpt(text):
    # 使用文本的最后 50 个字符作为上下文
    return text[:-50] + api(text[-50:])

with gr.Blocks() as demo:
    textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
    btn = gr.Button("Generate")

    btn.click(complete_with_gpt, textbox, textbox)

demo.launch()
```

### 创建多步骤 demo [[创建多步骤 demo]]

在某些情况下，你可能需要一个多步骤演示，将其中一个函数的输出作为下一个函数的输入。使用 `Blocks` 很容易做到这一点，因为你可以使用一个组件作为一个事件触发器的输入，同时又作为另一个事件触发器的输出。看一下下面的示例，文本组件的输入是语音转文本模型的结果，同时，这个文本也被传递到情感分析模型中：

```py
from transformers import pipeline

import gradio as gr

asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")

def speech_to_text(speech):
    text = asr(speech)["text"]
    return text

def text_to_sentiment(text):
    return classifier(text)[0]["label"]

demo = gr.Blocks()

with demo:
    audio_file = gr.Audio(type="filepath")
    text = gr.Textbox()
    label = gr.Label()

    b1 = gr.Button("Recognize Speech")
    b2 = gr.Button("Classify Sentiment")

    b1.click(speech_to_text, inputs=audio_file, outputs=text)
    b2.click(text_to_sentiment, inputs=text, outputs=label)

demo.launch()
```

### 更新组件属性 [[更新组件属性]]

到目前为止，我们已经了解了如何创建事件来更新另一个组件的值。但是，如果你想更改组件的其他属性，例如文本框的显示/隐藏或单选按钮中的选项，又该怎么办呢？你可以通过返回组件类的 `update()` 方法来代替函数的常规返回值来做到这一点。

这很容易用一个例子来说明：

```py
import gradio as gr

def change_textbox(choice):
    if choice == "short":
        return gr.Textbox.update(lines=2, visible=True)
    elif choice == "long":
        return gr.Textbox.update(lines=8, visible=True)
    else:
        return gr.Textbox.update(visible=False)

with gr.Blocks() as block:
    radio = gr.Radio(
        ["short", "long", "none"], label="What kind of essay would you like to write?"
    )
    text = gr.Textbox(lines=2, interactive=True)

    radio.change(fn=change_textbox, inputs=radio, outputs=text)
    block.launch()
```

我们刚刚探索了 `Blocks` 的所有核心概念！就像 `Interfaces` 一样 你可以创建很酷的 demo，可以通过在 `launch()` 方法中使用 `share=True` 选项创建共享的链接，或者部署在 [Hugging Face Spaces](https://huggingface.co/spaces) 上。

