python系列&deep_study系列:Gradio中文教程(九)Blocks and Event Listeners




Gradio中文教程(九)Blocks and Event Listeners

块和事件监听器

我们在快速入门中简要描述了Blocks类,作为一种构建自定义演示的方式。让我们深入探讨。

块结构

看一下下面的演示。

import gradio as gr


def greet(name):
    return "Hello " + name + "!"


with gr.Blocks() as demo:
    name = gr.Textbox(label="Name")
    output = gr.Textbox(label="Output Box")
    greet_btn = gr.Button("Greet")
    greet_btn.click(fn=greet, inputs=name, outputs=output, api_name="greet")

demo.launch()

首先,注意with gr.Blocks() as demo:子句。Blocks应用程序代码将包含在这个子句中。
接下来是组件。这些是用于Interface的相同组件。然而,组件不是传递给某个构造函数,而是在with子句内创建时自动添加到Blocks中。

最后,是click()事件监听器。事件监听器定义了应用程序内的数据流。在上面的例子中,监听器将两个文本框连接在一起。文本框name作为输入,文本框output作为输出到greet方法。这种数据流是在按钮greet_btn被点击时触发的。与Interface一样,事件监听器可以接受多个输入或输出。

您还可以使用装饰器附加事件监听器 - 跳过fn参数,并直接分配inputsoutputs

import gradio as gr


with gr.Blocks() as demo:
    name = gr.Textbox(label="Name")
    output = gr.Textbox(label="Output Box")
    greet_btn = gr.Button("Greet")

    @greet_btn.click(inputs=name, outputs=output)
    def greet(name):
        return "Hello " + name + "!"

   

demo.launch()

事件监听器和交互性

在上面的例子中,您会注意到您可以编辑文本框name,但不能编辑文本框output。这是因为任何作为事件监听器输入的组件都是交互式的。然而,由于文本框output仅作为输出,Gradio确定它不应该被设为交互式。您可以覆盖默认行为,并通过布尔interactive关键字参数直接配置组件的交互性。

output = gr.Textbox(label="Output", interactive=True)

注意:如果Gradio组件既不是输入也不是输出会发生什么?如果组件是用默认值构造的,那么它被认为是显示内容的,并且被渲染为非交互式的。否则,它被渲染为交互式的。同样,这种行为可以通过为interactive参数指定一个值来覆盖。

事件监听器的类型

看一下下面的演示:

import gradio as gr

def welcome(name):
    return f"Welcome to Gradio, {name}!"

with gr.Blocks() as demo:
    gr.Markdown(
    """
    # Hello World!
    Start typing below to see the output.
    """)
    inp = gr.Textbox(placeholder="What is your name?")
    out = gr.Textbox()
    inp.change(welcome, inp, out)

demo.launch()

而不是通过点击触发,welcome函数是通过在文本框inp中输入来触发的。这是由于change()事件监听器的作用。不同的组件支持不同的事件监听器。例如,Video组件支持一个play()事件监听器,当用户按下播放时触发。请查看文档以了解每个组件的事件监听器。

多个数据流

与接口不同,Blocks应用程序不限于单一数据流。看一下下面的演示:

import gradio as gr

def increase(num):
    return num + 1

with gr.Blocks() as demo:
    a = gr.Number(label="a")
    b = gr.Number(label="b")
    atob = gr.Button("a > b")
    btoa = gr.Button("b > a")
    atob.click(increase, a, b)
    btoa.click(increase, b, a)

demo.launch()

注意,num1可以作为num2的输入,也可以反过来!随着您的应用程序变得更加复杂,您将有许多数据流连接各种组件。

这里有一个“多步骤”演示的例子,其中一个模型的输出(一个语音转文本模型)被输入到下一个模型(一个情感分类器)。

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()

函数输入列表 vs 字典

到目前为止,您看到的所有事件监听器都有一个单一的输入组件。如果您想让多个输入组件将数据传递给函数,您有两种选择,关于函数如何接受输入组件的值:

  1. 作为参数列表,或者

  2. 作为单个值的字典,键由组件提供

让我们看一个每种方式的例子:

import gradio as gr

with gr.Blocks() as demo:
    a = gr.Number(label="a")
    b = gr.Number(label="b")
    with gr.Row():
        add_btn = gr.Button("Add")
        sub_btn = gr.Button("Subtract")
    c = gr.Number(label="sum")

    def add(num1, num2):
        return num1 + num2
    add_btn.click(add, inputs=[a, b], outputs=c)

    def sub(data):
        return data[a] - data[b]
    sub_btn.click(sub, inputs={a, b}, outputs=c)


demo.launch()

add()sub()都接受ab作为输入。然而,这些监听器之间的语法是不同的。

  1. 对于add_btn监听器,我们将输入作为列表传递。函数add()将这些输入作为参数接受。a的值映射到参数num1,而b的值映射到参数num2

  2. 对于sub_btn监听器,我们将输入作为集合传递(注意花括号!)。函数sub()接受一个单一的字典参数data,其中键是输入组件,值是那些组件的值。

