第5章 Gradio 输入与输出
理解了 Gradio 的基本概念之后,接下来深入看看任何 Gradio 应用中最关键的部分之一:输入和输出。
一个 Gradio 应用只有能接收用户信息并返回有用的结果,才算真正有用。
听起来很简单,但用户可能提供的信息类型五花八门。
他们可能输入一句话、上传图片、从下拉菜单中选择选项、拖动滑块、上传 PDF、录制音频,或者一次性提供多种信息。
针对所有这些场景,Gradio 都有对应的组件。
什么是输入?
输入是你的应用从用户那里接收到的信息。
比如:
name = gr.Textbox()
用户可以在文本框里输入内容。
这个值随后可以传给一个 Python 函数。
看这个例子:
def greet(name):
return f"Hello, {name}!"
这里的 name 就是输入。
什么是输出?
输出是你的应用返回给用户的信息。
比如:
output = gr.Textbox()
你的 Python 函数可能返回一个字符串,Gradio 会把它显示到这个组件里。
这种基本关系用代码表示如下:
def greet(name):
return f"Hello, {name}!"
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Greeting")
button = gr.Button("Greet")
button.click(
fn=greet,
inputs=name,
outputs=output
)
demo.launch()
第一个文本框提供输入,函数处理之后,第二个文本框展示输出。
输入和输出未必是不同的组件类型
一个常见的误解是:某些组件是“输入组件”,另一些是“输出组件”。
实际上,很多 Gradio 组件两种角色都能胜任。
例如:
gr.Textbox()
既可以接收文本,也可以显示文本。
同样地:
gr.Image()
用于接收或显示图像。
组件的用法取决于它连接在哪里。
一个输入与一个输出
从最简单的模式开始。
import gradio as gr
def double(number):
return number * 2
with gr.Blocks() as demo:
number = gr.Number(label="Number")
result = gr.Number(label="Result")
button = gr.Button("Double")
button.click(
fn=double,
inputs=number,
outputs=result
)
demo.launch()
用户输入一个数字,点击按钮触发 double(),随后显示结果。
多个输入
Python 函数可以接收多个参数。
例如:
def calculate_total(price, quantity):
return price * quantity
该函数需要两个输入。
我们提供两个组件:
import gradio as gr
def calculate_total(price, quantity):
return price * quantity
with gr.Blocks() as demo:
price = gr.Number(label="Price")
quantity = gr.Number(label="Quantity")
result = gr.Number(label="Total")
button = gr.Button("Calculate")
button.click(
fn=calculate_total,
inputs=[price, quantity],
outputs=result
)
demo.launch()
这个列表:
inputs=[price, quantity]
决定了值传入函数的顺序。
第一个组件提供 price。
第二个提供 quantity。
从概念上讲,Gradio 执行的是:
calculate_total(price_value, quantity_value)
多个输出
函数也可以返回多个值。
假设我们想分析一句话:
def analyze_text(text):
characters = len(text)
words = len(text.split())
return characters, words
函数返回两个值,因此我们提供两个输出:
import gradio as gr
def analyze_text(text):
characters = len(text)
words = len(text.split())
return characters, words
with gr.Blocks() as demo:
text = gr.Textbox(
label="Text",
lines=6
)
characters = gr.Number(
label="Characters"
)
words = gr.Number(
label="Words"
)
button = gr.Button("Analyze")
button.click(
fn=analyze_text,
inputs=text,
outputs=[characters, words]
)
demo.launch()
第一个返回值传给第一个输出组件,第二个返回值传给第二个输出组件。
输出顺序很重要
假设:
def analyze_text(text):
return characters, words
和:
outputs=[characters_output, words_output]
顺序一一对应,没有问题。
但如果不小心写成:
outputs=[words_output, characters_output]
数值就会显示到错误的位置。
所以保持输入和输出顺序清晰非常重要。
用字典表示结构化结果
有时应用会生成多条相关信息。
在 Python 中可以返回一个字典:
def analyze_person(name, age):
return {
"name": name,
"age": age,
"adult": age >= 18
}
然后用合适的组件(如 gr.JSON)来展示结果。
import gradio as gr
def analyze_person(name, age):
return {
"name": name,
"age": age,
"adult": age >= 18
}
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
age = gr.Number(label="Age")
output = gr.JSON(label="Result")
button = gr.Button("Analyze")
button.click(
fn=analyze_person,
inputs=[name, age],
outputs=output
)
demo.launch()
当函数产出结构化信息时,这种方式非常实用。
输入组件可以设置默认值
可以为组件提供初始值。
例如:
gr.Textbox(
value="Hello!"
)
或者:
gr.Number(
value=10
)
又或者:
gr.Slider(
minimum=0,
maximum=100,
value=50
)
这样能让应用更容易理解,因为用户一眼就能看出组件期望什么样的值。
标签帮助用户理解界面
对比一下:
gr.Textbox()
和:
gr.Textbox(
label="Enter your question"
)
第二种写法传达的信息要清晰得多。
标签应该描述组件的用途,而不是简单地重复它的数据类型。
例如,这样写:
gr.Textbox(label="Question")
通常比这样更有用:
gr.Textbox(label="Textbox")
占位文本
占位符可以在不真正填入内容的情况下给出示例。
gr.Textbox(
label="Question",
placeholder="Ask something about your document..."
)
用户一开始输入,占位符就会消失。因此它很适合用来展示示例和提示。
value 与 placeholder 的区别
看这个例子:
gr.Textbox(
value="Hello"
)
此时文本框里真的包含 "Hello"。
再看看这个:
gr.Textbox(
placeholder="Type something here..."
)
文本框是空的,那句话只是作为提示显示出来。
设计表单时,这个区别很重要。
行数与更大的文本区域
如果需要输入较长的文本,可以这样设置:
gr.Textbox(
lines=10
)
这样能给用户更多的输入空间。
一个文本生成应用可能会这样写:
prompt = gr.Textbox(
label="Prompt",
lines=8,
placeholder="Describe what you want the AI to generate..."
)
让组件不可交互
有时你希望用户只能查看信息,而非编辑。你可以通过 interactive 参数控制组件是否可交互。
例如:
output = gr.Textbox(
label="Generated Result",
interactive=False
)
这对输出类组件尤其有用。
让组件不可见
你也可以通过 visible 参数控制组件的可见性。
gr.Textbox(
visible=False
)
当某个组件只在特定条件下才需要显示时,这一点很有用。后面会介绍如何根据事件动态修改组件属性。
组件不必直接绑定按钮
用户修改组件值时同样可以触发交互。
例如:
name.change(
fn=greet,
inputs=name,
outputs=output
)
这样函数在值变化时直接运行,而无需等待按钮点击。第 7 章将深入讲解事件机制。
理解数据类型
不同组件天然对应不同类型的数据:Textbox 处理字符串,Number 处理数值,Image 处理图像数据,Checkbox 对应布尔值,Dropdown 返回选中项,Slider 返回数值。
这一点很重要,因为 Python 函数需要匹配组件提供的数据类型。例如:
def is_adult(age):
return age >= 18
这里用 Number 更合适。如果改用 Textbox,就需要手动把字符串转成数字:
def is_adult(age):
age = int(age)
return age >= 18
选择合适的组件可以减少不必要的数据转换。
手动转换输入值
有时转换是绕不开的。例如:
def calculate_age_in_months(age):
return int(age) * 12
如果你接收的是文本,可能需要先做类型转换:
age = int(age)
但别盲目转换。
用户可能输入意料之外的值。例如,下面这行会直接报错:
int("hello")
好的应用会在处理前校验输入。
输入校验
假设有如下代码:
def divide(a, b):
return a / b
如果 b 为零会怎样?Python 会抛出异常。
更安全的写法是:
def divide(a, b):
if b == 0:
return "You cannot divide by zero."
return a / b
这样应用就能返回一条有意义的提示信息,而不是让交互直接崩溃。
随着应用复杂度增加,输入校验也越来越重要。
多输入表单
我们来搭一个简单的个人简介生成器。
import gradio as gr
def create_profile(name, age, occupation):
return (
f"Name: {name}\n"
f"Age: {age}\n"
f"Occupation: {occupation}"
)
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
age = gr.Number(label="Age")
occupation = gr.Textbox(label="Occupation")
button = gr.Button("Create Profile")
output = gr.Textbox(
label="Profile"
)
button.click(
fn=create_profile,
inputs=[name, age, occupation],
outputs=output
)
demo.launch()
这展示了一个你会反复使用的模式:收集 → 处理 → 展示。
输入不必来自同一类组件
你可以混合使用不同类型的组件。
例如:
def create_message(name, age, subscribed):
status = "subscribed" if subscribed else "not subscribed"
return f"{name} is {age} years old and is {status}."
界面上可以这样定义:
name = gr.Textbox()
age = gr.Number()
subscribed = gr.Checkbox()
然后:
button.click(
fn=create_message,
inputs=[name, age, subscribed],
outputs=output
)
Gradio 会按正确的顺序传入这些值。
可选输入
你的 Python 函数也可以定义默认值。
例如:
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
这时需要仔细考虑可选参数与界面之间的交互方式。
在许多应用中,把选项明确暴露出来会更清晰:
greeting = gr.Dropdown(
choices=["Hello", "Hi", "Welcome"]
)
然后:
button.click(
fn=greet,
inputs=[name, greeting],
outputs=output
)
这样用户就能直接控制选项了。
把输入输出视为应用契约
可以把组件理解为一种契约。
你的函数声明:
"给我这些值,我返回这些结果。"
你的 Gradio 界面声明:
"我负责从用户那里收集这些值,并展示这些结果。"
当双方达成一致时,应用就能顺畅运行。
一旦不一致,就会遇到报错或令人困惑的行为。
动手试一试
我们来构建一个温度转换器。
你的应用应该:
接受一个摄氏温度
将其转换为华氏温度
展示转换结果
从这个 Python 函数开始:
def celsius_to_fahrenheit(celsius):
return (celsius * 9 / 5) + 32
然后自己动手创建 Gradio 界面。
成功运行后,再修改它,让用户可以在摄氏和华氏之间选择。
要点回顾
输入是传给 Python 函数的值。
输出是返回给用户的值。
函数可以有多个输入。
函数可以返回多个输出。
输入输出的顺序很重要。
组件类型应与应用所需的数据相匹配。
标签和占位符能让界面更易理解。
校验可防止无效的用户输入引发故障。
组件根据连接方式不同,既可以作为输入,也可以作为输出。