这许多游戏中，我最喜欢的是假扮另一个阿斯特里昂。我假装他来做客，我带他看看房屋。我毕恭毕敬对他说：现在我们回到先前的岔口，或者现在我们进另一个庭院，或者我早就说过你会喜欢小水沟的，或者现在你将看到一个积满泥沙的蓄水池，或者你还会看到一分为二的地下室。有时候我搞错了，我们俩高兴地大笑。
作者：APlayBoy 链接：https://zhuanlan.zhihu.com/p/679668818 来源：知乎 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。1. 引言：快速入门Gradio —— 你的AI展示利器Gradio的简介在人工智能飞速发展的今天，向世界展示你的AI模型变得越来越重要。这就是Gradio发挥作用的地方：一个简单、直观、且强大的工具，让初学者到专业开发者的各个层次的人都能轻松展示和分享他们的AI模型。选择Gradio的理由\quad\quad Gradio的魅力在于它的易用性。无需复杂的前端知识，只需几行代码，你就能将任何机器学习模型转化为一个美观、交互式的界面。这不仅使模型展示变得简单，还为非技术背景的人群提供了探索和理解AI的窗口。\quad\quad 而对于追求深度定制和企业级应用的开发者来说，Gradio同样提供了强大的功能和灵活的配置选项。无论是定制复杂的用户界面，还是在不同的环境中部署你的应用，Gradio都能胜任。\quad\quad 在这里，我们将一起探索Gradio的各个方面。从基础概念的讲解到高级应用的实践，再到企业级部署的策略，我们将逐步深入，帮助你全面掌握Gradio。无论你是AI领域的新手，还是资深的技术专家，相信在这里，你都能找到值得一读的内容。本教程中的所有示例和代码都是基于Gradio的新版本API编写的。在新版本的Gradio中，一些输入输出组件的调用方式已经得到简化。在旧版本中我们可能会使用gr.inputs.Audio来创建一个音频输入组件，用gr.outputs.Audio来创建一个音频输出组件，而在新版中，您只需使用gr.Audio即可。这种变化旨在使API更加直观和易于使用，另外inputs组件的参数可能也有所修改，在使用组件的时候需要根据实际情况传入参数。2.基础入门：第一步，掌握Gradio安装与配置环境要求：Gradio 需要Python 3.8 或更高版本的Python版本操作系统：Gradio可以在Windows、MacOS和Linux等主流操作系统上运行。安装步骤：使用pip安装，即打开你的终端或命令提示符，输入以下命令来安装Gradiopip install gradio检查安装：安装完成后，可以通过运行以下Python代码来检查Gradio是否正确安装import gradio as gr print(gr.__version__) # 4.15.0Gradio基础教程：讲解Gradio的基本概念和操作1. 初识GradioGradio简介：Gradio是一个开源的Python库，用于创建机器学习模型的交互式界面。它使得展示和测试模型变得简单快捷，无需深入了解复杂的前端技术。使用场景：Gradio广泛应用于数据科学、教育、研究和软件开发领域，尤其适合于快速原型设计、模型验证、演示和教学。2. 核心组件界面（Interface）：Gradio的核心是Interface类，它允许用户定义输入和输出类型，创建交互式的Web界面。输入类型：Gradio支持多种输入类型，如gr.Text用于文本输入，gr.Image用于图像上传，gr.Audio用于音频输入等。输出类型：输出类型与输入类型相对应，包括gr.Text、gr.Image和gr.Audio等，用于展示模型的输出结果。3. 基本操作创建界面import gradio as gr def greet(name): return "Hello " + name + "!" iface = gr.Interface(fn=greet, inputs=gr.Textbox(), outputs=gr.Textbox()) iface.launch()这段代码创建了一个简单的Gradio界面，用户可以输入名字，点击提交后界面会显示问候语。4. 交互流程处理和输出：上面的示例中，greet函数接收用户输入的名字，并返回问候语。Gradio自动处理这种输入输出流程，使得交互流畅自然。回调函数：在Gradio中，界面与Python函数（如greet）直接关联，这种函数被称为回调函数，负责处理输入数据并生成输出。5. 界面定制修改样式：Gradio界面可以通过参数定制，如增加title、description属性来提供界面的标题和描述：iface = gr.Interface( fn=greet, inputs=gr.Textbox(), outputs=gr.Textbox(), title="简单问候", description="输入你的名字，获得个性化问候。" )实用属性：Interface类提供了多种属性，如layout用于改变输入输出组件的布局，theme用于改变界面主题风格等。创建Gradio应用：创建一个简单的“Hello World”示例在这个例子中，我们将创建一个简单的Gradio应用，它接受用户的名字作为输入，并返回一个问候语。设置开发环境： 首先，确保你的Python环境中已经安装了Gradio。如果尚未安装，可以通过以下命令安装： pip install gradio编写Python脚本： 打开一个新的Python脚本文件，比如命名为gradio_hello_world.py。导入Gradio库： 在脚本的开始处导入Gradio库： import gradio as gr定义处理函数： 接下来，定义一个处理用户输入的函数。这个函数将接收一个字符串参数（用户的名字），并返回一个问候语。 def greet(name): return f"Hello {name}!"创建Gradio界面： 使用Gradio的Interface类来创建一个交互式界面。这个界面将有一个文本输入框和一个文本输出框。 iface = gr.Interface(fn=greet, inputs="text", outputs="text")运行应用： 最后，使用launch()方法启动你的应用。 iface.launch()尝试你的应用： 运行脚本后，你的默认Web浏览器会打开一个新的页面，显示你的Gradio应用。在文本框中输入你的名字，点击提交，你会看到问候语出现在下方。界面元素介绍：详解不同的输入输出组件\quad\quad Gradio提供了多种输入和输出组件，适应不同的数据类型和展示需求。了解这些组件对于设计有效的Gradio界面至关重要。输入组件 (Inputs)Audio：允许用户上传音频文件或直接录音。参数：source: 指定音频来源（如麦克风）、type: 指定返回类型。 示例：gr.Audio(source="microphone", type="filepath")Checkbox：提供复选框，用于布尔值输入。参数：label: 显示在复选框旁边的文本标签。 示例：gr.Checkbox(label="同意条款")CheckboxGroup：允许用户从一组选项中选择多个。参数：choices: 字符串数组，表示复选框的选项、label: 标签文本。示例：gr.CheckboxGroup(["选项1", "选项2", "选项3"], label="选择你的兴趣")ColorPicker：用于选择颜色，通常返回十六进制颜色代码。参数：default: 默认颜色值。示例：gr.ColorPicker(default="#ff0000")Dataframe：允许用户上传CSV文件或输入DataFrame。参数：headers: 列标题数组、row_count: 初始显示的行数。示例：gr.Dataframe(headers=["列1", "列2"], row_count=5)Dropdown：下拉菜单，用户可以从中选择一个选项。参数：choices: 字符串数组，表示下拉菜单的选项、label: 标签文本。示例：gr.Dropdown(["选项1", "选项2", "选项3"], label="选择一个选项")File：用于上传任意文件，支持多种文件格式。参数：file_count: 允许上传的文件数量，如"single"或"multiple"、type: 返回的数据类型，如"file"或"auto"。示例：gr.File(file_count="single", type="file")Image：用于上传图片，支持多种图像格式。参数：type图像类型，如pil。示例：gr.Image(type='pil')Number：数字输入框，适用于整数和浮点数。参数：default: 默认数字、label: 标签文本。示例：gr.Number(default=0, label="输入一个数字")Radio：单选按钮组，用户从中选择一个选项。参数：choices: 字符串数组，表示单选按钮的选项、label: 标签文本。示例：gr.Radio(["选项1", "选项2", "选项3"], label="选择一个选项")Slider：滑动条，用于选择一定范围内的数值。参数：minimum: 最小值、maximum: 最大值、step: 步长、label: 标签文本。示例：gr.Slider(minimum=0, maximum=10, step=1, label="调整数值")Textbox：单行文本输入框，适用于简短文本。参数：default: 默认文本、placeholder: 占位符文本。示例：gr.Textbox(default="默认文本", placeholder="输入文本")Textarea：多行文本输入区域，适合较长的文本输入。参数：lines: 显示行数、placeholder: 占位符文本。示例：gr.Textarea(lines=4, placeholder="输入长文本")Time：用于输入时间。参数：label: 标签文本。示例：gr.Time(label="选择时间")Video：视频上传组件，支持多种视频格式。参数：label: 标签文本。示例：gr.Video(label="上传视频")Data：用于上传二进制数据，例如图像或音频的原始字节。参数：type: 数据类型，如"auto"自动推断。示例：gr.Data(type="auto", label="上传数据")输出组件 (Outputs)Audio：播放音频文件。参数：type 指定输出格式。示例：gr.Audio(type="auto")Carousel：以轮播方式展示多个输出，适用于图像集或多个数据点。参数：item_type 设置轮播项目类型。示例：gr.Carousel(item_type="image")Dataframe：展示Pandas DataFrame，适用于表格数据。参数：type 指定返回的DataFrame类型。示例：gr.Dataframe(type="pandas")Gallery：以画廊形式展示一系列图像。HTML：展示HTML内容，适用于富文本或网页布局。Image：展示图像。参数：type 指定图像格式。 示例：gr.Image(type="pil")JSON：以JSON格式展示数据，便于查看结构化数据。KeyValues：以键值对形式展示数据。Label：展示文本标签，适用于简单的文本输出。Markdown：支持Markdown格式的文本展示。Plot：展示图表，如matplotlib生成的图表。Text：用于显示文本，适合较长的输出。Video：播放视频文件。示例应用让我们以一个简单的应用为例，演示如何使用文本输入和标签输出：import gradio as gr def greet(name): return f"Hello, {name}!" iface = gr.Interface( fn=greet, inputs=gr.Textbox(label="Your Name"), outputs=gr.Label() ) iface.launch()在这个示例中，用户可以在文本框中输入名字，点击提交后，应用将在标签中显示问候语。3. 中级应用：提升你的Gradio技能多样化输入输出处理\quad\quad 在Gradio中，有效地处理多种输入（Inputs）和输出（Outputs）类型是提升用户交互体验的关键。不同类型的组件可以帮助用户更直观地与模型进行交互，并获得清晰的反馈。 处理不同类型的输入1、组合不同输入类型： 在Gradio中，你可以在同一个界面上结合使用多种输入类型。 * 示例：结合文本框（Textbox）和图片上传（Image）输入，用于同时接收用户的文本描述和相关图片。 2、输入类型的选择：根据你的模型需求选择合适的输入类型。例如，如果你的模型进行图像分类，那么应选择gr.Image()；如果是文本生成模型，则应使用gr.Textbox()。 3、自定义输入设置：利用输入组件的参数自定义用户的输入体验。例如，为gr.Slider()设置最大值和最小值，或为gr.Dropdown()提供一个选项列表。处理不同类型的输出展示多样化的输出：Gradio允许你以多种方式展示模型的输出。示例：对于图像处理模型，使用gr.Image()来展示处理后的图像；对于文本分析，使用gr.Text()来展示分析结果。 输出类型的选择：根据模型的输出选择合适的输出类型。 如果模型输出为结构化数据，可以考虑使用gr.Dataframe()；对于音频处理模型，使用gr.Audio()。 增强输出可视化：使用合适的输出类型增强模型输出的可视化效果。例如，使用gr.Gallery()来展示一系列生成的图像，或使用gr.Plot()来展示数据图表。实例应用让我们通过一个实际的例子来演示如何处理多样化的输入输出：import gradio as gr def process_data(text, image): # 假设这里有数据处理逻辑 processed_text = text.upper() return processed_text, image iface = gr.Interface( fn=process_data, inputs=[gr.Textbox(label="输入文本"), gr.Image(label="上传图片")], outputs=[gr.Text(label="处理后的文本"), gr.Image(label="原始图片")] ) iface.launch()在这个示例中，我们创建了一个Gradio应用，它接收文本和图片作为输入，并返回处理后的文本和原始图片作为输出。这样的应用展示了如何有效地结合和处理不同类型的输入输出。界面定制\quad\quad 在Gradio中，界面的定制化是提升用户体验的关键。你可以调整布局、样式和界面元素的显示方式，使其更符合特定需求和审美。自定义布局组合布局：在Gradio中，你可以灵活地组合不同的输入和输出组件。示例：创建一个界面，其中包括文本输入、图片上传和按钮，以实现不同功能的模块化布局。 调整元素排列：利用布局参数调整元素的排列方式。示例：使用layout="grouped"或layout="stacked"来更改组件的排列方式，使界面更加紧凑或分散。定制样式更改界面风格：使用CSS样式来定制界面的外观。示例：添加CSS代码来更改按钮的颜色、字体的大小或元素的边距。 使用主题：Gradio提供了内置的主题选项，可用于快速更改界面风格。示例：使用theme="dark"或theme="huggingface"来应用暗色主题或Hugging Face风格。响应式设计适配不同屏幕大小：确保你的Gradio界面在不同设备上均有良好的显示效果。示例：测试在手机、平板和电脑上的显示情况，调整布局以适应不同屏幕。 界面元素的适配性：调整输入输出组件的大小和排列，使其适应不同的显示环境。示例：为小屏幕减少边距和间距，或在大屏幕上增加额外的空间。实例应用让我们通过一个具体的例子来展示如何定制一个Gradio界面：import gradio as gr def process_data(text): return text.upper() css = ".input_text { color: blue; } .output_text { font-weight: bold; }" iface = gr.Interface( fn=process_data, inputs=gr.Textbox(lines=2, placeholder="输入文本"), outputs="text", css=css, theme="dark" ) iface.launch()在这个示例中，我们创建了一个简单的文本处理应用，应用了暗色主题，并通过CSS改变了输入输出文本的颜色和样式。(貌似没有起作用，原因待排查！)与预训练模型相结合\quad\quad 在Gradio中，结合预训练模型可以让你快速创建强大的交互式界面，展示模型的能力。以下是如何实现这一目标的步骤和建议。选择合适的预训练模型模型源：选择适合你需求的预训练模型。常见的来源包括TensorFlow Hub、PyTorch Hub或Hugging Face模型库。模型类型：确定你的应用场景，如图像识别、文本生成或语音处理，然后选择相应类型的模型。集成模型到Gradio应用加载模型：根据所选模型的文档加载模型。这通常涉及导入相应的库并加载预训练权重。创建处理函数：编写一个处理函数，该函数接收Gradio输入，并使用模型进行预测或处理，然后返回输出。构建Gradio界面：根据模型的输入输出类型，选择合适的Gradio输入输出组件。然后将处理函数绑定到Gradio界面。示例：集成图像分类模型假设我们使用Hugging Face的预训练图像分类模型。以下是一个简单的示例：import gradio as gr from transformers import pipeline # 加载预训练模型 model = pipeline('image-classification') # 定义处理函数 def classify_image(img): return {i['label']: i['score'] for i in model(img)} # 创建Gradio界面 iface = gr.Interface( fn=classify_image, inputs=gr.Image(type="pil"), outputs=gr.Label(num_top_classes=5)) iface.launch()在这个例子中，我们使用了一个预训练的图像分类模型来识别上传的图片，并显示前五个最可能的类别。动态界面与实时反馈\quad\quad 在Gradio应用中，实现动态界面和实时反馈可以极大地提高用户的交互体验。以下是如何实现这些功能的步骤和建议。实现动态界面条件显示组件：使用Gradio的内置功能来根据用户输入动态显示或隐藏某些组件。示例：根据用户选择的选项，显示不同的输入字段。 界面元素更新：根据用户的交互实时更新界面元素。示例：用户上传图片后，立即在界面上显示预览。提供实时反馈即时处理与展示结果：设计应用逻辑，使其能够快速响应用户输入并展示结果。示例：用户输入文本后，立即显示文本分析结果。 使用状态管理：利用状态管理来保存和更新用户交互的状态。示例：记录用户的选择或输入，以便在整个会话中使用。示例：实现图片处理应用的动态界面假设我们正在构建一个图片处理应用，以下是如何实现动态界面和实时反馈的示例：import gradio as gr def process_image(img, filter_type): if filter_type == "Black and White": img = img.convert("L") return img iface = gr.Interface( fn=process_image, inputs=[gr.Image(type="pil"), gr.Radio(["None", "Black and White"])], outputs="image" ) iface.launch()在这个例子中，用户上传图片并选择滤镜类型后，应用会立即处理并显示处理后的图片。这个过程实现了动态交互和实时反馈。高级特性和组件\quad\quad 在Gradio的中级应用部分，引入高级特性和组件是提升应用交互性和功能性的关键。以下是Gradio提供的一些高级特性和组件及其应用方式。热加载支持快速迭代开发：利用热加载特性，使得在开发过程中对代码所做的更改能够即时反映在应用界面上，无需重启应用。这对于调试和快速迭代开发非常有帮助。Jupyter Notebook集成Notebook内交互：Gradio应用可以直接嵌入Jupyter Notebook中，便于在数据科学和机器学习的探索性分析中直接展示和交互。用户可以在Notebook环境中实时演示和测试Gradio应用。共享和展示URL共享：Gradio允许用户通过生成的URL共享他们的应用，这使得在不同设备和环境中的演示和测试变得简单。用户可以通过设置share=True来获取可以公开访问的URL。自定义组件个性化界面设计：用户可以根据特定需求创建自定义的输入输出组件，包括利用HTML、CSS和JavaScript进行定制。这使得应用界面可以高度个性化，更好地满足特定用途。4. 高级功能：成为Gradio专家高级界面和布局使用ChatInterface聊天界面：ChatInterface允许创建类似聊天应用的界面，适用于构建交云式聊天机器人或其他基于文本的交互式应用。import gradio as gr def slow_echo(message, history): for i in range(len(message)): time.sleep(0.05) yield "机器人回复: " + message[: i+1] demo = gr.ChatInterface(slow_echo).queue() if __name__ == "__main__": demo.launch()使用ChatInterface创建聊天界面，构建一个简单的聊天机器人界面，接收用户输入，并回复相应的文本信息。使用TabbedInterface标签界面：TabbedInterface允许在一个应用中创建多个标签页，每个标签页可以包含不同的界面和功能。import gradio as gr def function1(input1): return f"处理结果: {input1}" def function2(input2): return f"分析结果: {input2}" iface1 = gr.Interface(function1, "text", "text") iface2 = gr.Interface(function2, "text", "text") tabbed_interface = gr.TabbedInterface([iface1, iface2], ["界面1", "界面2"]) tabbed_interface.launch()这里展示了如何使用TabbedInterface来创建包含多个标签的界面。界面1效果这里展示了如何使用TabbedInterface来创建包含多个标签的界面。界面2效果使用Blocks进行自定义布局Blocks布局：Blocks是Gradio中用于自定义布局的一种强大工具，允许用户以更灵活的方式组织界面元素。布局组件：Row, Column, Tab, Group, AccordionRow和Column：分别用于创建水平行和垂直列的布局。示例：使用Row来水平排列几个按钮，使用Column来垂直排列一系列输入组件。 Tab：用于在TabbedInterface中创建各个标签页。示例：在一个应用中创建多个Tab，每个标签页包含特定主题的内容。 Group：将多个组件组合成一个组，便于统一管理和布局。示例：创建一个包含多个相关输入组件的Group。 Accordion：创建可以展开和折叠的面板，用于管理空间和改善界面的可用性。示例：将不常用的选项放入Accordion中，以减少界面的拥挤。# 使用Blocks及Row、Column实现自定义布局 import gradio as gr with gr.Blocks() as demo: with gr.Row(): with gr.Column(): input_text = gr.Textbox(label="输入") submit_button = gr.Button("提交") with gr.Column(): output_text = gr.Label(label="输出") submit_button.click(fn=lambda x: f"你输入了: {x}", inputs=input_text, outputs=output_text) demo.launch() 这个示例中使用Blocks及Row、Column实现自定义布局。# 使用Group和Accordion组织组件 import gradio as gr with gr.Blocks() as demo: with gr.Group(): input1 = gr.Textbox() input2 = gr.Slider() with gr.Accordion("详细设置"): checkbox = gr.Checkbox(label="选项") dropdown = gr.Dropdown(choices=["选项1", "选项2"]) submit_button = gr.Button("提交") output_label = gr.Label() submit_button.click(fn=lambda x, y, z: f"{x}, {y}, {z}", inputs=[input1, input2, checkbox], outputs=output_label) demo.launch() 在这个示例中，我们使用Group将一些组件组合在一起，并使用Accordion创建了一个可折叠的面板，用于包含更详细的设置选项。构建复杂界面\quad\quad 在Gradio中构建复杂界面意味着整合多种输入输出组件、利用高级布局技巧，以及实现复杂的交互逻辑。这些技巧对于创建高级的机器学习和数据科学应用至关重要。组合多种组件多输入多输出界面：结合多种输入和输出组件，以处理复杂的数据类型和格式。示例：创建一个界面，包含文本输入、图像上传、滑动条等多种输入类型，并同时展示文本、图表、图片等多种输出。 复杂的数据处理：设计能够处理多种输入并产生多种输出的复杂函数。示例：一个接收文本和图像作为输入，同时输出文本分析结果和图像处理结果的应用。高级布局技巧自定义布局：使用CSS和HTML自定义界面布局，以适应特定的视觉设计和用户体验需求。示例：利用CSS调整组件的大小、间距和颜色，或使用HTML布局元素。 响应式设计：确保界面在不同设备和屏幕尺寸上保持良好的可用性和视觉效果。示例：调整布局以适应手机和平板屏幕，确保元素在小屏幕上也易于操作。复杂交互实现状态管理和动态更新：利用Gradio的状态管理功能来保存用户交互的状态，并根据状态动态更新界面。示例：根据用户先前的选择或输入来调整后续展示的选项和结果。 集成外部资源和API：将Gradio界面与外部资源或API集成，以实现更复杂的功能。示例：集成外部数据源，如数据库或API，以实时获取和展示数据。示例：创建一个综合数据分析应用让我们通过一个实例来展示如何构建复杂界面：import gradio as gr def complex_analysis(text, image, threshold): text = "我的回复:" + text return text, ~image + threshold iface = gr.Interface( fn=complex_analysis, inputs=[ gr.Textbox(lines=2, placeholder="输入文本"), gr.Image(type="numpy"), gr.Slider(minimum=0, maximum=100) ], outputs=[ "text", "image" ] ) iface.launch() 在这个例子中，我们创建了一个具有文本输入、图像上传和滑动条的复杂界面，用于综合分析文本和图像，并展示处理后的结果。高级交互组件运用\quad\quad 在Gradio中，高级交互组件的运用可以让你的应用更加动态和互动性强。这些高级特性允许你创建更为复杂和有趣的用户界面，从而提高用户参与度。动态控制元素动态显示/隐藏组件：根据用户的操作或输入动态地显示或隐藏某些界面元素。示例：根据用户在一个下拉菜单中的选择，显示不同的输入框。 更新组件选项：实时更新组件的选项，如下拉菜单或单选按钮的选项。示例：根据用户在一个输入框中的文本，更新下拉菜单的选项。高级输入输出处理复杂输入数据处理：处理多维数据或多格式数据输入。示例：创建一个应用，用户可以上传CSV文件，并选择一系列处理选项。 多步骤输出展示：分步骤展示处理结果，提供更详细的信息和解释。示例：在数据分析应用中，先展示初步结果，然后提供进一步的详细分析。交互式图表和可视化集成交互式图表：使用诸如Plotly之类的库，创建交互式的图表和数据可视化。示例：展示一个交互式的数据散点图，用户可以通过滑动条调整参数。 自定义可视化组件：利用HTML和JavaScript创建自定义的交互式可视化组件。示例：创建一个自定义的数据地图，展示地理位置相关的数据。实时反馈和动态更新实时数据反馈：设计应用逻辑，以便在用户进行输入时即时展示反馈。示例：在用户输入文本的同时，显示文本的实时情感分析结果。 动态内容更新：根据用户的操作或其他外部事件动态更新内容。示例：创建一个新闻聚合应用，根据用户的兴趣动态更新新闻列表。示例：创建一个动态数据探索工具import gradio as gr import pandas as pd import plotly.express as px def explore_data(dataset, columns): df = pd.read_csv(dataset) fig = px.scatter(df, x=columns[0], y=columns[1]) return fig demo = gr.Interface( fn=explore_data, inputs=[ gr.File(label="上传CSV文件"), gr.CheckboxGroup(choices=['Column1', 'Column2', 'Column3'], label="选择列") ], outputs=gr.Plot() ) demo.launch()在这个示例中，用户可以上传CSV文件并选择要探索的数据列，应用将展示这些列的交互式散点图。状态管理技巧\quad\quad 在Gradio中利用状态管理技巧，可以有效地保存和更新用户在界面上的交互状态。这对于创建交互式的数据分析工具、多步骤表单或任何需要记住用户之前操作的应用尤为重要。理解状态管理什么是状态管理：状态管理指的是在应用的生命周期内跟踪和更新用户界面的状态，如用户的输入、选择或界面的显示状态。状态的重要性：在复杂的交互过程中，状态管理允许应用“记住”用户之前的操作，这对于提供个性化体验和维护数据一致性至关重要。应用状态管理初始化状态：使用Gradio的State组件来初始化状态。示例：初始化一个状态来跟踪用户是否点击了某个按钮。 更新状态：根据用户的交互来更新状态。示例：当用户提交表单时，更新状态来反映新的用户输入。 利用状态进行逻辑控制：根据当前状态来控制应用的逻辑和界面展示。示例：如果用户选择了特定选项，显示额外的输入字段。状态管理实践实现多步骤界面：利用状态管理来创建多步骤的用户界面，如分步表单或多阶段数据输入过程。示例：在用户完成第一步输入后，显示第二步的相关选项。 保存用户会话数据：在用户与应用交互期间，保存用户的选择和输入，以便在后续步骤中使用。示例：保存用户在一个查询界面中的搜索历史，以便于后续快速访问。示例：创建具有状态管理的数据分析应用import gradio as gr def update_output(input_text, state_counter): state_counter = state_counter or 0 return f"您输入了：{input_text}", state_counter + 1, state_counter + 1 iface = gr.Interface( fn=update_output, inputs=[gr.Textbox(), gr.State()], outputs=[gr.Textbox(), gr.Label(), gr.State()] ) iface.launch()在这个例子中，应用通过State组件跟踪用户输入次数，并在每次输入后更新计数器。性能优化秘籍\quad\quad 在Gradio中，性能优化是确保应用能够高效处理大量数据和复杂计算的关键。以下是一些优化Gradio应用性能的策略和技巧。优化数据处理高效的数据加载：对于需要加载大量数据的应用，考虑使用高效的数据存储和读取方法，如使用Pandas的高效文件读取函数。示例：使用pandas.read_csv()时，指定usecols参数只加载需要的列。 数据预处理：在应用启动之前进行数据预处理，以减少实时处理的负担。示例：在应用启动前，对数据进行清洗、筛选和预计算。优化模型使用模型加载策略：如果使用机器学习模型，考虑在应用启动时预加载模型，避免每次请求时重新加载。示例：在脚本的全局作用域中加载模型，而不是在处理函数内部。 批处理和缓存：对于重复的请求，使用缓存来存储和复用结果，减少不必要的计算。示例：使用缓存装饰器或字典来存储已处理的请求。界面优化简化界面元素：避免在界面上使用过多复杂的组件，这可能导致加载时间增加和性能下降。示例：仅保留核心的输入输出组件，移除不必要的装饰和复杂布局。 异步操作：对于耗时的操作，考虑使用异步处理，以防止界面卡顿。示例：在处理函数中使用异步库或线程处理耗时任务。性能监控监控应用性能：使用性能监控工具来跟踪应用的响应时间和资源消耗。示例：使用Python的time或cProfile模块来监控函数执行时间。 响应时间优化：根据性能数据优化慢速的操作或瓶颈。示例：优化数据处理流程，或替换效率低下的算法。示例：性能优化的数据分析应用import gradio as gr import pandas as pd import time # 预处理数据 data = pd.read_csv("large_dataset.csv").preprocess() def analyze_data(filter_option): start_time = time.time() filtered_data = data[data["column"] == filter_option] analysis = filtered_data.compute_statistics() execution_time = time.time() - start_time return analysis, f"处理时间: {execution_time:.2f}秒" iface = gr.Interface( fn=analyze_data, inputs=gr.Dropdown(["选项1", "选项2", "选项3"]), outputs=["text", "text"] ) iface.launch()在这个示例中，我们对数据进行了预处理，使用了时间监控，并在处理函数中实现了快速的数据筛选和统计。5. 企业级应用：Gradio在商业环境中的部署多种部署方式\quad\quad 在企业级应用中，选择合适的部署方式对于确保应用的稳定性、可扩展性和安全性至关重要。Gradio应用可以通过多种方式部署，包括本地部署、云服务和容器化。本地部署服务器部署：将Gradio应用部署到内部服务器，适合对数据隐私和安全性有严格要求的场景。示例：在公司的内部服务器上设置Gradio应用，确保数据不会离开内部网络。 性能考虑：评估服务器的性能，确保足够处理应用的负载。示例：选择有足够CPU和内存的服务器，以支持大规模用户访问。云服务部署利用云平台：在云平台（如AWS、Azure、Google Cloud）上部署Gradio应用，享受可扩展性和灵活性。示例：在AWS EC2实例上部署应用，利用云平台的扩展能力来处理不同的负载需求。 自动伸缩和负载均衡：利用云平台的自动伸缩和负载均衡特性，以应对流量波动。示例：设置自动伸缩策略，根据访问量自动增减实例数量。容器化和编排Docker容器：使用Docker容器化Gradio应用，提高部署的灵活性和一致性。示例：创建Docker镜像，并在不同环境中部署相同的镜像。 Kubernetes编排：对于需要高可用性和复杂编排的场景，使用Kubernetes进行容器编排。示例：在Kubernetes集群中部署Gradio应用，实现服务的自我修复和水平扩展。安全性和监控安全性考虑：确保应用的安全性，特别是在处理敏感数据时。示例：实施SSL加密，设置防火墙和访问控制。 性能监控：监控应用的性能，确保稳定运行。示例：使用Prometheus和Grafana等工具监控应用性能和资源使用情况。Docker化部署\quad\quad Docker容器化技术使得应用部署变得更加简单和一致，特别适合于企业级的生产环境。以下是利用Docker部署Gradio应用的步骤和建议。创建Docker镜像编写Dockerfile：创建一个Dockerfile来定义如何构建你的Gradio应用的Docker镜像。示例：在Dockerfile中指定基础镜像，复制应用代码，并安装所需的依赖。DockerfileCopy code FROM python:3.8 COPY . /app WORKDIR /app RUN pip install gradio CMD ["python", "your_app.py"] 构建镜像：使用docker build命令来构建镜像。示例：docker build -t gradio-app .部署Docker容器运行容器：使用docker run命令来在本地或服务器上运行你的Gradio应用容器。示例：docker run -p 7860:7860 gradio-app * 端口映射：确保在运行容器时正确映射Gradio应用的端口（默认为7860）。 示例：上面的命令将容器的7860端口映射到宿主机的7860端口。容器编排和管理Docker Compose：如果应用有多个服务（如数据库、后端服务），考虑使用Docker Compose进行管理。示例：创建docker-compose.yml文件来定义和运行多服务应用。 Kubernetes集群部署：对于需要高可用性和大规模部署的应用，考虑使用Kubernetes进行容器编排。示例：编写Kubernetes部署配置来管理Gradio应用的容器。安全性和监控安全最佳实践：确保遵循Docker安全最佳实践，如使用非root用户运行容器。示例：在Dockerfile中创建并使用新用户。 容器监控：使用工具监控容器的健康状况和性能。示例：使用Prometheus和Grafana监控Docker容器。示例：Docker化部署Gradio应用bashCopy code # 创建Dockerfile # Dockerfile内容如上所示 # 构建Docker镜像 docker build -t gradio-app . # 运行Docker容器 docker run -p 7860:7860 gradio-app\quad\quad 在这个示例中，我们首先创建并构建了Gradio应用的Docker镜像，然后在本地运行了该容器，实现了应用的Docker化部署。安全与隐私保护加强网络安全使用HTTPS：通过HTTPS部署Gradio应用来确保数据传输过程中的加密和安全。示例：使用SSL/TLS证书实现HTTPS，保护数据不被窃听。 防火墙和访问控制：设置防火墙规则，限制对Gradio应用的访问。示例：只允许来自特定IP地址或网络的访问请求。数据保护和隐私数据加密：在存储和传输过程中对敏感数据进行加密。示例：使用AES或RSA算法加密存储在服务器上的数据。 遵守数据隐私法规：确保应用符合GDPR、HIPAA等数据保护法规的要求。示例：实施数据处理和存储的合规性措施。身份验证和授权身份验证机制：为Gradio应用实现强大的身份验证系统。示例：集成OAuth2.0或OpenID Connect进行用户认证。 角色基础的访问控制：根据用户角色设置不同的访问权限。示例：为不同的用户角色定义不同的界面和功能访问权限。日志记录和监控审计日志：记录用户活动和系统事件，以便于事后审计和问题追踪。示例：记录用户登录、数据查询和修改等关键操作。 实时监控和警报：实现实时监控系统，及时发现和响应潜在的安全威胁。示例：使用SIEM系统监控异常活动，并设置自动警报。实现高可用性和扩展性高可用性（High Availability）冗余部署：在多个服务器或数据中心部署应用的副本，以防单点故障。示例：在不同地理位置的数据中心部署Gradio应用的副本。 负载均衡：使用负载均衡器分配流量，以避免单个服务器的过载。示例：配置Nginx或AWS ELB作为负载均衡器来分配请求。 故障切换和恢复：实现自动故障切换和快速恢复机制。示例：使用自动化脚本或服务如Kubernetes的自我修复能力来处理故障。扩展性（Scalability）水平扩展：设计应用以支持通过增加更多服务器来扩展（而不是仅仅升级现有服务器的硬件）。示例：在容器编排平台如Kubernetes上运行Gradio应用，实现容易的水平扩展。 自动伸缩：实现基于负载的自动伸缩，以适应流量波动。示例：使用AWS Auto Scaling或Kubernetes HPA（Horizontal Pod Autoscaler）自动调整实例数量。 无状态设计：尽可能使应用无状态，以简化扩展过程。示例：避免在单个实例中存储状态，使用中央数据库或缓存来存储会话和状态数据。性能优化优化资源利用：确保应用高效使用资源，避免不必要的资源浪费。示例：优化代码和数据库查询，减少CPU和内存的使用。 缓存策略：利用缓存来减少重复的计算和数据库访问。示例：使用Redis或Memcached来缓存常见查询结果或计算密集型操作的结果。监控和日志实时监控系统：实现实时监控，及时了解应用的健康状况和性能指标。示例：使用Prometheus和Grafana监控应用和基础设施的性能。 详细日志记录：记录详细的应用和系统日志，以便于故障排查和性能分析。示例：使用ELK堆栈（Elasticsearch, Logstash, Kibana）来收集和分析日志。示例：在Kubernetes上部署高可用性Gradio应用# Kubernetes部署配置示例 apiVersion: apps/v1 kind: Deployment metadata: name: gradio-app spec: replicas: 3 # 多副本以实现高可用性 selector: matchLabels: app: gradio-app template: metadata: labels: app: gradio-app spec: containers: - name: gradio-app image: gradio-app:latest ports: - containerPort: 7860 --- apiVersion: v1 kind: Service metadata: name: gradio-app-service spec: selector: app: gradio-app ports: - protocol: TCP port: 80 targetPort: 7860 type: LoadBalancer\quad\quad 在这个示例中，我们通过Kubernetes部署了具有多个副本的Gradio应用，确保了应用的高可用性和容易的水平扩展。6. 实战案例案例1：数据可视化探索工具场景描述：创建一个数据可视化工具，用户可以上传数据集，选择不同的图表类型进行数据探索。功能实现：使用File组件上传数据文件。利用Dropdown组件让用户选择图表类型，如柱状图、折线图等。使用Plot组件展示生成的图表。代码示例：import gradio as gr import pandas as pd import seaborn as sns import matplotlib.pyplot as plt def plot_data(file, chart_type): df = pd.read_csv(file) if chart_type == "柱状图": plt.figure(figsize=(10, 6)) sns.barplot(data=df) elif chart_type == "折线图": plt.figure(figsize=(10, 6)) sns.lineplot(data=df) plt.tight_layout() return plt iface = gr.Interface( plot_data, inputs=[gr.File(), gr.Dropdown(["柱状图", "折线图"])], outputs="plot" ) iface.launch() 创建一个数据可视化工具，用户可以上传数据集，选择不同的图表类型进行数据探索。案例2：多功能聊天机器人界面场景描述：构建一个具有多个功能的聊天机器人，如天气查询、新闻更新等。功能实现：使用Chatbot组件作为主要交互界面。结合API调用来提供不同的服务。代码示例：import gradio as gr import time def chatbot_response(message, history): if "天气" in message: # 假设的天气API调用 text = "今天的天气是晴朗。" elif "新闻" in message: # 假设的新闻API调用 text = "最新新闻：..." else: text = "对不起，我不理解你的问题。" for i in range(len(text)): time.sleep(0.1) yield "机器人回复: " + text[: i+1] demo = gr.ChatInterface(chatbot_response).queue() if __name__ == "__main__": demo.launch()构建一个具有多个功能的聊天机器人，如天气查询、新闻更新等。
第一章 肖恩和凯特尔骤雨初歇，阳光和煦地照在淅沥滴落水珠的雨篷上，炊烟从火堆上升起，团团笼罩四周。人们站在炊烟里呼唤同伴，修补屋顶，晾晒皮毛，准备食材和烹饪。从这座牧场的高处看去，人们身边是排布得不算有秩序的工棚，凌乱、简陋，充满生活气息。马厩棚顶下露出几只抖动的耳朵和不断甩尾巴的马屁股，长角牛三三两两地穿行在不远处的三角叶杨树丛中，悠闲地啃着草皮。一道身影缓提着缰绳，让马停立在牧场栅门所处的缓坡下，他披着长长的黑袍，下摆几乎垂到地面，黑袍中心绘着数枚白色的符号，这些符号既像图案，又像文字，意味难明，仿佛神秘禁忌的咒语。来人撩起黑袍，动作利落地跳下马，缓步走进牧场。整个过程中不发一言，脸庞都藏在一顶奇怪的帽子下面，看不清楚面容。再见多识广的人也没遇到过这种形式的帽子，它有着圆柱形的顶，看起来像军帽，但材质十分沉重，上面装饰着数量繁多，色彩缤纷的羽毛，遮住了本来的颜色。黑袍人边走边摇着手中的木柄铃铛，伴随着每声脆响，都有羽毛掉落，洒在他行走的路径上。黑袍人走到工棚区周围，先是默默站立了一会儿，直到牧场中忙碌的人们因为不速之客的闯入，纷纷把目光投了过来，才清了清嗓子，抚胸致了一礼后说道：“『一生惑幻，临殁见真。』苍生无知，我为拯救而来。”他的嗓音低沉而柔和，听不出年纪。“你是谁？”一个穿着背带帆布裤，身材矮壮，正拿着阔边帽把呛人炊烟扇离自己的人问道。他叫格雷罗，是牧场的一位牛仔，因为年纪大又勇敢，在牧场里有一些威望。他见陌生人不像寻衅，也就暂时没有拿枪的意图。“失礼了，我是流浪驱魔人歌德，世间一切魔魅的死敌，我远远看见有魔气徘徊在这里的上空，所以冒昧打扰。”“魔魅是什么意思？”牧民里有见识少的。“冒昧是什么意思？”还有没受过教育的。格雷罗咳了一声，觉得自己有必要说点什么。“不是我不相信你的话，但这个牧场一直很平静，既没有牛羊生病，也没有人死去，恐怕你搞错了。再说，恶魔光顾我们这些贫穷的人们，又有什么好处呢？”“死神降临无助贫民茅屋，同样光顾煊赫帝王宫殿。”来人还是不紧不慢的，说着晦涩难懂的话。“我并不要求你们的报酬，只要作法让它现形再予以驱散，就自行离开。像我这样的超凡者，世人的金钱犒赏并无意义。”超凡者！人们交口接耳起来，超凡者可不是一个简单的称呼，它代表着凡人无法企及的力量，牧场中年纪小一点的，甚至还没有见过超凡者的模样。就连胆大的格雷罗也不禁咽着口水，他不怕恶人，但对神秘力量还是很敬畏。“好……好吧，您是做完法就走吗？我们也没有什么可招待的，我们……”黑袍人歌德摆手制止了他，在原地盘膝坐下，摇了三下铃铛，在胸前轻点了三下，这才仰头，露出奇怪帽子下的脸庞。牧民们纷纷惊叹出声，这位超凡者的脸上红白油彩交杂，不同的颜色扭曲纠缠，形成奥妙难明的符号，像是戴了一张面具。这场景略有些恐怖，但大家也明白，既然是超凡者，自然有些怪异之处的，忐忑的心情不由笃定几分。歌德向虚空望了一眼，又突地指向一处工棚，高声吟唱道：“比黄昏还要昏暗的东西，比血液还要鲜红的东西，在时间之流中出现吧！”砰地一声！虚掩着的木门被撞开，一道赤红的身影蹿了出来，在阳光的照耀下蒸腾出浓郁的红雾，口中发出吱呀乱叫的声音，手中寒光泛起，抓向歌德。黑袍人不为所动，将手向下一指，又喊道：“『沉眠于大地深处而存在,拥有冰冻灵魂的霸王,请赋予我冻结！』”赤红身影蓦地迟缓，挥击的速度也变慢了许多。黑袍人猛地掷出铃铛撞向赤红身影的面门，声音嘶哑低沉：“『在您的伟大的名下，我在这黑暗中起誓，赐与你平等的毁灭！』”铜铃一声脆响，正中面门，红影仿佛痉挛一般向后仰倒，双手乱抓，一蓬蓬带着圣洁气息的金色烟雾和红雾在空中缠杂飞舞，不分彼此。歌德用斗篷掩住面部，悄悄咳嗽了一声。这几下兔起鹘落，众人完全没有反应过来，呆滞地站在一旁，直到红影倒在地上彻底不动，才有几个大婶捂着心口，惊魂未定地喘息起来。就像开启了什么开关，众人议论纷纷，语气中带着惊叹和畏惧，没人敢上前与歌德对话，或验看“恶魔”。歌德显然十分满意众人的反应，仪态优雅地站起身来，微笑说道：“羔羊们……”嘭！一道震彻耳边的巨响打断了话语，把刚站起身的歌德吓了一个趔趄，地上刚才还一动不动的红影骨碌一下站了起来，警惕地看向四周。只见牧场的厨师，身高足足两米一十的沙奎老爹端着霰弹枪大步向这里走来，向天的枪口还袅袅散着白烟，被枪声吓得惊骇欲绝的母鸡们争先恐后地从他身边逃开。“凯特尔！肖恩！我就知道是你们两个混球小子！你头上是不是从我这偷走的铁锅？还有我那些原本很漂亮的大公鸡的毛？啊，我的辣椒面！”不小心吸入空中红雾的老爹边咳嗽边抡着大手来抓两人，“歌德”和“红影”见势不妙，纷纷把身上的黑袍和红布甩开，兔子一样蹦来跳去，躲开老爹愤怒的追捕，一边互相推卸：“都是肖恩的主意！”“放屁！你演驱魔人不是很开心？”“松明粉和辣椒又不是我偷的！”“我就说松明粉很好用吧？你要是把劳拉大婶做衣服的亮片偷出来，效果就更好了。”“那倒是……喂不要跑那么快啊！”一旁呆立的众人也纷纷反应过来，大婶们怒吼出声：“小兔崽子，那是我的窗帘！怪不得眼熟……”“还想偷我的亮片，上次的内裤是不是你们偷的，给我站住！”“谁会偷你内衣啊，那是格雷罗上次喝醉了……”“臭小子你敢污蔑我，看我打不死你！”一派笑闹声贯穿了牧场的早晨，马厩中的马儿们不明所以地抬头看看，又仿佛觉得人类太无聊了似的，打了几声响鼻，晃晃头颅。……好容易吃过午饭，凯特尔捂着肿得老高的脸颊，抗着马鞍出来时，肖恩已经收拾好马匹，在和一个修葺屋顶的牛仔聊天。肖恩今年十九岁，身材瘦高，黑发棕瞳，面部线条柔和，细长的眉毛，眼神明亮，脸上时刻带着的微笑和相对整洁的衣装，使他区别于绝大多数给人以粗鲁不文印象的牛仔，看起来腼腆而内向。但没有人凯特尔更清楚，这都是伪装，肖恩这小子肚子里的坏主意，比沙奎老爹给自己咖啡里加的糖还多。“笑什么笑，每次都是你提主意我挨打！”面对着看向自己窃笑的肖恩，凯特尔气不打一处来，自己的罗圈腿跑得慢，被大伙堵住打了一顿，那家伙倒是一溜烟跑掉，看大伙气消了才回来。凯特尔已经卸掉“驱魔人”伪装，恢复了十八岁年轻牛仔应有的装扮，脖子上系着方巾，腿上绑着皮套裤，马甲里塞着各式各样的小物件儿。他个子只比格雷罗高一些，瘦了不少，但力气不算弱，举着马鞍的手非常稳定。一边笑骂，凯特尔将自己的宝贝马鞍仔细地放置好，紧紧勒上双肚带，又把行李放在马鞍后面，轻巧地上了马。“快走吧，我们得出发巡边了。”肖恩应了一声，转身走向自己的马匹。“小心！”伴随惊叫，一道黑影从屋顶滑落，带起沉重的风声砸向肖恩的脑袋。肖恩头也没抬，左脚撑地，右膝迅速提起，整条腿以惊人的柔韧性高踢过头，在飞来的物事上轻轻一蹬。屋顶的牛仔尾音还没收，就看到自己不小心脱手的锤子划着半弧飞了回来，下意识接住，眼见着肖恩没当一回事，只是挥了挥手就继续往马匹的方向走去了。“就会耍帅……”凯特尔咕哝了几句，不过刚才那一幕也吓了他一跳，心里明白幸亏肖恩身手好，换了自己，最轻也是个头破血流的下场。“知道了,知道了。”莫雷亚也心有余悸地应道。他的眼眶红红的，倒不是因为惊吓或离别感伤，而是因为肖恩上午撒“红雾”（辣椒面）时他吸到不少，呛着了……不然也不会握不紧锤子。“混球们！”沙奎老爹的大嗓门传了过来，肥大的身躯一路小跑着到马前站定，平视骑在马上的肖恩——没办法，老爹的身高太恐怖了。“这是你们两个人的干粮，省着吃，调料用来烤野味，辣椒就别想了，都被你们挥霍光了。”皮肤黝黑的老爹把一个大包袱挂在肖恩的马上，压得马背一沉。“肖恩，给你这个。”一个大婶把手上的东西递过来。“这是什么？提亚大婶。”“我这有块毛毡，你们巡边时盖着吧，保暖用。”大婶抹着眼泪说——也是呛的。肖恩不禁红了脸，接过来连连道谢。不知不觉间，两人拍马踏着小碎步，已经到了牧场门口，向众人告别。“注意安全啊。”老爹替众人说。“放心吧，我准备得很充分。”肖恩微笑着，拍了拍背后的步枪。这只枪很新，还配着足够的子弹，看起来非常可靠。“如果不是有肖恩，谁放心凯特尔那混小子！”大家异口同声。“我才是在这里长大的啊，为什么你们都只关心肖恩！”凯特尔叫起屈来。又是一阵笑声。老爹一拍脑门：“差点忘了，喂，你们！都站好位。”“啊？老爹，不要了吧——”莫雷亚察觉到一丝不妙。“别废话，混球，难道你希望他们遭受厄运吗！”老爹令出如山，莫雷亚嘴里嘟哝着，还是不情愿地跟其它人一起围着两人的马匹站成一圈。“一——二——”随着老爹的口号，众人扭身摆手，节奏相同，但姿势各异地转了几圈，最后在同一时刻停住动作，或蹲或站，齐声大喊：“祝你美梦成真！”“噗——”不同于凯特尔的兴高采烈，肖恩实在没忍住，伏在马上笑得直不起腰。“严肃点儿肖恩，对我们『梦境』信徒来说，这是很庄重的祝福仪式。”沙奎的黑面皮上看不出一丝尴尬，板起脸端着老爹的架子。旁边的大婶们倒神色如常，早已司空见惯。肖恩知道大家是好意，但眼角瞄着一脸羞愤的葛雷亚，还是好容易才止住笑声，和凯特尔一起回了礼，向众人告别。马蹄声渐渐轻快，两人沿着小路迈向原野。“沙奎，怎么只派两个年轻人啊？巡边那么危险。”提亚大婶有点担心。“人手不够了。”沙奎撇着嘴，“霍根搞砸了那批皮货，得派人去交涉，这事只能格雷罗搞定，莫雷亚身手一般，当个随行。凯特尔从老雷蒙那学了不少东西，而肖恩虽然没来多久，但为人非常可靠。”“我有点担心，万一他们遇到那个……”大婶忧心忡忡。“这不是叮嘱过很多次了嘛。”老爹摆手，“只要躲起来就没事。”他仰头看天，又重复了一遍，“只要躲起来就没事。”……马匹沿路走着，肖恩稳稳骑在马上，没有看路，而是在摆弄他的步枪。这是一支新式“温彻斯特”，胡桃木的枪托，黑漆漆的枪身连着枪管，5发盒式弹仓扣在扳机前。扳机护圈则连着杠杆握把，只要一推一拉，机簧就会抛出弹壳，换上新弹。摆弄枪是为了熟悉手感。肖恩的身手固然不错，枪法却是新学的，事实上，就在几个月前，他还连枪都没有摸过。砰！抬枪，瞄准，扣动扳机，百米外一颗树枝应声而断，枪声沿着草原一路飘出去。凯特尔的马被枪声一震，蹄子滑落浅坑，把主人晃了个趔趄，惹得他嚷嚷：“你搞什么……”肖恩却心情不错地微笑起来，自觉准头还可以。牧场的路面——说是路，其实也不过是草原上一些绵延的小径而已。随着前辈们的开拓，这条小径延伸出几个分支，通向牧场的各个区域，外围的那一道围绕着牧场，仅在几处树丛和沼泽间有所中断，用铁丝篱笆充作牧场的边界标志。在骑马的人看来，这些马蹄踩出的单薄小径实在称不上平整，一到雨后就满是坑洼，很不好走。两个人在接下来的几天里，要沿着这条小径走马巡边。所谓走马巡边，按照凯特尔的说法，像“国王派狗腿子巡视他的领地”，起个宣示主权的作用。“沿着有营地的线路走，看看有没有需要帮助的牲畜，那个来了，就躲到帐篷里，没什么危险。”“凯特尔，这都是你养父教的？”凯特尔的养父是个叫雷蒙的老牛仔，在凯特尔14岁时去世，所有值钱的就只有一双靴子。“差不离，他在牧场巡边20年，也没出过岔子。对了，你昨天找松明粉怎么去了那么久？”泥路颠簸，凯特尔身体歪来倒去的，好在他腰腹力量不错，能够勉强应付，只是说话时声音因为被颠得一颤一颤的。“对~了~肖~恩~”——类似这样。相比之下，肖恩骑马的方式则非常奇怪：双脚掌搭在马镫上，腿部随着马匹的前进，或踩或挑，或勾或踢，类似抽搐一样毫不安分，显得犹为滑稽。然而随着马背的每一下颠簸，这些微小的动作都可以反向抵消马匹的震动，让体身体不受颠簸影响，前进得极为平稳，让凯特尔颇为羡慕。“老爹那儿没有啊，溜到隔壁牧场拿的。”“大芭达那儿？啧啧啧，那婆娘可放出豪言说要娶你的，你不仗义啊，享艳福去了吧？”“要去你去。”肖恩横了伙伴一眼，“上次她的牛仔抢了我们一只野兔，这次拿点东西算回礼。”“我巴不得去呢，真想不通你为什么拒绝她，你不会对印玛族姑娘有歧视吧？”“怎么可能，我自己不也是‘异类’？在我眼里各色人种可没有什么区别，人格平等。”“那普通人和超凡者也是平等的吗？可惜大芭达的哥哥死了，他活着的时候是附近最知名的超凡者。”“超凡者死了，和普通人也没什么差别，都是朽烂在棺材里，尘归尘，土归土。”肖恩扬起马鞭在空中抽了一个脆响，远远惊起几只野兔。“我最佩服你没事冒出来的这些词，听着就有水平。‘尘归尘，土归土’，‘一生惑幻，临殁见真’，还有‘比黄昏还要昏暗的东西，比血液还要鲜红的东西’什么的，啧，虽然不太明白，但有点厉害。”“告诉过你了，真不是我编的，都是小说和漫画……呃，道听途说的。”“已经比我之前所听到的丰富多了。自从政府发布禁书令，民间就不再有所谓的书或者绘本了，我这辈子唯一见到印字母的纸是扑克牌……所有的故事和传说都只停留在口头上。”肖恩报之以沉默，这也是他心中不解的谜团，至少在他所到之处，还从未见过任何一本书籍。“联邦政府就是这样，自恃从所谓的灾祸中拯救了我们，总爱颁布各种奇怪的法令，你们那个，呃，神秘的东方国度不禁这个？真酷。”“酷可不是这个意思。”肖恩纠正好友的小错误，“我所出身的那个国家，人民很难用酷来形容，相反，他们勤恳朴实，所持的信条不过是守在自己的土地上活下去，因此挺过了很多的苦难。这不是酷，而是一种伟大。”“对我来说，这就是酷。”凯特尔有点向往，“有机会真想去看看。”“我也想，可我既不知道怎么来的，也不知道怎么回去……”“什么？”“没事，世界这么大，总有机会去看看的。”“敷衍，白叫你一声老铁了。”凯特尔不满地啧了一声。这都哪跟哪……肖恩乐了。他还记得初见凯特尔时的情形。彼时肖恩还在四处游荡，有次迈进酒馆，穿过拥挤的围观酒客，目睹到有人在打架，双方体型差距极为悬殊，那个极肥壮的汉子没费什么力气就把对面的倒霉蛋按在地下，姿势娴熟地反复殴打。这种事在任何酒馆都司空见惯，每天总有那么两三起，既没分寸，也没悬念，不出意外的话，那位倒霉蛋很快就会像破布袋一样被扔到路边，要么被好心人救起，要么被一场雨淋醒，然后自己回家——如果还能走路的话。当然，意外是必然发生的，那天意外的名字叫肖恩。“凯特尔，谁叫你丑得像那个东方人一样，怪不得倒霉。”？？？肖恩一头雾水，实在搞不清这种无端挑衅的目的何在，但这也不妨碍他冒着问号冲上去掀翻那个壮汉，进行了一场更为娴熟的殴打。当时凯特尔还躺在地上，只听到叫好声简直要冲破屋顶，看客们沸腾了，在他们眼中，这场景之荒谬离奇，就像有河马在蹂躏胡萝卜，然后被突然出现的兔子一脚蹬翻……总之，肖恩从企图混赖赌资的赌棍手下救了凯特尔一命，顺手也拯救了他十八岁的生日，在凯特尔引荐下，肖恩加入牧场，两人就此结识了。几番臭味相投后，两人友谊已经相当牢靠，除了那些鸡飞狗跳但也不算太过火的恶作剧外，最让肖恩意外的，倒是凯特尔经常向他讨教拼写。关于这里的语言，肖恩的水准也就那么回事，词汇量不算丰富，文法也很一般，但也由此才更能体会到，这片荒僻的地方在知识上也同样是荒野：没有书籍，没有学校，除了口耳相传的经验，几乎没有任何知识载体，新鲜的信息无法注入，于是生活不停重复着，回声一样单调乏味。自己这种半吊子，居然也算博学多闻了，真让人不好意思……本着知识扶贫的心思，肖恩答应下来，除拼写之外，他还答应给凯特尔讲故事。当然，拼写是正经拼写，故事就不是什么正经故事了……接下来要给他讲什么？《挑战风车的孤胆枪手》快结束了，下个故事的画风可以更清奇一点，《能实现任何愿望的七颗宝珠》？不同品类交杂才有趣嘛，时间充裕的话，《战斗之气幻化为马》也不是不行……肖恩表面老神在在的样子，歪脑筋动得飞快。一旁的凯特尔哪里察觉得到自己的未来正往肖恩指定的叫做“中二”的神秘词语上滑落，倒为好友叹起气来。“老铁，你见多识广，干嘛跑来当牛仔？这里偏僻，劳累，没什么钱，老爹煮的牛肉硬得像石头，你怎么吃得下去？”“偏僻有偏僻的好处，我要是被人追杀，就会觉得这里是个好地方。”“会有人追杀你？谁？在哪？为什么？”肖恩故意沉吟了一下，声音放低，露出追忆的表情：“追杀我的，可能并不是人……”“嗤——”凯特尔鼻腔里喷出一口气，“多大点事，我还被五条狗追着咬过，我有到处乱说吗？”两个人乱笑了一阵。“不过你也不要小看了牛仔。”凯特尔大手一挥，“不好当！要学的还多着呢。”“是是是。”肖恩摆出虚心求教的样子，倒也不是作伪。“骑马开枪，圈拢牛群，防御野兽等等，都是非常有用的经验。”凯特尔挥着双手，非常用力，这是他谈兴盎然时的习惯动作，和牧场中其他人不一样，无论求知欲还是讲述欲，他都旺盛得很。“就拿郊狼来说吧，对牛仔来说它们是最常见也最危险的敌人，虽然你和我们一起打死过几只，但那都是只能偷鸡的小狼，还有些体型巨大的个体，连长角牛都能拖走，枪弹也难以造成伤害，不仅因为它们个性狡猾，还因为——”“枪弹本来就难以对粘液生物造成伤害。”肖恩举手。“没错，你都会抢答了！这种大郊狼我们打不过，不过别担心，我掌握了足足十六种逃跑方式。”凯特尔口若悬河，胸脯拍得砰砰响。“面对郊狼最重要的是预判踪迹。比如它们偶尔会爬过荆棘，在上面留下汁液，根据汁液的干涸程度和拖行轨迹能确认他们的前进方向。气味也是个重要线索，它们爬过的草丛会有轻微倒伏痕迹，并且因为被腐蚀，带有特殊的酸臭味……”“像那样？”“没错！你看那丛荆棘上的汁液，阳光下会呈现墨绿色泽。因为沾了水，还有一部分变红，这是分辨郊狼和走鹃体液的主要凭据。地上那些焦痕，是它愤怒时喷出的腐蚀性口气造成的。汁液还没凝固，说明没走远，闻到浓郁的酸臭味了吗，啊哈，它好像就在我们脚底下呢，至于这‘呜噜’声，那是它要开始攻……我操，跑啊！”凯特尔终于反应了过来，嗷地一声，忙不迭地从枪套里掏他的左轮，可惜还是慢了一线，左侧浓密草丛中兀地刺出一条快若闪电的触手，螺旋状犬齿遍布周围，在空中带起飞溅的消化液。郊狼！幸好凯特尔的马因为受惊而扬起前蹄，黑光一闪，触手从马肚子下险险划过，带走一小条血肉，伤口迅速腐烂。疼痛让马儿惨嘶一声，奋起四蹄落荒而逃，凯特尔枪还没有拔出，脚一下从马镫上踩空，单脚挂着，双手胡乱扒着马颈：“快逃，郊狼不擅长追人啊啊啊啊啊啊啊啊——”凯特尔惨叫着被马裹挟远去了，看起来就算有一百六十种战术也派不上用场。顺便一提，他的声音其实是：“啊~啊~啊~啊~啊~啊~”这样。肖恩的动作比凯特尔快得多，在听到声音的一瞬间就已经握紧马鞭，并在触手出现的一瞬间扔出，闪过的黑光正是马鞭缠在触手上带偏了方向，让凯特尔逃过一劫。草丛蠕动，一坨仿佛被黏液包裹的生物探身出来。如果说它和肖恩印象中的“狼”有什么相似之处，也就是一双幽绿的眼睛和可怕的犬齿了，那些犬齿遍布在它周身的黏液触手上，尖端似乎有分泌孔，不断有消化液渗出，滴在杂草上腾起一道道气味刺鼻的白烟。它没有嘴，又或者全身都是嘴。它没有四肢，又或者全身都是四肢。黏液组成了它的皮肤，此时随着前行不停在路上留下污浊的痕迹，又不停从身体内部涌现出新的黏液来补充消耗。肖恩露出恶心的表情，右手按住鞍头，轻巧地一跃，脚尖腾起，以一个半蹲的姿势用脚尖点在马背上，然后迅速站直。就如同他骑马的姿态，站在马上的肖恩，肌肉也以一种奇怪的韵律在动作，脚尖和腿部像弹簧一样弹动，马背的颠簸被这种弹动吸收，让他的上半身好像在平地上一样稳当。在他这种姿态下，马匹也没有受到惊吓，缓慢又警惕地在肖恩指示下小步移动着。肖恩放开缰绳，将步枪抄在手里，枪口缓缓移动，瞄准了郊狼。郊狼惨绿色的眼珠裹在黏液里死死盯着肖恩，模样瘆人，它似乎对危险有所感应，并未贸然上前进攻，只慢慢蠕动，绕着圈子，皮肤上不停渗出消化液，把草丛腐蚀得吱吱作响。肖恩也没有着急开枪，他在耐心等待机会，小心控制马匹和狼相对绕着圈子，慢慢绕到一块大青石前面。黑影暴起，仿佛受到感应一般，效狼无定形的身体贴紧地面，最粗大的那根触手骑枪般刺出，带动空气发出撕裂般的啸音，直指肖恩，黑色触手因为快速抻长而在阳光下透射出原本墨绿的颜色，而肖恩也同时扣下扳机，枪声大作，子弹在空中划出火线。紧接着，肖恩却看也没看出膛的子弹，嘬唇发出一声唿哨，脚尖用力，身形如鹞子一般跃起，同时脚下的马四腿弯曲，马头下垂，整个身体趴在地上，避开了从岩石阴影中刺出的另一只触手。第二只郊狼。“等的就是你，果然不管哪里的狼都会玩前后伏击啊。”肖恩脑中默默背诵着某篇课文，手下丝毫不乱，空中屈腿转折，从马背上跃起的高空优势让他轻松越过大石，看到后面景象。另一只体型稍小些的郊狼整只身体都糊在岩石上，触手死死扒着岩石缝，因为触手伸得过长而被摊薄的身体就像岩石上一张二维抽象画，里面某些黑色的半液态内核清晰可见。身在空中来不及上弹，肖恩枪交左手，右腕急振，一道残影在夕阳余晖映照下急速飞出，紧贴岩石掠过，竖着划开郊狼薄薄的身体，将里面的原质一分为二，夺地一声插在地上，一丝痕迹都没有在岩石上留下。那是一张约9厘米长，6厘米宽，正面是数字和花色，背面是条纹纹理的硬纸片——确切地说，扑克牌。较大的郊狼见肖恩子弹出膛，连忙身体后仰，试图回收去势未尽的触手，然而哪来得及，子弹切过被抻得纤细的触手根部，就像被切断的橡皮筋，触手前端部分啪地一声不知道飞去什么地方，剩余部分猛地回弹，把郊狼的身体打得向后飞腾。郊狼发出类似哀鸣的叫声，因为没有声带，有点像在吹很响的泡泡糖。借着这股退势，郊狼剩余的触手飞快挥动，想要逃跑。电光火石间，肖恩纸牌出手后又一搭岩石上部，身体扭正，双腿踢在岩石上，黑色身影带着夕阳金色的辉光，利箭一般射向逃跑的郊狼。郊狼一只眼睛看着前路，另一只扭在身后看着肖恩，眼见肖恩速度极快，它的眼球渗出血丝，猛地挥舞几条剩下的短触手，要在肖恩赶上之前抢先攻击。肖恩不闪不避，在空中猛地伸腿，拉成一字马，身体俯低，正从触手的间隙穿过，同时扳动枪机下的杠杆，飞快压上子弹，枪口一拧，指向郊狼触手断裂的位置。砰！子弹近距离爆发出来的冲击力沿着伤口灌入了效狼体内，那些黑色的物质仿佛煮沸的沥青冒起气泡然后纷纷爆开，体表黏液则再也无法维持形状，像喷嚏打出的鼻涕一般倒飞了出去，眼球像鼻屎一样被崩出，显然死得不能再死了。借着枪身的后坐力，肖恩身体后仰，手掌撑地后翻，稳稳站住。一阵马蹄声急乱奔近，伴随着砰砰朝天放枪的声音，凯特尔独有的公鸭嗓音远远传来：“老铁!我来支援你了，~啊~啊~啊~啊~啊~”
*人于荒野独行神魂飘荡，污泥满身。——《最初之诗》*引子一缕晨光爬过山峦，刺破暧昧难明的黑暗，照耀在宽阔的红土平原上。杨树和雪松交织成林带，点缀着山坡和陡峭山壁，在鼠尾草原泛出的深紫色泽中拉出一道道长影子。几团小光点在散乱地游动，和朝阳一起点亮了小镇的轮廓。那是一些持着马灯跑动的人，全副武装，如临大敌，还有几个骑手在套着马鞍，端着枪支，沿着村子开始巡逻。现在还没有到起床做工时间，显然是有大事发生了。镇子中央是一座白色大屋，此时宽敞的大门正开着，那些拿着油灯的汉子们就从这里进出，高声喊着什么，但更多的是做出虚心接受训斥的模样。“雷德！去附近数数有没有丢失的马，再看看有没有陌生的蹄印！”“巴尔！带着你的手下去山林里转转，把你当猎人的手段都拿出来，别告诉我都忘在婆娘的肚皮上了！”“罗比！那两条狗是不是你的种？它们和你一样无能，如果还是什么都闻不出来，就炖了下酒！”一道身影就站在屋子中央，高壮的体型几乎隔绝了内外的光线，声声咆哮就发自他的口中，纷飞的唾液暴雨一样洗礼着那些可怜人。那些被咆哮的汉子瑟缩着肩膀，一点也不敢反驳。他们可不是怂蛋，正相反，他们是旅鸽镇凶名远播的警察队，再之前则是战火中走出的老兵，最少也有几条人命在手上。可他们还是不敢反抗，因为那是穆勒，旅鸽镇的副警长。穆勒是个粗犷的中年大汉，连髭胡须，头皮油亮亮的，紧绷的衬衣下是肌肉极为发达，像头巨熊一样。周身缠着好几条满挂大口径子弹的武装带。一边叫嚷，穆勒一边用手中长管步枪的枪托磕向地面，沉闷的巨响传得老远。每响一声，面前的警察们都会鹌鹑似的抖起来，不敢想象被那枪托砸中的下场，毕竟穆勒的枪托可不是木头，而是整块的铸铁。穆勒的身后还有道身影，对周围的嘈杂充耳不闻，正提着一盏马灯俯身照着什么。提灯光芒晃动，衬得身影摇摆不定，看得出来他体格瘦削，穿着黑色西装，头戴高顶礼帽。除了腰间手枪以外，他似乎没带任何武器。"穆勒！"人影起身招呼，提灯光线沿着考究的西装上移，照出胸前警徽。穆勒闻言转身，面对这道身影，他迫人的气势突然不见了，整个矮了一头似的，走到尸体附近询问。“怎么说，拉塞尔？”旅鸽镇警长拉塞尔把礼帽推上去，露出一张不太显年纪的脸，尖下颏，细长眼睛，胡须和眉毛都仔细修饰过，就是面容有点阴沉。“不是普通人做的。”“……超凡者？”“周围还留存着超凡扰动，错不了。”拉塞尔伸手指向脚下，那里摆着一具尸体，不用说，这也是骚乱的由来了。“上任才几天就引来超凡者的狙杀，这位镇长可真有找麻烦的本事。”尸体呈大字形摊在墙壁一侧，头——如果还能叫头的话——枕着地板的边线，脚上的靴子正抵着办公桌的底缘。手上的戒指，齐整的胡须，昂贵的礼服和马靴，都昭示着这位先生的体面身份。只可惜对于死人来说，再体面也已经是过去式了。他自鼻梁以上的部位全部消失不见，只余留喷涂满墙的骨沫和已凝结的红白污迹，偶尔来往的先生们脚步重了点，还会震下那么一两团，在地板上翻滚几下。检查现场的这些人，动作可称不上轻柔。穆勒嗤了一声，“死掉一条蛀虫——”“罗比！”拉塞尔突然扬手一甩，“蠢货，给我离书远点！”办公桌旁一个极胖的身影飞快地缩起脖子，拉塞尔甩出的黑影擦着头皮飞过，钉在一旁的屋壁上微微颤动，竟然是一柄短刀。被称为罗比的胖子触电般远离了镇长的办公桌，不敢对那张桌子再看上一眼，只摸着头上被新削出的一截发茬，嘴唇嗫嚅着。“抱歉，头儿，我没注意那有书……不，其实……”“拉塞尔。”目睹这一幕，强壮如熊的穆勒也感到脊背发凉，硬着头皮开口。“镇长是死是活无关紧要。”拉塞尔知道穆勒的意思，挥手让如蒙大赦的罗比滚出屋子，“可这么一来，会有无数官面上的麻烦……”“尽快找到凶手不就是了？对你来说小事一桩。”穆勒问。“找个屁！”拉塞尔骂道，“是个老手，现场除了那些超凡扰动，什么痕迹都没留下。”“好像也没找到目击者。”“动动你的猪脑子！镇政府最后一位办事员已经被镇长自己打跑了，哪来的目击者？——咱们这位盖奇镇长真够奇葩的，上任之后没做过一件正经事，整天只知道打骂下属，有人呆得住才怪。”拉塞尔似乎有些不耐烦，开始左右踱着步子。“现在时机很敏感，咱们不能当出头鸟，得把这事遮过去。”“铁路局什么时候接管这片区域？”“很快，他们势力越来越大，那帮‘平克顿’杂种也掺和进来了。”地板震了两下，那是穆勒在心绪不宁地磕着枪托。“那怎么办，拉塞尔？咱们得在上头发怒前解决这事。”“还能怎么办，老规矩。”拉塞尔瞥了眼不敢靠近的手下们，嗓音低下来。“万一他们派了平克顿……”穆勒还是有点犹豫。“那就做干净点儿，蠢货！”拉塞尔又咆哮起来，“反正也没有目击者！”说完他又看着墙上的挂钟：“按时间来看，州府已经得到消息，派来的人应该早就上路了。”镇长的死亡现场是镇上居民发现的，而因为清理附近匪患而忙了一整夜的他们，回到镇上时才得知这个消息，传讯鹦鹉早就放出去了。穆勒啐了一口：“其实也没必要这么谨慎，就算来人发现点什么，我们还能让消息传出去不成？只要下手痛快点，有你我在，即使是超凡者……”“来人可能是朗基·贝登。”“那条疯狗？”穆勒听到一个让他略为惊讶的名字，但凶狠的神色还是一闪而没，“名头那么响，手底下可未必，真有机会，我倒想……”“想什么，说说看？”突兀的声音在屋里响起，所有人都吓了一大跳，什么时候多了个人？就在尸体一侧，两位警长视野盲区的位置，一个穿着米黄色条纹西装的人影静静站在那里，笑眯眯的神情带着些无辜，仿佛他一直、就该站在那里一样。他留着两撇以夸张角度向上翘起来的胡子，保养得当的脸上看不出年纪，深蓝色的眼珠带着不明的意味看着众人。拉塞尔率先冷静下来，他撩起西装下摆，亮出腰间的枪柄。“你是谁？不出示证明就闯入现场的人，警察有视同嫌犯，直接击毙的权力。”“那为什么不顺势开枪呢？穆勒不是刚想这么做？”来人用食指点了点眉心，然后哈哈大笑起来。他奇怪的口音里带着十足的嫌弃味道，仿佛面前站的不是人而是苍蝇。“接到州府的命令，鄙人不敢怠慢，花了好大力气才尽快赶到这里，没想目睹了让我痛心的一幕，警察不仅试图伪造现场，还持械威胁政府雇员，拉塞尔，你这是知法犯法啊。”“我可没说过什么伪造现场的话。” 拉塞尔扯扯嘴角。“原来你就是朗基·贝登。”“我确实没有证据。”被叫做朗基·贝登的人点头，“不过恐怕只要晚来那么几分钟，现场就已经被魔术师出身的拉塞尔警长偷天换日了吧。”想了想，他转而笑到：“不过照我看来，你倒更像一名驯兽师，不然身边怎么会带头熊？”“你才是熊，狗X的！”穆勒气得青筋暴跳，抡起步枪就砸，落至半空却再难寸进，原来被旁边的拉塞尔伸手抓住了枪管，碗口粗的枪管嵌在他纤细的手指里，没法动弹分毫。“我不变魔术很久了，贝登先生。”拉塞尔往前迎了一步,示意穆勒退后。“是吗？那怪不得你连道具都丢了。”贝登抬起右手，掌中多了一根精致手杖，随着手指的捻动飞舞旋转。拉塞尔目光微动，回头扫了一眼，才发现倚在桌边的手杖已不翼而飞。不过他神色未变，双掌啪地互击，分开时，右手提了条丝巾，在空中一抖，突地化为手杖，而贝登视线下移，发现自己掌中不过是束鼠尾草。“有意思。”贝登露出了感兴趣的神色，也拍了下手掌，本就干枯的野草顿时被拍成碎屑，迅速变黑、分解，消融在空气中。“手法不错，警长先生，送你件礼物。”贝登摊开手掌，将一枚袖扣递了过去，“我想你大概用得着。”拉塞尔猛地看向自己光秃秃的袖口，忍不住惊愕，表情变幻几次才伸手接过袖扣。“我要是你，就会把拾掇外表的时间用来练习魔术。”贝登语带嘲讽。“好了，寒暄结束，我的人来了。”伴随着他的话语，有沉重的马蹄声由远及近，最终停在了大屋门口不远处。一个衣着整齐的年轻人出现在门外，边走边嗓音洪亮地喝骂着什么，好像又是那个可怜的罗比冲撞了他，被骂得不敢还口。年轻人很快走到门前，停脚理了下衣衫才迈步入内，表情也瞬间由怒容变为含笑，但这笑容明显只向着贝登，环顾他人时，就有股掩不住的矜持傲慢。“开始吧。”贝登示意年轻人过来身边，把公文包递给自己。拉塞尔扫了公文包一眼，忍不住说：“我已经仔细检查过……”“你做过什么我不感兴趣。”贝登转身面对尸体，不客气地制止拉塞尔，接着把手伸进公文包，用两指夹出个小玻璃瓶，对着光线晃了晃，显出里面透出荧紫色的液体，接着屈指弹去，玻璃瓶的塞子飞出打在尸体头颅一侧，碎成残屑。贝登俯下腰，轻轻抖动瓶身，将液体均匀地洒在残屑上，接着手指一捏，做出虚提的动作，火焰随动作缓缓升起，将残屑烧成黑块。黑块又自行向上拉伸，凝成许多细小圆柱，围成高约30公分的鸟笼形状。鸟笼中心有绿色凝胶状物由小变大，渐渐组成不断挣扎，面容模糊的人形。“拉塞尔先生，听说你是超凡者，但你可能不知道，真正的超凡者如何做事。”接着，贝登饶有兴致地瞧了瞧镇长那破碎的脑壳，在众人惊愕的目光里，他屈膝蹲下，单手拄地，仿佛要进行什么仪式似的，慢慢地，毫不犹疑地，伸出舌头用力地，舔了向尸身的脑壳内部。舌头一翻，将刮起来的可疑污迹送入口腔，然后腮部鼓了几鼓，喉结蠕动，将那团物事吞了下去。几乎同时，那道绿色凝胶小人发出惨叫，无数细小的绿色触手从脚下生出，团团缠住周身，又纷纷变黑枯死，散成黑雾消失。每有触手脱落，小人的面容就更加清晰。有目睹过程的警察再忍不住，跑出屋外大声呕吐了起来。拉塞尔和穆勒脚步没有动，但也满脸强忍着恶心的表情，说不出话。全场只有贝登露出回味美食的笑容盯着那个不断发出凄厉嚎叫的绿色小人，慈祥的笑容就像父亲看着儿子。眼见小人的轮廓逐渐成型，拉塞尔忍不住又开口说道：“超凡力量有时也会受到蒙蔽，可以用推理……”“小家伙。”贝登压根没理拉塞尔，他双脚不动，上半身转了半圆，以一个滑稽的姿势面向他身边的助理。“听说你受过相当良好的痕迹追踪学教育，我问你，如果是你，要怎么应对这种犯罪现场？”年轻人正观察着周围人的神色露出好笑的神情，闻言肃容思索：“我老师有教过我，只有超凡才能应对超凡。面对类似事件，需要准备好对应的能力、秘仪，至少也要拥有几只受过训练的信标猎犬。”“不错，对一个合格的毕业生来说，这都是基本常识。”贝登点点头，“那么，不涉及超凡的寻常事件要怎么办？”“寻常事件。”年轻人露出笑容，“寻常事件和我们有什么关系呢？”贝登忍不住大笑起来，声音极响，还不停拍着年轻人的肩膀以示赞许。“没错，没错。可你看那位警长先生，该做的事情全没做，一直在看来看去的瞎耽误工夫，像个普通人一样。而且明明养了两只猎犬，却连追踪超凡扰动都不会，他平时是把驯狗的精力用来骂手下了吗？”贝登提到的猎犬就在房内，是两只雄健的罗威纳犬，此时正蹲坐在墙角垂头丧气，呈面壁思过状。贝登的笑声突然顿住，他装模作样地思考了一下：“嗯，可能在警长看来，两者差不多吧。”又是一阵夸张的笑声，连带那个年轻人也附和着笑起来。“现在，先生们！我要玩‘好消息和坏消息’的游戏了。”贝登突然提高音量引起众人的注意，然后扭正身体，朝向两位脸色铁青的警长。“好消息是——就在我动身的前一刻，铁路局已经取得政令，以后凡有铁路经过的区域，我们都享有对超凡事件的绝对处置权。镇长既然死于超凡者手下，那我朗基·贝登，就宣布以铁路七局外务专员的名义接管此案。而你们则不用担负失职的责任。”贝登拿出一枚小巧金属名片，在拉塞尔等人的眼前晃晃。“但另一方面，我决定上报州府，为旅鸽镇警署的人选提出新的建议。拉塞尔，你和你的属下在不合适的岗位上待得太久了，而我认为在『群星』治下，你们这些信仰『高塔』的废物，唯一的归宿就只有监狱——”贝登的眼睛死死盯住拉塞尔，再次无声地笑起来，嘴角咧得非常夸张，昏暗灯光下仿佛张开了一个黑洞，说不出的惊悚可怖。一片寂静，只有屋外人员忙碌的脚步声，偶尔的狗吠声，和不知道是否该留在屋里的警察们局促不安的衣物摩擦声。拉塞尔眼角直跳，几度要爆发，但面色变幻几次后，看向贝登手中的名片，还是选择了沉默。笼中的小人渐渐停止挣扎，保持固定的姿势停在那里。变成黑色失去活力的触手焦油般贴在它的表皮上，形成道道沟壑。微型旋风自笼底升起，小人表面裂开，触手变成灰烬剥落，现出眉眼清晰，须发可辨的人形，是个三十多岁的男性，穿着就像片土地上随处可见的枪手，方巾，牛仔帽，眼神中带着茫然，垂着手浑身透绿地站在那里，他的左手只有三根手指。贝登收回盯在拉塞尔身上的视线，在身前比了个手势，向着鸟笼低声念出单词：“苏醒！”他的音调非常奇怪，像是在咽腔内发生了多次共鸣，回荡不休，出口之后带着层层叠叠的震动扑向笼内。笼内小人浑身一震，先是诧异地看了看四周，接着猛跳起来，嘴巴开合，仿佛在怒骂着什么，还从腰间拔出一把小枪，对准贝登频频射击，动作十分娴熟，可惜枪口噗噗作响，喷射出的就只有绿色汁水而已。贝登手指下压，笼子上方迅速凝结出黑色结晶，有滚雷从中劈落，打在小人的头上，嘭的一声，小人的头炸裂成四溅的汁水，随即缓缓愈合，帽子不翼而飞，头发根根直竖。反复几次，数道雷霆将小人的衣服劈碎，露出的隐私部位被绿色的马赛克遮盖。贝登面无表情地看着小人由怒骂变成告饶，惨叫，最终跪在地上连连哀求才收了手势。“听听嫌疑犯们可爱的惨叫，对我来说是无上的愉悦，就是我活跃在一线，并乐此不疲的原因。好了，接下来该例行公事了。”贝登从怀里掏出怀表看了时间，转身向门外走去，助手急忙小跑着跟上。来到门外，助手利落地攀上自己的马，而贝登轻轻一跃，就坐在了自己的马背上，手里稳稳提着那个黑色鸟笼，里面绿惨惨的小人此刻蹲坐在笼中，无奈地咕哝着什么，指向远处。贝登拨转了马头，忽又回身对站立在门口的两位警长抬帽笑道：“两位，如果觉得无事可做，不妨收收尸，打打牌，作出忙碌的样子，毕竟监狱里可没有这样的悠闲日子。”“狗X的！”穆勒再也忍耐不住，一拳打在大屋的墙上，巨大的拳力轰然爆碎了半面木墙，碎屑如同箭矢射向贝登二人。年轻助手吓了一跳，带着马避让，贝登则连姿势都没有变动，微笑看着疾飞过来的碎屑从身周掠过去，带起的气流吹得二人衣服猎猎作响。贝登又抬了下帽子：“气势不错。”蹄声响起，两骑不紧不慢地离开了。旅鸽镇渐渐被抛在身后，年轻助手仿佛还在回味对方的窘态，嘴角翘起，腰杆也更直了，眼光扫到贝登手里的黑色鸟笼，好奇询问道：“贝登先生，刚才那些警察……”年轻人志得意满，却看着贝登头颅不动，一道冰冷目光扫过来，莫名想起他刚才对尸体做出的那些行为，不由生出些许寒意，腰杆也没那么直了。再一定睛，却见上司已经露出笑容。“很满意刚才的胜利，是不是？”“啊，没错。”和善的笑容让年轻人怀疑刚才的寒意只是幻觉，他又想到自己老师的身份，心里不由多了些淡定。“我从小就听说『高塔』的威名，没想到现在这些信徒这么不堪。”“自从得不到『巴别』的回应，他们就已经是『高塔』的弃民了，力量就减弱，教派凋零……要不是有些事非他们不可，早就该清算他们了，迟早有一天，他们都要进监狱。”不知想到什么，贝登皱了下眉头又很快舒展开。“当然，足够优秀的人也不会信仰『高塔』，而应该信仰我们『群星』。”“感谢您让我见识了铁路局的强大，这样的办案手段前所未闻，是您的超凡能力吗？”年轻人神色兴奋，美好前程在向他招手。“不，这个能力来自咒物，虽然都是超凡力量，物品和本身的能力可不太一样，你老师没教过你吗？”贝登抚着笼子，不厌其烦地介绍。“教过，但我见过的咒物还不多……所以笼子里就是凶手的样子吗？”“这个咒物可以利用现场痕迹，与疑犯建立起联系，并以复制体的方式重现，外貌、性格都和本体一样，也能找到本体的位置。所以没错，这就是凶手，你不妨猜下他的身份。”年轻人目光在小人身上游移不定，最终停留在那三根手指上，蓦地睁大眼睛：“神枪手比利！？‘狂鼠’范迪伦的手下？”“没错，你果然很优秀。”贝登不吝啬赞许的笑容，“他们那些家伙就像阴沟里的老鼠一样难缠，其中还不乏超凡者，只凭我的话，应付起来有些棘手。”年轻人没有惧怕，反而自信地笑起来，“只要不是太厉害的超凡者，其实我能帮上忙。”“靠他们？” 贝登没有回头，只用拇指点点身后隐约的黑暗，“我知道那是你老师赠送的礼物。你让他们悄悄跟在后面，想测试我什么时候察觉吗？”年轻人顿时有些讪讪，“贝登先生，我……”“不用道歉，接下来有用得着他们的地方，卖力点。”“没问题，贝登先生。”年轻人现出激动的红晕，“事情会顺利的，身即国土！”“身即国土。”贝登神色也严肃起来，他左手抚胸，视线投向天穹，不知道在追逐着什么，“我们是时代的仪轨。”