您更喜欢哪种语法取决于个人偏好!对于有许多输入组件的函数,选项2可能更容易管理。

函数返回列表 vs 字典

类似地,您可以返回多个输出组件的值,无论是作为:

  1. 值的列表,或者

  2. 由组件键入的字典

让我们首先看一个例子(1),我们通过返回两个值来设置两个输出组件的值:

with gr.Blocks() as demo:
    food_box = gr.Number(value=10, label="Food Count")
    status_box = gr.Textbox()
    def eat(food):
        if food > 0:
            return food - 1, "full"
        else:
            return 0, "hungry"
    gr.Button("EAT").click(
        fn=eat,
        inputs=food_box,
        outputs=[food_box, status_box]
    )

在上面的例子中,每个返回语句返回两个值,分别对应于food_boxstatus_box

而不是按照顺序返回与每个输出组件对应的值列表,您也可以返回一个字典,其中键对应于输出组件,值作为新值。这也允许您跳过更新某些输出组件。

with gr.Blocks() as demo:
    food_box = gr.Number(value=10, label="Food Count")
    status_box = gr.Textbox()
    def eat(food):
        if food > 0:
            return {food_box: food - 1, status_box: "full"}
        else:
            return {status_box: "hungry"}
    gr.Button("EAT").click(
        fn=eat,
        inputs=food_box,
        outputs=[food_box, status_box]
    )

注意,当没有食物时,我们只更新了status_box元素。我们跳过了更新food_box组件

当事件监听器在返回时影响许多组件,或者有条件地影响输出而不影响其他输出时,字典返回非常有用。

请记住,使用字典返回时,我们仍然需要在事件监听器中指定可能的输出。

更新组件配置

事件监听器函数的返回值通常是对应输出组件的更新值。有时我们也想更新组件的配置,比如可见性。在这种情况下,我们返回一个新的组件,设置我们想要改变的属性。

import gradio as gr


def change_textbox(choice):
    if choice == "short":
        return gr.Textbox(lines=2, visible=True)
    elif choice == "long":
        return gr.Textbox(lines=8, visible=True, value="Lorem ipsum dolor sit amet")
    else:
        return gr.Textbox(visible=False)


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


demo.launch()

看看我们如何通过一个新的gr.Textbox()方法来配置文本框本身。value=参数仍然可以用来更新组件配置的同时更新值。我们没有设置的任何参数将使用它们之前的值。

示例

就像使用gr.Interface一样,当您使用gr.Blocks时,也可以为您的函数添加示例。在这种情况下,类似于实例化任何其他组件,实例化一个gr.Examplesgr.Examples的构造函数接受两个必需的参数:

  • examples: 示例的嵌套列表,其中外层列表包含示例,每个内层列表包含对应于每个输入组件的输入

  • inputs: 当点击示例时应该被填充的组件或组件列表

您还可以设置cache_examples=True,类似于gr.Interface,在这种情况下,必须提供两个额外的参数:

  • outputs: 对应于示例输出的组件或组件列表

  • fn: 运行以生成对应于示例的输出的函数

这里有一个例子,展示了如何在gr.Blocks应用程序中使用gr.Examples

import gradio as gr


def calculator(num1, operation, num2):
    if operation == "add":
        return num1 + num2
    elif operation == "subtract":
        return num1 - num2
    elif operation == "multiply":
        return num1 * num2
    elif operation == "divide":
        return num1 / num2


with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            num_1 = gr.Number(value=4)
            operation = gr.Radio(["add", "subtract", "multiply", "divide"])
            num_2 = gr.Number(value=0)
            submit_btn = gr.Button(value="Calculate")
        with gr.Column():
            result = gr.Number()

    submit_btn.click(
        calculator, inputs=[num_1, operation, num_2], outputs=[result], api_name=False
    )
    examples = gr.Examples(
        examples=[
            [5, "add", 3],
            [4, "divide", 2],
            [-4, "multiply", 2.5],
            [0, "subtract", 1.2],
        ],
        inputs=[num_1, operation, num_2],
    )

if __name__ == "__main__":
    demo.launch(show_api=False)

注意:在Gradio 4.0更高版本中,当您点击示例时,不仅输入组件的值会更新为示例值,而且组件的配置也会恢复到您构建组件时的属性。这确保了即使组件的配置已经改变,示例也与组件兼容。

连续运行事件

您还可以通过使用事件监听器的then方法来连续运行事件。这将在前一个事件完成后运行一个事件。这对于运行分多步更新组件的事件非常有用。

例如,在下面的聊天机器人示例中,我们首先立即用用户消息更新聊天机器人,然后在模拟延迟后用计算机响应更新聊天机器人。

import gradio as gr
import random
import time

with gr.Blocks() as demo:
    chatbot = gr.Chatbot()
    msg = gr.Textbox()
    clear = gr.Button("Clear")

    def user(user_message, history):
        return "", history + [[user_message, None]]

    def bot(history):
        bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
        time.sleep(2)
        history[-1][1] = bot_message
        return history

    msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
        bot, chatbot, chatbot
    )
    clear.click(lambda: None, None, chatbot, queue=False)
    
