我们快速浏览了Quickstart中的 Blocks。 让我们深入探讨。 本指南将涵盖块的结构、事件侦听器及其类型、连续运行事件、更新配置以及使用字典与列表。
We took a quick look at Blocks in the Quickstart. Let's dive deeper. This guide will cover the how Blocks are structured, event listeners and their types, running events continuously, updating configurations, and using dictionaries vs lists.
看看下面的演示。
Take a look at the demo below.
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 应用程序代码将包含在本条款中。
First, note the with gr.Blocks() as demo: clause. The Blocks app code will be contained within this clause.
接下来是组件。 这些是 Interface 中使用的相同组件。 但是,组件不会被传递给某些构造函数,而是在 with 子句中创建时自动添加到块中。
Next come the Components. These are the same Components used in Interface. However, instead of being passed to some constructor, Components are automatically added to the Blocks as they are created within the with clause.
最后, click() 事件侦听器。 事件侦听器定义应用程序内的数据流。 在上面的示例中,侦听器将两个文本框绑定在一起。 文本框 name 充当输入,文本框 output 充当 greet 方法的输出。 单击 Button greet_btn 时会触发此数据流。 与界面一样,事件侦听器可以接受多个输入或输出。
Finally, the click() event listener. Event listeners define the data flow within the app. In the example above, the listener ties the two Textboxes together. The Textbox name acts as the input and Textbox output acts as the output to the greet method. This dataflow is triggered when the Button greet_btn is clicked. Like an Interface, an event listener can take multiple inputs or outputs.
在上面的示例中,你会注意到你可以编辑文本框 name ,但不能编辑文本框 output 。 这是因为任何充当事件侦听器输入的组件都是交互式的。 但是,由于文本框 output 仅用作输出,因此它不是交互式的。 你可以使用 interactive= 关键字参数直接配置组件的交互性。
In the example above, you'll notice that you are able to edit Textbox name, but not Textbox output. This is because any Component that acts as an input to an event listener is made interactive. However, since Textbox output acts only as an output, it is not interactive. You can directly configure the interactivity of a Component with the interactive= keyword argument.
output = gr.Textbox(label="Output", interactive=True)
看看下面的演示:
Take a look at the demo below:
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() 事件侦听器,当用户按下播放时触发。 有关每个组件的事件侦听器,请参阅文档。
Instead of being triggered by a click, the welcome function is triggered by typing in the Textbox inp. This is due to the change() event listener. Different Components support different event listeners. For example, the Video Component supports a play() event listener, triggered when a user presses play. See the Docs for the event listeners for each Component.
Blocks 应用程序不像界面那样仅限于单个数据流。 看看下面的演示:
A Blocks app is not limited to a single data flow the way Interfaces are. Take a look at the demo below:
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")
btoa = gr.Button("a > b")
atob = gr.Button("b > a")
atob.click(increase, a, b)
btoa.click(increase, b, a)
demo.launch()请注意, num1 可以作为 num2 的输入,反之亦然! 随着你的应用程序变得越来越复杂,你将有许多连接各种组件的数据流。
Note that num1 can act as input to num2, and also vice-versa! As your apps get more complex, you will have many data flows connecting various Components.
这是一个“多步”演示示例,其中一个模型(语音到文本模型)的输出被输入到下一个模型(情感分类器)。
Here's an example of a "multi-step" demo, where the output of one model (a speech-to-text model) gets fed into the next model (a sentiment classifier).
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()
到目前为止,你看到的事件侦听器只有一个输入组件。 如果你想让多个输入组件将数据传递给函数,你有两个选项来决定函数如何接受输入组件值:
The event listeners you've seen so far have a single input component. If you'd like to have multiple input components pass data to the function, you have two options on how the function can accept input component values:
作为参数列表,或
as a list of arguments, or
作为单个值字典,由组件键入
as a single dictionary of values, keyed by the component
Let's see an example of each:
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()让我们看一个例子:
import gradio as grwith gr.Blocks() as demo: a = gr.Number(label="a") b = gr.Number(label="b") with gr.Row(): addbtn = gr.Button("Add") subbtn = 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 作为输入。 但是,这些侦听器之间的语法不同。
Both add() and sub() take a and b as inputs. However, the syntax is different between these listeners.
对于 add_btn 侦听器,我们将输入作为列表传递。 函数 add() 将这些输入中的每一个作为参数。 a 的值映射到参数 num1 , b 的值映射到参数 num2 。
To the add_btn listener, we pass the inputs as a list. The function add() takes each of these inputs as arguments. The value of a maps to the argument num1, and the value of b maps to the argument num2.
对于 sub_btn 侦听器,我们将输入作为一个集合传递(注意大括号!)。 函数 sub() 采用单个字典参数 data ,其中键是输入组件,值是这些组件的值。
To the sub_btn listener, we pass the inputs as a set (note the curly brackets!). The function sub() takes a single dictionary argument data, where the keys are the input components, and the values are the values of those components.
你喜欢哪种语法是一个偏好问题! 对于具有许多输入组件的功能,选项 2 可能更易于管理。
It is a matter of preference which syntax you prefer! For functions with many input components, option 2 may be easier to manage.
同样,你可以将多个输出组件的值返回为:
Similarly, you may return values for multiple output components either as:
值列表,或
a list of values, or
由组件键入的字典
a dictionary keyed by the component
我们先看(1)的例子,我们通过返回两个值来设置两个输出组件的值:
Let's first see an example of (1), where we set the values of two output components by returning two values:
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 。
Above, each return statement returns two values corresponding to food_box and status_box, respectively.
除了按顺序返回对应于每个输出组件的值列表,你还可以返回一个字典,其中键对应于输出组件,值作为新值。 这也允许你跳过更新某些输出组件。
Instead of returning a list of values corresponding to each output component in order, you can also return a dictionary, with the key corresponding to the output component and the value as the new value. This also allows you to skip updating some output components.
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 组件。
Notice how when there is no food, we only update the status_box element. We skipped updating the food_box component.
当事件侦听器在返回时影响许多组件,或有条件地影响输出而不影响其他组件时,字典返回很有用。
Dictionary returns are helpful when an event listener affects many components on return, or conditionally affects outputs and not others.
请记住,对于字典返回,我们仍然需要在事件监听器中指定可能的输出。
Keep in mind that with dictionary returns, we still need to specify the possible outputs in the event listener.
事件侦听器函数的返回值通常是相应输出组件的更新值。 有时我们也想更新组件的配置,比如可见性。 在这种情况下,我们返回一个 gr.update() 对象,而不仅仅是更新组件值。
The return value of an event listener function is usually the updated value of the corresponding output Component. Sometimes we want to update the configuration of the Component as well, such as the visibility. In this case, we return a gr.update() object instead of just the update Component value.
import gradio as gr
def change_textbox(choice):
if choice == "short":
return gr.update(lines=2, visible=True, value="Short story: ")
elif choice == "long":
return gr.update(lines=8, visible=True, value="Long story...")
else:
return gr.update(visible=False)
with gr.Blocks() as demo:
radio = gr.Radio(
["short", "long", "none"], label="Essay Length to Write?"
)
text = gr.Textbox(lines=2, interactive=True)
radio.change(fn=change_textbox, inputs=radio, outputs=text)
demo.launch()查看我们如何通过 gr.update() 方法配置文本框本身。 value= 参数仍可用于更新组件配置中的值。
See how we can configure the Textbox itself through the gr.update() method. The value= argument can still be used to update the value along with Component configuration.
你还可以使用事件侦听器的 then 方法连续运行事件。 这将在前一个事件完成运行后运行一个事件。 这对于运行在多个步骤中更新组件的事件很有用。
You can also run events consecutively by using the then method of an event listener. This will run an event after the previous event has finished running. This is useful for running events that update components in multiple steps.
例如,在下面的聊天机器人示例中,我们首先使用用户消息立即更新聊天机器人,然后在模拟延迟后使用计算机响应更新聊天机器人。
For example, in the chatbot example below, we first update the chatbot with the user message immediately, and then update the chatbot with the computer response after a simulated delay.
import gradio as gr
import random
import time
md = """This is some code:
hello
```py
def fn(x, y, z):
print(x, y, z)
"""
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.Button("Clear")
def respond(message, chat_history):
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
chat_history.append((message, md))
time.sleep(1)
return "", chat_history
msg.submit(respond, [msg, chatbot], [msg, chatbot])
clear.click(lambda: None, None, chatbot, queue=False)
demo.launch()
事件侦听器的 .then() 方法执行后续事件,而不管先前事件是否引发任何错误。 如果你只想在前一个事件成功执行后才运行后续事件,请使用 .success() 方法,它采用与 .then() 相同的参数。
The .then() method of an event listener executes the subsequent event regardless of whether the previous event raised any errors. If you'd like to only run subsequent events if the previous event executed successfully, use the .success() method, which takes the same arguments as .then().
你可以使用事件侦听器的 every 参数按固定计划运行事件。 这将在客户端连接打开时 every 几秒运行一次事件。 如果连接关闭,则事件将在以下迭代后停止运行。 请注意,这没有考虑事件本身的运行时间。 因此,使用 every=5 运行时运行时间为 1 秒的函数实际上每 6 秒运行一次。
You can run events on a fixed schedule using the every parameter of the event listener. This will run the event
every number of seconds while the client connection is open. If the connection is closed, the event will stop running after the following iteration.
Note that this does not take into account the runtime of the event itself. So a function
with a 1 second runtime running with every=5, would actually run every 6 seconds.
这是每秒更新的正弦曲线示例!
Here is an example of a sine curve that updates every second!
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()
你可以通过将关联的事件数据类作为类型提示添加到事件侦听器函数中的参数来收集有关事件的特定数据。
You can gather specific data about an event by adding the associated event data class as a type hint to an argument in the event listener function.
例如, .select() 的事件数据可以通过 gradio.SelectData 参数进行类型提示。 当用户选择触发组件的某个部分时会触发此事件,并且事件数据包括有关用户具体选择的内容的信息。 如果用户选择了 Textbox 中的特定单词、 Gallery 中的特定图像或 DataFrame 中的特定单元格,则事件数据参数将包含有关特定选择的信息。
For example, event data for .select() can be type hinted by a gradio.SelectData argument. This event is triggered when a user selects some part of the triggering component, and the event data includes information about what the user specifically selected. If a user selected a specific word in a Textbox, a specific image in a Gallery, or a specific cell in a DataFrame, the event data argument would contain information about the specific selection.
在下面的 2 人井字游戏演示中,用户可以选择 DataFrame 中的一个单元格来进行移动。 事件数据参数包含有关所选特定单元格的信息。 我们可以先检查单元格是否为空,然后用用户的移动更新单元格。
In the 2 player tic-tac-toe demo below, a user can select a cell in the DataFrame to make a move. The event data argument contains information about the specific cell that was selected. We can first check to see if the cell is empty, and then update the cell with the user's move.
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])
demo.launch()