我们在快速入门中简要介绍了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 参数并直接分配 inputs 和 outputs
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 关键字参数直接配置组件的交互性,例如 gr.Textbox(interactive=True)。
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 应用程序不像 Interfaces 那样仅限于单个数据流。请看下面的演示
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()
到目前为止,您看到的事件监听器都只有一个输入组件。如果您希望有多个输入组件将数据传递给函数,您有两种方式让函数接受输入组件的值
让我们看一个各自的例子
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() 都将 a 和 b 作为输入。然而,这些监听器之间的语法不同。
add_btn 监听器,我们将输入作为列表传递。函数 add() 将这些输入作为参数。a 的值映射到参数 num1,b 的值映射到参数 num2。sub_btn 监听器,我们将输入作为集合传递(注意花括号!)。当您传递一个集合时,函数 sub() 会接收一个名为 data 的单个字典参数,其中键是输入组件,值是这些组件的值。选择哪种语法取决于您的偏好!对于有许多输入组件的函数,选项 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]
)在上面,每个 return 语句都返回两个值,分别对应 food_box 和 status_box。
注意:如果您的事件监听器只有一个输出组件,则不应将其作为单项列表返回。这不会起作用,因为 Gradio 不知道是否应将该外部列表解释为您返回值的一部分。您应该直接返回该值。
现在,让我们看选项(2)。您可以返回一个字典,而不是返回一个按顺序对应于每个输出组件的值列表,其中键对应于输出组件,值是新值。这还允许您跳过更新某些输出组件。
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, buttons=["copy"])
radio.change(fn=change_textbox, inputs=radio, outputs=text)
demo.launch()
看看我们如何通过新的 gr.Textbox() 方法配置文本框本身。value= 参数仍然可以用于在更新组件配置的同时更新值。我们未设置的任何参数都将保留其先前的值。
在某些情况下,您可能希望保留组件的值不变。Gradio 包含一个特殊函数 gr.skip(),可以从您的函数中返回。返回此函数将保持输出组件(或组件)的值不变。让我们用一个例子来说明
import random
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
clear_button = gr.Button("Clear")
skip_button = gr.Button("Skip")
random_button = gr.Button("Random")
numbers = [gr.Number(), gr.Number()]
clear_button.click(lambda : (None, None), outputs=numbers)
skip_button.click(lambda : [gr.skip(), gr.skip()], outputs=numbers)
random_button.click(lambda : (random.randint(0, 100), random.randint(0, 100)), outputs=numbers)
demo.launch()请注意返回 None(通常会将组件的值重置为空状态)与返回 gr.skip()(保持组件值不变)之间的区别。
如果您有多个输出组件,并且希望保持它们的值都不变,您可以只返回一个 gr.skip(),而不是返回一个包含每个元素的跳过元组。
您还可以使用事件监听器的 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 + [{"role": "user", "content": user_message}]
def bot(history):
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
time.sleep(2)
history.append({"role": "assistant", "content": 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.launch()
事件监听器的 .then() 方法会执行后续事件,无论前一个事件是否引发了任何错误。如果您只想在前一个事件成功执行后才运行后续事件,请使用 .success() 方法,它的参数与 .then() 相同。相反,如果您只想在前一个事件失败(即引发错误)后才运行后续事件,请使用 .failure() 方法。这对于错误处理工作流特别有用,例如在操作失败时显示错误消息或恢复到先前的状态。
通常,您可能希望将多个触发器绑定到同一个函数。例如,您可能希望允许用户单击提交按钮或按 Enter 键来提交表单。您可以使用 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")
def greet(name, evt_data: gr.EventData):
return "Hello " + name + "!", evt_data.target.__class__.__name__
def clear_name(evt_data: gr.EventData):
return ""
gr.on(
triggers=[name.submit, greet_btn.click],
fn=greet,
inputs=name,
outputs=[output, trigger],
).then(clear_name, outputs=[name])
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 事件的输入组件的 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。这个便捷的方法可以帮助您省去编写大量重复代码的麻烦!
如果您想将组件的值始终设置为其他组件的值的函数,可以使用以下简写方式
with gr.Blocks() as demo:
num1 = gr.Number()
num2 = gr.Number()
product = gr.Number(lambda a, b: a * b, inputs=[num1, num2])这在功能上与以下代码相同
with gr.Blocks() as demo:
num1 = gr.Number()
num2 = gr.Number()
product = gr.Number()
gr.on(
[num1.change, num2.change, demo.load],
lambda a, b: a * b,
inputs=[num1, num2],
outputs=product
)