demo.queue()
demo.launch()

事件监听器的.then()方法执行后续事件,无论前一个事件是否引发任何错误。如果您只想在前一个事件成功执行时运行后续事件,请使用.success()方法,它接受与.then()相同的参数。

连续运行事件

您可以使用事件监听器的every参数在固定的时间表上运行事件。只要客户端连接是打开的,这将每every秒运行一次事件。如果连接关闭,事件将在下一次迭代后停止运行。请注意,这并不考虑事件本身的运行时间。因此,一个运行时间为1秒的函数,如果设置every=5,实际上将每6秒运行一次。还要注意,这个参数不适用于js函数,只适用于与事件监听器关联的Python函数

这里有一个正弦曲线每秒更新的例子!

import math
import gradio as gr
import plotly.express as px
import numpy as np


plot_end = 2 * math.pi


def get_plot(period=1):
    global plot_end
    x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
    y = np.sin(2*math.pi*period * x)
    fig = px.line(x=x, y=y)
    plot_end += 2 * math.pi
    if plot_end > 1000:
        plot_end = 2 * math.pi
    return fig


with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            gr.Markdown("Change the value of the slider to automatically update the plot")
            period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
            plot = gr.Plot(label="Plot (updates every half second)")

    dep = demo.load(get_plot, None, plot, every=1)
    period.change(get_plot, period, plot, every=1, cancels=[dep])


if __name__ == "__main__":
    demo.queue().launch()

收集事件数据

您可以通过在事件监听器函数中将相关的事件数据类作为类型提示添加到一个参数中,来收集有关特定事件的数据。

例如,.select()的事件数据可以通过一个gradio.SelectData参数进行类型提示。当用户选择触发组件的某个部分时,会触发此事件,事件数据包括有关用户具体选择的信息。如果用户在Textbox中选择了一个特定的单词,在Gallery中选择了一个特定的图像,或者在DataFrame中选择了一个特定的单元格,事件数据参数将包含有关特定选择的信息。

在下面的2人井字棋演示中,用户可以在DataFrame中选择一个单元格来移动。事件数据参数包含有关所选特定单元格的信息。我们可以首先检查单元格是否为空,然后更新单元格以反映用户的移动。

import gradio as gr

with gr.Blocks() as demo:
    turn = gr.Textbox("X", interactive=False, label="Turn")
    board = gr.Dataframe(value=[["", "", ""]] * 3, interactive=False, type="array")

    def place(board, turn, evt: gr.SelectData):
        if evt.value:
            return board, turn
        board[evt.index[0]][evt.index[1]] = turn
        turn = "O" if turn == "X" else "X"
        return board, turn

    board.select(place, [board, turn], [board, turn], show_progress="hidden")

demo.launch()

将多个触发器绑定到同一函数

很多时候,您可能希望将多个触发器绑定到同一个函数。例如,您可能希望允许用户点击提交按钮,或者按下回车键来提交表单。您可以使用gr.on方法,并向trigger传递一个触发器列表来实现这一点。

import gradio as gr

with gr.Blocks() as demo:
    name = gr.Textbox(label="Name")
    output = gr.Textbox(label="Output Box")
    greet_btn = gr.Button("Greet")
    trigger = gr.Textbox(label="Trigger Box")
    trigger2 = gr.Textbox(label="Trigger Box")

    def greet(name, evt_data: gr.EventData):
        return "Hello " + name + "!", evt_data.target.__class__.__name__
    
    def clear_name(evt_data: gr.EventData):
        return "", evt_data.target.__class__.__name__
    
    gr.on(
        triggers=[name.submit, greet_btn.click],
        fn=greet,
        inputs=name,
        outputs=[output, trigger],
    ).then(clear_name, outputs=[name, trigger2])


demo.launch()

您也可以使用装饰器语法:

import gradio as gr

with gr.Blocks() as demo:
    name = gr.Textbox(label="Name")
    output = gr.Textbox(label="Output Box")
    greet_btn = gr.Button("Greet")

    @gr.on(triggers=[name.submit, greet_btn.click], inputs=name, outputs=output)
    def greet(name):
        return "Hello " + name + "!"


demo.launch()

您可以使用gr.on通过绑定到实现它的组件的change事件来创建“实时”事件。如果您没有指定任何触发器,函数将自动绑定到所有包含change事件的输入组件的change事件(例如,gr.Textbox有一个change事件,而gr.Button没有)。

import gradio as gr

with gr.Blocks() as demo:
    with gr.Row():
        num1 = gr.Slider(1, 10)
        num2 = gr.Slider(1, 10)
        num3 = gr.Slider(1, 10)
    output = gr.Number(label="Sum")

    @gr.on(inputs=[num1, num2, num3], outputs=output)
    def sum(a, b, c):
        return a + b + c


demo.launch()

您可以像任何常规事件监听器一样,在gr.on后面跟随.then。这个方便的方法应该可以帮助您避免编写大量重复的代码!







老刘

Gradio中文教程(九)Blocks and Event Listenersr

posted @   坦笑&&life  阅读(243)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示