杜克大学-Rust-编程-Linux-命令行与-LLMOps-笔记-全-

杜克大学 Rust 编程、Linux 命令行与 LLMOps 笔记(全)

001:认识讲师-Alfredo Deza 👨‍🏫

在本节课中,我们将认识本课程的讲师Alfredo Deza,并了解这门结合Rust与Python的课程将涵盖哪些内容。

大家好,我是Alfredo Deza。我很高兴为大家带来这门Rust与Python课程。在这门课程中,我们将结合这两种编程语言,为大家提供成功所需的一切知识。无论你是需要其中一种语言,还是已经精通其中一种,这门课程都适合你。

我拥有多年的软件工程经验,工作经历涵盖小型初创公司到大型工程团队。在这些经历中,我始终致力于为项目和团队增加一些自动化改进。

我认为这些经验至关重要,并且你将在整个课程中看到它们以各种形式呈现。例如,在自动化方面,我认为仅仅了解或掌握一门编程语言是不够的,拓展视野、学习其他更强大、更新的编程语言(如Rust)同样重要。

我们将探讨Rust的一些积极方面和不同之处,分析你为何以及何时可能选择其中一种语言。我们会从命令行工具的基础知识开始,然后进一步深入。

我非常高兴能为大家带来这门课程,并希望你在课程结束时发现它非常有用。


本节课中,我们一起认识了讲师Alfredo Deza,并了解了这门课程的核心目标:结合Rust与Python两种语言的优势,从基础命令行工具开发入手,帮助大家拓展编程技能,并理解在不同场景下如何选择合适的工具。

002:认识讲师-Noah Gift 👨‍🏫

在本节课中,我们将认识本课程的讲师Noah Gift,并了解他将要教授的核心内容:如何结合Python与Rust的优势,构建优雅且高性能的解决方案。

大家好,我是Noah Gift,我是本课程的讲师。

我们将要学习Python和Rust,以及如何将两者结合,以创建优雅的解决方案。

具体方法是:使用Python最擅长的领域,同时使用Rust最擅长的领域,即高性能代码与安全性

接下来,简单介绍一下我的背景。我在软件行业拥有丰富的经验,包括创建初创公司、为财富500强公司工作。我撰写了10本书籍,其中包括关于Python和Rust的书籍。

本课程旨在帮助你成功掌握Python的技能、掌握Rust的技能,然后将它们融合在一起,以真正创建混合且前沿的解决方案。

好的,让我们开始吧。


本节课中,我们一起认识了讲师Noah Gift,并了解了本课程的核心目标:融合Python与Rust,利用各自优势构建解决方案。在接下来的课程中,我们将深入探索如何具体实现这一目标。

003:关于本课程 📘

在本课程中,我们将学习Rust和Python的基础知识,重点聚焦于CLI(命令行工具)的开发。命令行工具不仅适用于小型自动化任务,还能扩展至数据工程、机器学习及系统工程等更复杂的应用场景。

课程概述

命令行工具的重要性在于,它们为你提供了构建更复杂系统的基础知识和实践经验。你可以将这些脚本和程序集成到持续集成/持续交付平台中,应用于从简单自动化到复杂任务的各类场景。

上一节我们介绍了课程的整体方向,本节中我们将详细探讨课程的具体内容和学习路径。

课程内容与结构

以下是本课程的核心内容安排:

  1. 工具创建基础:我们将学习如何使用Python和Rust创建命令行工具,从最简单的用例开始,逐步过渡到更复杂的模式和高级应用。
  2. 语言结合应用:课程将展示如何混合使用Python和Rust,让两种语言相互连接。例如,你可以用Rust包来增强Python的功能。
  3. 打包与发布:在课程后期,我们将学习如何打包这些工具,并将其发布到不同的平台。
  4. 实践实验室:我们准备了配套的实验环境,帮助你通过实际动手操作来巩固所学知识,并高效利用课程中的示例。

学习目标

通过本课程的学习,你将掌握构建实用命令行工具的技能,并能够将这些新技能应用于实际工作中,从而显著提升工作效率。


本节课中我们一起学习了本课程的核心目标、内容结构以及最终的学习成果。接下来,我们将从基础开始,逐步深入命令行工具的开发世界。

004:引言_1

在本节课中,我们将学习如何使用Python构建一个命令行工具。我们将从最基础的环境搭建开始,涵盖Python开发中必需的概念,如虚拟环境。接着,我们会构建一个最简单的、无依赖的单文件命令行工具。之后,我们将进一步学习使用一个具体的框架——Click框架。本教程将采用一个真实世界的用例:与操作系统中的命令行工具交互,以获取硬盘和块设备信息,并用Python处理和呈现这些数据。这在系统工程中非常常见。

开发环境搭建与虚拟环境

首先,我们需要设置Python开发环境。对于Python开发而言,使用虚拟环境是至关重要的。虚拟环境可以为每个项目创建独立的Python运行环境,避免包依赖冲突。

以下是创建和激活虚拟环境的步骤:

  1. 创建虚拟环境:在项目目录下,运行 python -m venv venv 命令。这会在当前目录创建一个名为 venv 的文件夹,其中包含独立的Python解释器和包管理工具。
  2. 激活虚拟环境
    • 在Windows系统上,运行 venv\Scripts\activate
    • 在macOS或Linux系统上,运行 source venv/bin/activate
  3. 激活后,终端提示符通常会显示虚拟环境名称,表示你已在该环境中工作。之后使用 pip install 安装的包将只影响当前虚拟环境。

构建最简单的命令行工具

上一节我们介绍了环境准备,本节中我们来看看如何构建一个最简单的Python命令行工具。这个工具将是一个独立的Python脚本,不依赖任何外部库。

创建一个名为 simple_cli.py 的文件,并添加以下代码:

#!/usr/bin/env python3
import sys

def main():
    if len(sys.argv) > 1:
        print(f"Hello, {sys.argv[1]}!")
    else:
        print("Hello, World!")

if __name__ == "__main__":
    main()

这个脚本的功能是:如果运行命令时提供了参数,则向该参数问好;否则,输出“Hello, World!”。

使其可执行并运行:

  1. 在终端中,使用 chmod +x simple_cli.py 命令为脚本添加可执行权限。
  2. 可以直接运行 python simple_cli.py,或者使用 ./simple_cli.py(在Unix-like系统上)来执行。

使用Click框架构建增强工具

虽然简单的脚本可以工作,但对于需要复杂参数解析、子命令和帮助文档的工具,使用框架会更高效。接下来,我们将使用Click框架来重构并增强我们的工具。

首先,在激活的虚拟环境中安装Click:

pip install click

然后,创建一个新的文件 enhanced_cli.py,使用Click重写功能:

#!/usr/bin/env python3
import click

@click.command()
@click.option('--name', default='World', help='The person to greet.')
@click.option('--count', default=1, help='Number of greetings.')
def hello(name, count):
    """A simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    hello()

这个工具通过 --name 参数指定问候对象,通过 --count 参数指定问候次数,并且自动生成了帮助文档(通过 --help 查看)。

实战:与系统工具交互

现在,我们将应用所学知识,完成一个真实用例:与系统工具交互。我们将使用Python调用 lsblk(列出块设备)命令,解析其输出,并以更友好的格式呈现信息。

以下是实现此功能的脚本 disk_info.py 的核心部分:

#!/usr/bin/env python3
import subprocess
import json
import click

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/duke-rs-prog-linuxcli-llmops/img/a5e2dfb574afef143924f97ca6102269_1.png)

def get_lsblk_json():
    """调用 lsblk 命令并以JSON格式获取输出"""
    result = subprocess.run(['lsblk', '--json', '--output', 'NAME,SIZE,TYPE,MOUNTPOINT'],
                            capture_output=True, text=True)
    if result.returncode == 0:
        return json.loads(result.stdout)
    else:
        click.echo(f"Error running lsblk: {result.stderr}", err=True)
        return None

@click.command()
def list_disks():
    """列出系统上的磁盘和分区信息"""
    data = get_lsblk_json()
    if data:
        click.echo("Available block devices:")
        for device in data.get('blockdevices', []):
            click.echo(f"  - {device['name']} ({device['size']}) [{device['type']}]")
            if 'mountpoint' in device and device['mountpoint']:
                click.echo(f"    Mounted at: {device['mountpoint']}")

if __name__ == '__main__':
    list_disks()

这个脚本定义了 get_lsblk_json 函数来安全地调用系统命令并解析JSON输出。list_disks 命令则使用Click来格式化并显示这些信息,例如设备名、大小、类型和挂载点。

总结

本节课中我们一起学习了构建Python命令行工具的完整流程。我们从搭建虚拟开发环境开始,创建了一个最简单的无依赖脚本,然后引入了Click框架来高效处理命令行参数。最后,我们完成了一个实战项目,使用Python调用并解析系统 lsblk 工具的输出,从而掌握了与操作系统底层交互的基本方法。这套流程是开发更复杂命令行应用的基础。

005:搭建命令行工具开发环境 🛠️

在本节课中,我们将学习如何为Python命令行工具项目搭建一个独立的开发环境。我们将重点介绍虚拟环境的概念及其创建和激活方法,以确保项目依赖与系统环境隔离。

概述

我们当前有一个Python项目,一个用于命令行工具的Python CI项目。虽然我们尚未详细探讨项目中的所有目录和文件(这些内容将在后续课程中介绍),但首先正确配置开发环境至关重要。本节将指导你完成Python虚拟环境的设置,这是Python开发中的核心实践。

设置Python开发环境

首先,你需要确保系统中已安装Python。在本例中,我们使用的是Linux系统,并通过运行 python3 --help 来验证Python 3已正确安装。请注意,在你的系统中,命令可能是 pythonpython3,这取决于具体的系统配置。

接下来,我们将为项目创建一个虚拟环境。Python在处理依赖项和将其与系统隔离方面存在挑战。如果不使用虚拟环境,而直接将项目依赖安装到系统中,可能会导致依赖冲突。因此,创建虚拟环境是必要的,它能将项目的依赖隔离在一个独立的空间中,便于开发、更新和移除,而不会影响系统或其他项目。

以下是创建虚拟环境的步骤:

1. 创建虚拟环境

我们使用Python内置的 venv 模块来创建虚拟环境。命令格式如下:

python3 -m venv .venv

让我们分解这个命令:

  • python3:指定Python解释器。
  • -m venv:表示运行Python自带的 venv 模块。
  • .venv:这是为目标虚拟环境目录指定的名称。执行后,会在当前目录下创建一个名为 .venv 的文件夹。

运行此命令时,如果系统尚未安装创建虚拟环境所需的依赖,可能会遇到错误。例如,在某些Linux系统上,你可能需要先运行类似 sudo apt-get install python3-venv 的命令来安装必要的包。安装完成后,再次运行创建命令即可。

成功执行后,你会在文件管理器中看到一个名为 .venv 的新目录,其中包含大量文件,这就是你的隔离环境。

2. 激活虚拟环境

创建环境后,必须激活它才能使用。激活后,终端会话将使用虚拟环境内的Python解释器和工具,而不是系统全局的。

在Linux或macOS系统上,使用以下命令激活:

source .venv/bin/activate

激活后,你会注意到命令行提示符前出现了 (.venv) 字样。此时,运行 which pythonwhich python3 命令,路径将指向 .venv 目录下的解释器,而非系统路径。这至关重要,因为它确保了后续通过 pip 安装的所有依赖包都会被安装到这个虚拟环境中,从而实现依赖隔离。

如果虚拟环境因依赖冲突而出现问题,你可以简单地删除整个 .venv 目录,然后按照上述步骤重新创建。这是Python开发中一项核心且必须习惯的操作。

3. 使用虚拟环境中的工具

激活环境后,你可以使用虚拟环境内的 pip。可以通过运行 which pip 来确认它指向虚拟环境目录。此时,你可以安装项目所需的依赖。

通常,项目会有一个 requirements.txt 文件列出所有依赖。你可以使用以下命令一次性安装它们:

pip install -r requirements.txt

这个命令会读取 requirements.txt 文件,并下载安装所有列出的包到当前的虚拟环境中。

相反,如果你停用了虚拟环境(通过运行 deactivate 命令),那么 pip 命令可能不再可用,或者会指向系统的 pip。这清楚地展示了系统环境与虚拟环境之间的区别。

总结

本节课我们一起学习了搭建Python命令行工具开发环境的核心步骤。我们首先验证了Python的安装,然后重点讲解了如何创建和激活虚拟环境(使用 python3 -m venv .venvsource .venv/bin/activate),最后介绍了如何在激活的环境中使用 pip 安装项目依赖。掌握虚拟环境的使用是进行清晰、无冲突的Python开发的基础。

006:你的第一个Python命令行工具 🐍

在本节课中,我们将学习如何构建一个简单的Python命令行工具。我们将通过一个实际例子来理解其基本结构和工作原理,这个工具将帮助我们更好地处理lsblk命令的输出。

概述

我们将创建一个Python脚本,它能够接收一个设备名称作为参数,然后调用系统的lsblk命令,并以JSON格式解析其输出,最终返回与指定设备名称匹配的设备信息。整个过程仅使用Python的标准库,无需安装任何外部依赖。

问题场景

首先,让我们明确我们要解决的问题。在终端中运行lsblk命令可以列出所有块设备及其信息,例如名称、大小、类型和挂载点。然而,在某些环境(例如容器中)直接通过lsblk <设备名>查询特定设备可能会失败,提示“不是块设备”。我们的目标是创建一个工具,能够可靠地获取lsblk提供的信息,并以更可控的方式呈现特定设备的数据。

项目结构解析

现在,让我们来看一个实现此功能的简单Python脚本示例。我们将逐步分析其核心组成部分。

脚本入口与参数处理

脚本的底部是执行的入口点。以下代码确保当直接运行此Python文件时,会执行main函数。

if __name__ == "__main__":
    main(sys.argv[-1])

sys.argv[-1]会获取命令行传递的最后一个参数,并将其作为字符串传递给main函数。无论你传递的是数字还是其他内容,它都会被视为字符串。

核心函数:run_lsblk

main函数接收设备参数后,会调用run_lsblk函数。这个函数负责执行命令并处理输出。

def run_lsblk(device):
    command = f"lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT"
    output = run_command(command)
    data = json.loads(output)
    # ... 后续处理逻辑

它构建了一个调用lsblk的命令,要求以JSON格式(-J)输出名称、大小、类型和挂载点信息。run_command函数执行该命令并返回输出字符串,然后使用json.loads将其解析为Python字典。

以下是处理JSON数据、查找匹配设备的逻辑:

    for parent in data["blockdevices"]:
        if parent["name"] == device:
            return parent
        for child in parent.get("children", []):
            if child["name"] == device:
                return child

代码首先在顶级设备(父设备)中查找名称匹配项。如果找到,则返回该设备信息。如果未找到,则遍历每个父设备的子设备列表(children),继续查找。

辅助函数:run_command

run_command函数使用Python的subprocess模块来执行系统命令。

def run_command(cmd):
    parts = cmd.split()
    result = subprocess.run(parts, capture_output=True, text=True)
    return result.stdout

它将命令字符串分割成列表,然后使用subprocess.run执行。capture_output=Truetext=True参数确保我们捕获命令的输出并以文本形式返回。目前这个版本没有错误处理,我们将在后续改进。

标准库的使用

整个脚本的一个关键特点是它只使用了Python标准库:

import sys
import subprocess
import json

这意味着只要系统安装了Python,就可以运行此脚本,无需管理额外的包依赖或复杂的打包过程,非常适合快速开发与分发。

运行示例

让我们在终端中测试这个脚本。假设脚本名为simple.py

  1. 首先,列出所有设备以查看可用名称:
    lsblk
    
  2. 然后使用我们的Python工具查询特定设备,例如sdb
    python simple.py sdb
    
  3. 也可以查询子设备,如sdb1
    python simple.py sdb1
    
  4. 或者查询回环设备:
    python simple.py loop0
    

脚本将输出对应设备的JSON信息,例如大小和挂载点,这与lsblk的原始数据一致。

关键要点总结

本节课中我们一起学习了构建一个基础Python命令行工具的完整流程:

  1. 入口点:使用if __name__ == "__main__":来区分脚本是直接运行还是被作为模块导入。
  2. 参数获取:通过sys.argv列表获取命令行参数。
  3. 命令执行:利用subprocess模块调用并捕获系统命令的输出。
  4. 数据处理:使用json模块解析结构化的命令输出,并实现业务逻辑(如搜索匹配项)。
  5. 依赖最小化:仅依赖Python标准库,使得工具易于分发和运行。

这个简单的例子展示了Python在创建实用命令行工具方面的强大能力和便捷性。在后续课程中,我们将在此基础上增加错误处理、更复杂的参数解析等功能。

007:处理用户输入参数与选项

概述

在本节课中,我们将学习如何使用Python的click框架来构建一个更高级的命令行工具。我们将从创建一个简单的工具开始,然后通过引入包管理和框架来增强其功能,使其能够处理用户输入的参数和选项。我们将涵盖如何设置项目结构、使用虚拟环境、以及通过setup.py进行开发模式安装,最终实现一个功能完整的命令行工具。


项目结构与框架引入

上一节我们介绍了基础的命令行工具概念。本节中,我们来看看如何通过引入框架和优化项目结构来提升工具的可维护性和功能性。

我们正在开发一个名为akamelan的简单工具,它基于AliceS block项目。为了使其功能更加强大,我们将使用click框架。使用框架的好处在于,它能自动为我们处理许多高级功能,例如生成帮助文档和解析复杂的命令行参数。

首先,我们创建一个名为blk_pi的目录,并将主要代码放入其中的main.py文件。这种模块化的结构为未来添加更多复杂功能提供了良好的起点。

以下是main.py的初始代码结构,它使用了click的装饰器来增强函数功能:

import click

@click.command()
@click.argument('device')
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output.')
def main(device, verbose):
    """A simple command line tool."""
    click.echo(f'Device: {device}')
    click.echo(f'Verbose: {verbose}')

if __name__ == '__main__':
    main()

代码解释

  • @click.command():将main函数转换为一个命令行命令。
  • @click.argument('device'):定义一个名为device的位置参数。
  • @click.option('--verbose', '-v', is_flag=True, ...):定义一个名为verbose的选项(标志),-v是其简写形式,is_flag=True表示它是一个布尔值开关。

使用装饰器可以轻松地为函数添加额外的功能,而无需深入理解其背后的复杂机制。


Python包管理与setup.py

为了有效地管理和分发我们的命令行工具,我们需要了解Python的包管理。我们将使用setuptools并通过setup.py文件来定义我们的项目。

Python有多种打包方式,我们将选择一种相对直接的方法,这种方法也便于将来将工具发布到Python包索引(PyPI)。

首先,我们需要一个setup.py文件。以下是该文件的内容:

from setuptools import setup, find_packages

with open('requirements.txt') as f:
    install_requires = f.read().splitlines()

setup(
    name='blk-pi-demo',
    description='A demo command line tool built with click.',
    packages=find_packages(),
    author='Your Name',
    entry_points={
        'console_scripts': [
            'blkpi=blk_pi.main:main',
        ],
    },
    install_requires=install_requires,
    version='0.1.0',
    url='https://github.com/yourusername/blk-pi-demo',
)

setup函数参数解释

  • name:项目名称,也是在PyPI上注册的名称。
  • description:项目的简短描述。
  • packages:使用find_packages()自动发现项目中的包。
  • entry_points:这是关键部分。它定义了如何创建命令行可执行文件。console_scripts下的blkpi=blk_pi.main:main表示:创建一个名为blkpi的命令,该命令会执行blk_pi.main模块中的main函数。
  • install_requires:指定项目依赖,我们从requirements.txt文件读取。
  • version:项目版本号(必需)。

接下来,我们需要一个requirements.txt文件来声明依赖:

click==7.1.2

这个文件列出了项目所需的第三方库及其版本。每行一个依赖。


开发环境搭建与工具测试

现在,我们将把所有部分组合起来,在开发环境中安装并测试我们的工具。

以下是设置开发环境的步骤:

  1. 创建虚拟环境:在项目根目录下,运行python -m venv .venv来创建一个独立的Python环境。
  2. 激活虚拟环境
    • 在Linux/macOS上:source .venv/bin/activate
    • 在Windows上:.venv\Scripts\activate
  3. 以开发模式安装包:运行python setup.py develop。这与pip install .不同,develop模式会创建链接,允许你直接修改源代码并立即生效,而无需重新安装。
  4. 验证安装:激活虚拟环境后,运行which blkpi(或where blkpi on Windows)应能显示该命令的路径。

完成以上步骤后,你就可以使用blkpi命令了。尝试运行blkpi --help,你会看到click框架自动生成的帮助文档。

Usage: blkpi [OPTIONS] DEVICE

  A simple command line tool.

Options:
  -v, --verbose  Enable verbose output.
  --help         Show this message and exit.

现在,让我们测试工具的功能:

  • 运行blkpi /dev/ttyUSB0,输出将显示设备名称和VerboseFalse
  • 运行blkpi /dev/ttyUSB0 --verbose(或-v),输出中的Verbose将变为True

这演示了如何通过框架轻松处理位置参数和布尔标志。由于我们处于开发模式,如果你修改了main.py中的代码(例如更改输出信息),更改会在下次运行命令时立即反映出来,这对于快速迭代开发非常有用。


总结

本节课中我们一起学习了如何使用click框架构建更强大的Python命令行工具。我们首先通过引入框架和模块化目录结构来组织项目。然后,我们深入了解了如何使用setuptoolssetup.py文件来管理项目依赖和定义命令行入口点。最后,我们搭建了虚拟开发环境,并以开发模式安装工具,实现了代码的实时修改和测试。你现在已经掌握了创建、打包和开发Python命令行工具的基础工作流程。在后续课程中,我们将在此基础上添加错误处理、更多类型的选项和参数等功能。

008:通过模块和库扩展工具功能 🧩

在本节课中,我们将学习如何通过模块和库来扩展命令行工具的功能,使其结构更清晰、更易于维护和测试。


概述

到目前为止,我们已经介绍了创建命令行工具所需的几个要素。之前我们学习了如何使用 click 库。现在,我们将开始通过模块来扩展工具的功能。这就是为什么拥有一个包含模块的目录结构非常有用。

重构主模块

上一节我们介绍了命令行工具的基本结构,本节中我们来看看如何通过模块化来优化代码结构。

我们当前有一个 main.py 文件,其中包含一些可以放到其他地方的代码。之前提到过,我们可以使用 Linting 和其他扩展功能。我们安装了 Python 扩展,如果将鼠标悬停在某些波浪下划线上,会看到一些建议。例如,这里提示“缺少函数或方法字符串”。因此,最好进行一些调整。

如果我在导入部分执行某些操作,它会提示我缺少模块 dostream。这些提示你可以选择添加或忽略,如果你确信实际上并不需要它们。

接下来我们要做的是:让这个 main.py 文件变得更小,将部分代码移出去。我们将进入包目录,添加一个新文件。如果你有多种不同类型的实用工具和想要添加的内容,你完全可以把它们放在其他地方。但这里我们将放在此处。

我将创建一个名为 utils.py 的文件,目前它是空的。现在,我们将对这个文件进行一些“手术”,看看如何让它变得非常小。我将选择所有这些内容,把它们移走。你会看到 Linter 开始报错,但不用担心。

以下是需要移动的步骤:

  1. main.py 中剪切选定的代码块。
  2. 将其粘贴到新的 utils.py 文件中。
  3. 由于 jsonsubprocess 等模块未导入,会出现问题,这很正常。
  4. 回到 utils.py 文件顶部,粘贴必要的导入语句。
  5. 保存文件后,红色的波浪下划线消失了。

现在,这个 utils.py 模块已经准备就绪。

创建 __init__.py 文件

如果我们回到 main.py,会看到 run_lsblk 函数带有红色下划线,提示未定义。我们需要在目录中添加一个特殊的文件。在 blkpi 目录下,我们需要添加一个新文件,名为 __init__.py

这个文件的作用是允许我们将其作为一个标准的 Python 包来使用。我们不需要在里面放任何东西,但它能让我们进行常规的导入操作。我强烈建议你这样做。如果你创建的是另一种称为“命名空间包”的包,可能不一定需要这个文件。但对于命令行工具和常规的 Python 开发,我们将专注于这个用例。

所以,我们保留这个文件。下一步是回到 main.py,然后输入:

from blkpi.utils import run_lsblk

自动补全功能之所以有效,是因为 VSCode 在后台理解到这是一个标准的 Python 模块,你可以使用导入、模块和函数。现在它运行正常,看起来没问题。

我们在这里看到一个关于“缺少函数或方法字符串”的提示,这对于我们正在尝试做的事情并不重要。如果我们添加一个小文档字符串,比如“CLI 的主入口点”,保存后,Linter 就会满意。现在我认为我们已经准备好了。

测试模块化后的工具

现在我们已经实现了模块化,让我们看看 utils.py。我们有两个函数,这看起来很不错。现在需要测试一下。

我将打开一个终端,然后看看我们的工具是否还在。输入 blkpi,发现它不存在。这是因为我们没有激活虚拟环境。这是你需要始终注意并保持一致的事情。你应该经常检查你期望的东西是否存在。

当我输入 which blkpi 时,可以看到它现在显示出来了,很好。清屏后,输入 blkpi --help,显示正确。

现在让我们试试是否能够运行 blkpi lsblk。我们没有遇到任何问题,之前的更改仍然有效,可以看到它运行得相当好。输入 blkpi lsblk /dev/sda1 也能工作。所以现在一切正常。

模块化的优势

当我关闭这些窗口后,情况变得非常好。现在代码更加模块化,我们有了一个 utils.py 文件,甚至可以进一步提取更多内容。例如,所有这些父设备、子设备的逻辑,我们可以创建一个单独的函数,并继续添加更多辅助函数。

这有助于提高代码的可读性和可测试性。这些函数将对测试更加友好。我们还没有涉及测试,稍后会进行讲解,但这是一个非常扎实的起点。

我们添加了几个东西:__init__.py 文件,创建了 utils.py 模块,并修改了 main.py。现在看看 main.py,它看起来非常好,只有大约 15 行长。这非常棒,因为我们只专注于特定功能,这已经非常非常好了。

总结

本节课中,我们一起学习了如何通过创建模块和 __init__.py 文件来扩展和优化命令行工具的结构。这使得 main.py 非常简洁,仅为约15行代码,是一个很好的起点,便于你添加更多模块,并为后续的测试工作奠定了良好的基础。这种结构在测试时也会很有帮助,因为我们可以更好地隔离功能。想象一下,如果所有代码都在主函数里,那么只想测试其中一小部分(比如 run_lsblk 函数)就会非常困难。

009:管理输出、日志、错误与异常 🛠️

在本节课中,我们将学习如何为命令行工具添加有效的错误处理机制。我们将从基本的异常捕获开始,逐步改进错误信息的呈现方式,最终实现一个既能提供友好用户提示,又能为开发者输出详细调试信息的健壮系统。


目前,我们的小型命令行工具尚未进行任何错误处理。我们的 main 函数盲目地运行,完全信任 run_lsblk 函数能够毫无问题地执行。

但在某些情况下,我们可能会遇到麻烦。回顾我们的工具,可以看到我们调用了 lsblk -J 并传递了一些参数。这通常没问题。现在,让我们模拟一个 lsblk 未安装在系统中的问题。我将通过传递一个不存在的命令来实现这一点。

在终端中,如果我运行 plot pi sdd1,会得到一个异常回溯。异常类型是 FileNotFoundError,错误信息显示“没有那个文件或目录:llsp 或 llblk”。对于用户来说,看到这样的信息可能非常令人困惑且没有帮助。我们通常会说,工具在终端上“崩溃”了。这并不理想。

因此,让我们尝试改进这个输出,使其变得更好。


第一步:捕获特定异常

首先,我们需要处理异常。我们可以在 main.py 中使用 try...except 语句。

开发者有时会直接捕获所有异常(except Exception),然后简单地打印“错误”。但这样做并不有用,因为我们不知道具体是什么错误。

更好的做法是明确地捕获我们预期的特定异常。例如,我们预计 lsblk 未安装会导致 FileNotFoundError。这样我们可以向用户传达更有用的信息。

以下是改进后的代码结构:

try:
    # 尝试运行主要逻辑
    run_lsblk(...)
except FileNotFoundError as error:
    # 处理文件未找到的错误
    print(f"错误:可能是 lsblk 未安装。详情:{str(error)}")

这样,当 lsblk 未安装时,用户会看到一个更清晰的提示,而不是一长串的 Python 回溯信息。


第二步:美化错误输出

为了使错误信息对用户更友好,我们可以使用 click 框架的样式功能来美化输出。click.echo 类似于 Python 的 print,但能更安全地在不同平台上工作。click.style 可以为文本添加颜色。

我们可以将错误信息设置为红色,使其在终端中更醒目。

以下是使用 click 美化输出的示例:

import click

try:
    run_lsblk(...)
except FileNotFoundError as error:
    # 使用红色字体输出主要错误信息
    click.echo(click.style("错误:可能是 lsblk 未安装。", fg='red'), err=True)
    # 输出原始的异常信息
    click.echo(str(error), err=True)

这样,用户会先看到一个红色的醒目错误提示,然后是具体的错误详情。


第三步:根据详细模式控制输出

我们的工具支持一个 -v--verbose 标志。我们可以利用这个标志来控制错误信息的详细程度。

  • 当用户不启用详细模式时,只显示简洁、友好的错误提示。
  • 当用户启用详细模式时,则打印完整的异常回溯信息,便于开发者调试。

这可以通过一个条件判断来实现:

import traceback

try:
    run_lsblk(...)
except FileNotFoundError as error:
    if verbose:
        # 详细模式:打印完整回溯
        traceback.print_exc()
    else:
        # 非详细模式:打印友好提示
        click.echo(click.style("错误:可能是 lsblk 未安装。", fg='red'), err=True)
        click.echo(str(error), err=True)

现在,用户可以通过是否添加 -v 标志来选择看到的信息量。普通用户得到清晰的指引,而开发者或需要调试时可以获得完整的错误上下文。


总结

本节课中,我们一起学习了如何为命令行工具构建分层的错误处理系统。

  1. 我们从捕获特定的 FileNotFoundError 异常开始,提供了比通用异常更有用的信息。
  2. 接着,我们利用 click 库美化了错误信息的呈现方式,使其对终端用户更友好。
  3. 最后,我们结合工具的 --verbose 标志,实现了错误信息详细程度的可控输出,兼顾了普通用户的体验和开发者的调试需求。

通过这些步骤,我们显著提升了工具的健壮性和用户体验。

010:概述

在本课程中,我们将学习如何开始使用Python构建命令行工具,并介绍一些最佳实践。我们将从最简单的无依赖版本开始,逐步过渡到使用更复杂的框架,并涵盖开发环境设置、模块化、依赖管理等关键主题。

Python命令行工具入门与最佳实践:2:选择合适的库

上一节我们介绍了课程概述,本节中我们来看看如何为命令行工具选择合适的库。

选择正确的库取决于您最终想要实现的功能。您可能需要使用一个框架,这通常要求您使用Python的打包工具链。Python的打包机制相当复杂。标准库中提供了optparseargparse等选项,但这些框架较为复杂,我们并未深入探讨。

我的建议是使用click框架。需要注意的是,click框架需要单独安装,因为它不属于标准库。因此,这有利有弊。我认为Python的click框架非常出色,您绝对应该尝试一下。我们展示的关于打包和安装库的技术也值得学习。

如果您需要处理子命令,click框架在这方面表现尤为突出。我们目前只看到了click框架中一些基本的参数和选项用法。但当您开始处理更复杂的领域(例如我们稍后将看到的子命令)时,click框架能让您更轻松地实现。

另一个方面是,使用框架可以显著简化工具的创建过程。我们看到了帮助菜单、标志和选项的自文档化、输入值的处理以及一些错误处理。一些更高级的功能(我们稍后会讲到)可以自动完成所有这些错误检查。例如,如果您想将字符串转换为整数,在使用基本参数解析时,所有内容最终都是字符串,但像click这样的框架可以在幕后将这些值强制转换为整数或其他类型的值。

所有这些click框架的功能将帮助您创建更强大的命令行工具,并方便地将click框架的各个部分集成到您的工具中。

Python命令行工具入门与最佳实践:3:设置开发环境

上一节我们讨论了如何选择合适的库,本节中我们来看看如何为Python命令行工具设置一个良好、彻底的开发环境。

设置一个完善的开发环境对于构建Python命令行工具至关重要。您可以使用任何您偏好的工具。如果您使用的文本编辑器与我展示的不同,这完全没问题。我更喜欢Visual Studio Code,因为它功能丰富、免费,并且拥有大量特性。我建议安装一些扩展,例如Python扩展和一些Linting工具。您看到我在某些时候遇到了麻烦,安装这些扩展是值得的。如果您不使用Visual Studio Code,请务必探索您的文本编辑器可能提供的类似功能,以便在开发时获得同样的帮助。

我们看到的另一件事是在命令行工具中处理用户输入。这非常有用,但您需要考虑要公开哪些选项来定制工具的行为。在本例中,我们设置了一个布尔标志来增加输出的详细程度,这绝对是有用的功能。随着您为命令行工具构建更多功能,您将接触到更多此类选项。

Python命令行工具入门与最佳实践:4:扩展功能与模块化

上一节我们介绍了开发环境的设置,本节中我们来看看如何扩展工具的功能并进行模块化。

当您想要开始扩展工具的功能时,可能会开始使用模块。您可能不希望将所有辅助函数或其他想要重用的额外代码都放在一个单独的文件中。我确实有过处理超过1000行的单个Python文件的恐怖经历,这非常难以处理。我们可以看到,每当我们将逻辑分离到其他模块中时,我们都能使主main.py模块更具可读性。这是使用模块的好处之一。另一个好处是,当您想要扩展功能时,您可以轻松、直接地做到这一点,并且很容易理解某些文件和代码应该放在哪里。

例如,如果您想创建异常,可以有一个exceptions模块,那么这些异常放在那里就很合理。我们当然还可以继续添加更多功能,比如日志记录和其他配置能力。

Python命令行工具入门与最佳实践:5:管理依赖与虚拟环境

上一节我们讨论了功能扩展,本节中我们来看看如何管理依赖并使用虚拟环境。

当您想更进一步时,因为在click的情况下,您已经在安装依赖项并创建用于分发的工具包,您可以继续添加更多库以实现更复杂的工具功能。例如,如果您想进行网络请求,Python中一个流行的HTTP请求库是requests库。如果您想让您的工具更容易地发送HTTP请求并处理这些请求,那么您绝对可以做到这一点。

另一件事是,我们需要稍微谈谈使用虚拟环境管理依赖。这对于设置您的环境和处理这些依赖至关重要。我们看到了Python setup.py develop的一些用法,它允许您在开发时对工具进行更改并尝试可执行文件,这是一种非常强大的开发方式。

请务必始终使用虚拟环境。检查您是否在虚拟环境中,并且没有将库安装到不同的位置。这绝对至关重要。在创建、开发的整个过程中,甚至在您尝试发布命令行工具时,这都是您必须做的事情。稍后,当我们进入持续集成和持续交付,并尝试自动化这些工具的发布时,这也是您必须牢记的事情。

Python命令行工具入门与最佳实践:6:总结

在本节课中,我们一起学习了使用Python构建命令行工具的入门知识和一些最佳实践。

我们涵盖了从选择合适库(特别是推荐使用click框架)到设置高效开发环境的全过程。我们探讨了如何通过模块化来组织代码、扩展工具功能,并深入了解了使用虚拟环境管理依赖的重要性。这些步骤和原则是构建可维护、可扩展且专业的命令行工具的基础。

011:使用Rust构建命令行工具 🛠️

在本节课中,我们将学习如何使用Rust语言来构建一个命令行工具。我们将从一个最基础的例子开始,逐步理解在Rust中开发命令行工具的含义和方法,并最终构建一个实用的工具。

概述

我们将从最简单的用例和方法入手,逐步过渡到使用框架来构建Rust命令行工具。过程中会涉及如何安装依赖、Rust生态系统如何运作,并可能将其与Python进行简要对比。Rust拥有许多超越Python的实用特性和优势,这也是为什么学习一些Rust知识至关重要。最终,你将得到一个性能优异、可用于真实世界系统运维或DevOps场景的工具。这个工具将驻留在系统中,解析输出,并以不同的方式再次呈现信息。

现在,让我们看看如何用Rust来实现这一切,并最终拥有一个可用的工具。


上一节我们介绍了本课程的目标,本节中我们来看看具体的实现路径。

我们将遵循一个循序渐进的学习过程:

以下是我们的学习步骤:

  1. 从基础开始:理解在Rust中开发命令行工具的基本概念和外观。
  2. 逐步深入:从最简单的用例和方法开始构建。
  3. 引入框架:学习如何使用框架来更高效地构建工具。
  4. 管理依赖:了解如何安装依赖以及Rust的生态系统如何工作。
  5. 对比分析:可能将Rust的方法与Python进行简要比较,理解Rust的优势。

本节课中我们一起学习了使用Rust构建命令行工具的路线图。我们从理解基础概念开始,规划了从简单实现到使用框架、管理依赖的逐步学习路径,并认识到Rust在构建高性能系统工具方面的价值。

012:搭建命令行开发环境 🛠️

在本节课中,我们将学习如何搭建一个用于开发命令行工具的Rust开发环境。我们将介绍必要的安装步骤、推荐的工具和扩展,以及如何使用Cargo工具链来格式化、检查和构建你的代码。


概述

与Python不同,Rust需要一个特定的开发环境。安装Rust仅对开发环境是必需的。一旦你构建并发布了二进制文件,目标系统上就不需要安装Rust。这是使用Rust构建命令行工具的一个优势,因为你无需在目标系统上安装Rust环境。

上一节我们介绍了Rust的优势,本节中我们来看看如何具体搭建开发环境。


安装Rust

安装Rust推荐使用rustup工具。以下是在终端中安装rustup的命令:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

执行此命令后,它会创建几个目录:

  • ~/.rustup
  • ~/.cargo

cargo目录将包含一个bin目录,该目录会被添加到你的系统PATH环境变量中,以便你可以访问Rust工具链。

注意:即使你使用的是Linux系统,并且系统包管理器提供了Rust,也建议使用rustup进行安装,以确保获得最新版本和最佳体验。


配置开发环境

代码编辑器与扩展

推荐使用Visual Studio Code进行Rust开发。以下是需要安装的重要扩展:

以下是推荐的VS Code扩展:

  1. Rust Analyzer:提供代码补全、类型提示、文档悬停提示、查找引用和错误检查等功能。
  2. GitHub Copilot(可选):提供AI辅助的代码建议,可以帮助你更快地编写Rust代码,尤其适合从Python转来的开发者。

认识Rust项目结构

一个典型的Rust命令行工具项目结构如下:

my_project/
├── Cargo.toml          # 项目配置和依赖定义
├── Cargo.lock          # 锁定依赖版本
├── .gitignore          # Git忽略文件
├── src/
│   └── main.rs         # 程序主入口文件
├── target/             # 编译输出目录(自动生成)
└── examples/           # (可选)示例代码目录
  • Cargo.toml:类似于Python的pyproject.tomlsetup.py,用于定义项目元数据和依赖。
  • Cargo.lock:由Cargo自动生成,用于锁定所有依赖的确切版本,确保可重复构建。
  • src/:存放所有Rust源代码文件。
  • target/:存放编译过程中生成的所有文件(二进制文件、中间文件等)。

使用Cargo工具链

Cargo是Rust的构建系统和包管理器。我们将使用它的三个核心命令来保证代码质量。

1. 代码格式化:cargo fmt

cargo fmt(或cargo fmt)命令可以将你的代码自动格式化为Rust社区的标准风格。

例如,如果你的代码格式混乱,运行以下命令可以将其规范化:

cargo fmt

2. 代码检查与建议:cargo clippy

cargo clippy是一个强大的代码检查工具,它能发现代码中的常见错误、提供改进建议和性能提示。

运行以下命令进行检查:

cargo clippy

它会输出建议。例如,它可能建议你将&str替换为char来进行分割操作。你可以根据这些建议修改代码,然后再次运行cargo clippy来确认问题已解决。

3. 快速编译检查:cargo check

cargo check命令会快速检查你的代码是否能通过编译,而无需生成最终的二进制文件。这比完整编译要快得多,适合快速验证代码的正确性。

cargo check

如果你在代码中引入了一个错误(例如,在不该加分号的地方加了分号),cargo check会立即报告错误。同时,VS Code中的Rust Analyzer扩展也会实时用红色波浪线标出错误位置,并将鼠标悬停其上可以查看详细错误信息。


总结

本节课中我们一起学习了如何搭建一个高效的Rust命令行工具开发环境。主要内容包括:

  1. 使用rustup安装Rust工具链。
  2. 配置Visual Studio Code及其扩展(Rust Analyzer和GitHub Copilot)。
  3. 认识了标准的Rust项目结构及其核心文件。
  4. 掌握了三个关键的Cargo工作流命令:cargo fmt(格式化)、cargo clippy(代码检查)和cargo check(快速编译检查)。

通过这个环境,你可以更高效、更规范地开发Rust项目,并利用工具快速发现和修复问题。

013:你的第一个Rust命令行工具 🛠️

在本节课中,我们将学习如何创建一个简单的Rust命令行工具。我们将从环境准备开始,逐步构建一个能够列出块设备信息的工具,并了解Rust项目的基本开发流程。

环境准备与工具安装

在开始构建我们的第一个命令行工具之前,我们需要确保开发环境已正确配置。我们将在一个Ubuntu 20.04 LTS环境中进行操作。

首先,我们运行以下命令来查看当前的操作系统信息:

cat /etc/os-release

接下来,我们安装Rust工具链。使用rustup命令可以方便地完成安装:

rustup install stable

安装过程会下载包括cargoclippy在内的多个组件。安装完成后,我们需要配置环境变量,以便在终端中直接使用rustccargo命令:

source $HOME/.cargo/env

现在,我们可以验证Rust是否安装成功:

which rustc
which cargo
rustc --version

如果一切顺利,你将看到类似rustc 1.68.2的版本信息。

问题定义与目标

我们的目标是创建一个lsblk命令的替代工具。在容器化环境中,标准的lsblk命令可能无法正确识别块设备。例如,运行以下命令可能会失败:

lsblk /dev/sdb
lsblk /dev/sdb1

为了解决这个问题,我们将利用lsblk的JSON输出格式。通过传递-J标志,我们可以获取结构化的设备信息,然后解析这些数据来提取所需内容:

lsblk -J

代码结构与解析

现在,让我们深入分析实现这个命令行工具的Rust代码。代码的主要入口点是main.rs文件中的main函数。

以下是代码的核心组成部分:

  1. run_command函数:这个函数负责执行外部命令。它接收一个命令字符串,将其按空格分割成向量,然后使用std::process::Command来执行。函数会捕获命令的标准输出,并以字符串形式返回。

    fn run_command(cmd: &str) -> String {
        let parts: Vec<&str> = cmd.split_whitespace().collect();
        let output = Command::new(parts[0])
            .args(&parts[1..])
            .output()
            .expect("Failed to execute command");
        String::from_utf8(output.stdout).unwrap()
    }
    
  2. run_lsblk函数:这个函数专门用于运行lsblk -J命令,并解析返回的JSON数据以查找特定的块设备信息。它目前使用panic!来处理错误,这在生产代码中是不推荐的,但对于示例来说是可以接受的。

  3. main函数:这是程序的入口点。它从命令行参数中获取设备名称,调用run_lsblk函数,并打印结果或处理错误。

项目构建与运行

我们将使用cargo来管理我们的Rust项目。首先,检查项目的依赖和代码是否有错误:

cargo check

我们的项目依赖一个名为serde_json的库来处理JSON数据。你可以在Cargo.toml文件中看到这个依赖项。

接下来,我们可以编译项目以生成可执行文件:

cargo build

编译完成后,可执行文件将位于target/debug/目录下,名称由Cargo.toml中的配置决定(例如block-rs)。我们可以直接运行它:

./target/debug/block-rs /dev/sdb

然而,更便捷的开发方式是使用cargo run命令,它会自动编译并运行程序:

cargo run -- /dev/sdb1

当前实现的局限性

虽然我们的工具已经可以工作,但它存在一些需要改进的地方:

  • 错误处理:代码中大量使用了expectpanic!,这会导致程序在遇到错误时突然崩溃。在生产环境中,我们应该使用更优雅的错误处理机制,例如Result类型。
  • 功能单一:目前工具只实现了最基本的功能,缺乏帮助菜单、参数验证等命令行工具常见的特性。
  • 代码结构:随着功能增加,代码可能需要更好的模块化组织。

在后续的课程中,我们将学习如何使用命令行解析框架(如clap)来增强工具的功能和健壮性。

总结

本节课中我们一起学习了如何从零开始创建一个Rust命令行工具。我们完成了环境配置,定义了一个实际问题(替代lsblk),并逐步实现了核心功能。我们了解了Rust项目的基本结构,学会了使用cargo checkcargo buildcargo run命令来编译和运行程序。虽然当前实现比较简单,但它为我们后续学习更复杂的Rust编程概念和工具开发打下了坚实的基础。

014:使用Clap框架处理命令行参数

在本节课中,我们将学习如何使用Rust的Clap框架,为我们的命令行工具添加参数解析、标志和帮助菜单生成功能。我们将从一个全新的项目开始,逐步构建一个更健壮的工具。

概述

上一节我们实现了一个简单的命令行工具。本节中,我们将进一步提升其健壮性,通过引入Clap框架来优雅地处理用户输入参数与选项。

从零开始创建项目

首先,我们需要创建一个新的Rust项目。我们将使用cargo init命令在当前目录初始化一个名为blockrs的二进制应用包。

cargo init --name blockrs .

执行后,Cargo会自动生成src目录、Cargo.toml文件,并更新.gitignore文件。让我们快速查看一下生成的Cargo.toml文件。

[package]
name = "blockrs"
version = "0.1.0"
edition = "2021"

[dependencies]

目前还没有任何依赖项。

添加项目依赖

接下来,我们需要为项目添加必要的依赖库。我们将使用clap框架来处理命令行参数,以及serde_json库来处理JSON数据。

以下是需要添加到Cargo.toml文件[dependencies]部分的内容:

clap = "2.33.3"
serde_json = "1.0"
  • clap 是我们新添加的框架,用于构建命令行界面。
  • serde_json 用于JSON数据的序列化和反序列化。

添加依赖后,我们可以回到src/main.rs文件查看初始代码。

初始代码与重构

cargo init生成的main.rs文件包含一个基础的main函数。为了与我们之前的功能保持一致,我们需要将原有的逻辑复制到这个新项目中。

在完成代码粘贴后,我们可以在终端中运行cargo build来确保所有依赖都能正确下载并编译。

cargo build

现在,我们的开发环境已经准备就绪。

引入Clap框架

之前,我们通过直接访问std::env::args()来获取命令行参数,这种方法比较原始。现在,我们将使用Clap框架来更优雅地处理参数。

首先,我们在main函数中设置Clap应用的基本信息。

let matches = clap::App::new("blockrs")
    .version("0.1.0")
    .author("Alfredo Deza")
    .about("Lists block devices in JSON format")
    .get_matches();

这段代码创建了一个名为blockrs的应用,并定义了版本、作者和简要描述。get_matches()方法会启动参数解析过程。

保存代码后,我们可以通过运行以下命令来测试基础的帮助菜单:

cargo run -- --help

注意,第一个--用于告诉Cargo,后面的参数是传递给我们构建的工具(blockrs)的,而不是给Cargo本身的。

添加命令行参数

我们的工具需要一个位置参数来指定设备名称(例如/dev/sda)。以下是添加该参数的方法:

let matches = clap::App::new("blockrs")
    .version("0.1.0")
    .author("Alfredo Deza")
    .about("Lists block devices in JSON format")
    .arg(
        clap::Arg::with_name("device")
            .index(1) // 第一个位置参数
            .required(true)
            .help("The block device to list (e.g., /dev/sda)"),
    )
    .get_matches();
  • clap::Arg::with_name("device") 定义了一个名为device的参数。
  • .index(1) 表示它是命令行中第一个位置参数(索引0通常是程序名本身)。
  • .required(true) 表示该参数是必需的。
  • .help(“...”) 为该参数提供帮助文本。

再次运行cargo run -- --help,现在可以在帮助信息中看到新添加的device参数。

集成参数到业务逻辑

定义了参数后,我们需要在代码中获取它的值,并替换之前硬编码或简单获取参数的方式。

以下是更新后的核心逻辑部分:

// 从解析的 matches 中获取 device 参数的值
let device = matches.value_of("device").unwrap();

// 使用获取到的 device 值进行后续操作
// ... (例如,将其传递给列出块设备的函数)

我们使用matches.value_of(“device”)来获取用户为device参数输入的值。.unwrap()在这里用于直接获取值(后续可以改进为更优雅的错误处理)。

现在,我们的工具可以这样使用:

cargo run -- /dev/sdb
cargo run -- /dev/sdb1

如果忘记提供必需的device参数,Clap会自动显示清晰的错误信息,例如:“The following required arguments were not provided: ”。

总结

本节课中,我们一起学习了如何从零开始构建一个更专业的Rust命令行工具。

  1. 我们使用 cargo init 创建了新项目。
  2. 我们通过编辑 Cargo.toml 文件添加了 clapserde_json 依赖。
  3. 我们使用 Clap 框架定义了应用程序的基本信息(名称、版本、作者、描述)。
  4. 我们添加了一个必需的位置参数 device,并为其提供了帮助文本。
  5. 我们将参数解析逻辑集成到主函数中,替换了之前简陋的参数获取方式。

通过引入Clap,我们获得了自动生成的帮助菜单(--help)、版本信息(--version)以及健壮的参数验证和错误提示,这使得我们的工具更加用户友好和健壮。下一步,我们可以在此基础上进一步改进错误处理,并添加更多功能标志(flags)。

015:通过模块和库扩展工具功能 🧩

在本节课中,我们将学习如何通过模块和库来组织和扩展Rust命令行工具的功能。我们将把一个单一文件的项目拆分成更模块化的结构,以提高代码的可维护性和组织性。


概述

目前,我们的工具使用Rust实现了lsblk命令的功能。所有代码都位于main.rs文件中。虽然这可以正常工作,但随着项目增长,将所有代码放在一个文件中会变得难以管理。本节我们将学习如何通过创建库模块来更好地组织代码。

从单一文件到模块化结构

上一节我们介绍了在单一文件中实现功能的方法。本节中,我们来看看如何将代码拆分成模块。

在Python等语言中,将所有代码放在一个文件中是可以的,直到你希望更好地组织代码。Rust应用也是如此。我们将使代码更加模块化,从在src目录中添加内容开始,这是Rust应用中的常见做法。

首先,我们将添加一个lib.rs文件。通常,你至少会有main.rslib.rs。根据组织需求,你还可以拥有更多文件甚至包含更多模块和文件的子目录。我们将探索如何为当前项目进行模块化。虽然有很多不同选项,但我们将从最简单的开始:直接在这里添加一个lib.rs文件。

创建库模块

以下是创建库模块的步骤:

  1. main.rs中提取代码:回到main.rs,查看现有代码。我们有run_command函数和run_lsblk函数,以及main函数。我将保持main函数不变,选择main函数之外的所有内容并剪切。

  2. 粘贴到lib.rs:现在转到lib.rs文件,粘贴刚才剪切的所有内容并保存。此时,Rust分析器会显示警告,因为一些函数未被使用,这是正常的。

  3. main.rs中引用模块:回到main.rs,现在会遇到错误:“cannot find run_lsblk in this scope”。这是因为run_lsblk现在位于不同的模块中。如何解决呢?如果你记得应用程序的名称,可以打开Cargo.toml文件查看。我们的包名是blokrs。因此,在main.rs中,我们可以使用blokrs::来调用函数。

  4. 设置函数为公有:即使通过包名引用,我们可能仍会看到错误提示:“function run_lsblk is private”。这是因为当所有代码在单一文件main.rs中时,函数默认不是公开可用的。我们需要使其公开。转到lib.rs,找到run_lsblk函数,在其前面添加pub关键字将其设为公有。

  5. 测试运行:完成上述步骤后,回到main.rs,错误提示应该消失。现在可以切换回终端,运行cargo run命令进行测试。首次编译可能会稍慢,因为进行了更改。再次运行则会很快,因为Rust知道无需重新编译。

使用use声明提高代码清晰度

目前我们处于良好状态,拥有两个模块。你可以继续模块化,使代码随着项目增长而更加有序。这种最小化的模块化方式是一个好的起点,后续课程中我们将看到如何进一步扩展。

我们看到了如何使用包名blokrs直接调用run_lsblk。实际上,我们也可以在文件顶部定义run_lsblk,使其以我们期望的方式工作。这样做的方法是在顶部添加use声明,明确说明从另一个模块使用了哪些内容。例如,可以写use blokrs::run_lsblk;。然后,在代码中可以直接使用该函数,无需前缀。这样可以使代码更清晰。

同样,我们也可以对clap库这样做,以更清晰地展示我们在做什么。我个人更喜欢在顶部明确声明使用的外部依赖和内部模块,这有助于保持代码组织有序。我们可以写use clap::{App, Arg};,然后在代码中直接使用AppArg。这样更加有序,顶部明确列出了使用的内容,而输出调用部分的代码保持不变。

总结

本节课中,我们一起学习了如何通过模块和库来组织和扩展Rust命令行工具。我们从一个单一文件的项目开始,逐步将其拆分成main.rslib.rs两个模块,使代码结构更加清晰和可维护。通过使用pub关键字公开函数,以及利用use声明提高代码可读性,我们建立了一个良好的模块化基础。这是一个非常直接和入门级的方法,展示了如何在Rust命令行工具中组织代码,为将来构建更复杂的多文件项目奠定了坚实的基础。

016:管理输出、日志、错误与恐慌 🛠️

在本节课中,我们将学习如何改进命令行工具的错误处理。目前,我们的工具假设一切都会顺利进行,但如果出现问题,就会导致许多麻烦。我们将逐步修复这些问题,使工具更加健壮和用户友好。

概述

目前,我们的Rust命令行工具在遇到错误时会直接触发panic,这会导致程序崩溃并输出对用户不友好的信息。本节中,我们将学习如何优雅地处理错误,例如命令执行失败或数据解析问题,而不是简单地让程序崩溃。我们将通过返回空值、捕获并打印错误信息等方式来改进代码。

修复JSON序列化错误

上一节我们指出了代码中存在的多个panic点。首先,我们来处理当找不到设备时触发的panic。在run_lsblk函数中,如果blockdevices不存在,unwrap()会引发恐慌。

我们不希望程序因此崩溃。相反,返回一个空的JSON对象是更合理的做法。这样,任何使用此库的工具都能接收到一个明确表示“未找到”的结果,而不是遭遇一个中断。

以下是修改方法:

// 修改前:如果找不到会 panic
let devices = output["blockdevices"].as_array().unwrap();

// 修改后:返回一个空的JSON数组
let devices = output["blockdevices"].as_array().unwrap_or(&vec![]);

但更精确的做法是,当整个输出无效时,直接返回一个空的JSON对象。我们可以使用json!宏:

if output.is_empty() {
    return json!({});
}

经过此修改后,当指定设备(如sdb33)不存在时,程序将输出一个空的JSON对象{},而不是崩溃。这暗示着没有找到任何设备,对调用者更加友好。

处理命令执行失败

接下来,我们处理run_command函数中的错误。当前,如果命令执行失败(例如命令不存在),.expect()会导致程序panic并打印出一堆对终端用户无用的调试信息。

我们的目标是捕获这个错误,将其打印到终端(最好是标准错误输出stderr),然后让函数继续执行并返回一个空字符串,而不是让整个程序崩溃。这样可以将错误处理逻辑隔离在函数内部。

以下是实现步骤:

  1. 移除.expect()
  2. 使用match语句对Command::output()的结果进行模式匹配。
  3. 成功时,解析输出并返回字符串。
  4. 失败时,将错误信息打印到stderr,并返回一个空字符串。

修改后的核心代码如下:

pub fn run_command(cmd: &str, args: &[&str]) -> String {
    let output = Command::new(cmd).args(args).output();

    match output {
        Ok(output) => {
            // 命令执行成功,解析输出
            String::from_utf8_lossy(&output.stdout).to_string()
        }
        Err(e) => {
            // 命令执行失败,打印错误并返回空字符串
            eprintln!("命令执行失败: '{}' - 错误: {}", cmd, e);
            String::new()
        }
    }
}

整合错误处理逻辑

现在,我们已经分别处理了命令执行和JSON解析的错误。但是,当run_command返回空字符串(表示命令失败)时,后续的serde_json::from_str解析会再次失败。

因此,我们需要在run_lsblk函数中添加一个检查:如果run_command的返回结果是空字符串,则直接返回空的JSON对象。

pub fn run_lsblk(device: &str) -> Value {
    let output = run_command("lsblk", &["-J", "-o", "NAME,SIZE,TYPE,MOUNTPOINT", device]);

    // 检查命令是否返回了有效输出
    if output.is_empty() {
        return json!({});
    }

    // 尝试解析JSON
    let json_output: Value = serde_json::from_str(&output).unwrap_or(json!({}));
    json_output
}

此外,为了提供更多上下文,我们还可以改进错误信息:

eprintln!("命令失败: '{} {:?}' - 错误: {}", cmd, args, e);

现在,当运行一个无效命令时,用户将看到清晰的错误信息(例如“命令失败: ‘lsblk …’ - 错误: No such file or directory”),并且程序会输出{}而不是崩溃。

总结

本节课中我们一起学习了如何管理Rust程序中的输出、日志和错误。

  1. 避免不必要的panic:在生产级代码中,应谨慎使用panic,仅用于处理不可恢复的错误。
  2. 优雅的错误处理:使用Result类型和matchunwrap_or等方法来安全地处理潜在错误。
  3. 提供清晰的用户反馈:将错误信息打印到标准错误输出(stderr),并与正常输出(stdout)分离,方便用户和脚本处理。
  4. 保持函数健壮性:通过返回默认值(如空字符串、空JSON对象)来防止错误在调用链中向上传播。

通过这些改进,我们的命令行工具变得更加健壮和用户友好,能够更好地适应生产环境。

017:优化命令行工具性能与最佳实践 🚀

在本节课中,我们将探讨使用Rust构建命令行工具时的一些关键模式、最佳实践和性能优化建议。我们将对比Rust与Python等语言在构建工具时的差异,并介绍如何利用Rust生态系统的优势来创建高效、健壮的工具。


Rust与Python构建命令行工具的差异

上一节我们介绍了Rust构建命令行工具的背景,本节中我们来看看Rust与Python等语言在构建工具时的核心差异。

Rust是一种静态类型、编译型语言,而Python是一种解释型语言。这种根本差异导致了在构建和分发命令行工具时的不同做法。

  • 二进制分发:Rust编译后生成独立的二进制可执行文件。用户无需安装Rust工具链即可运行。公式表示为:rustc main.rs -> ./main
  • 单文件脚本:Python工具通常以脚本形式分发,需要用户系统安装Python解释器和相关依赖。代码表示为:python script.py

使用Rust的主要优势在于其极致的执行速度和便捷的部署方式,避免了目标系统环境配置的复杂性。


命令行工具框架选择

在Rust生态中,有多种框架可用于构建命令行工具。虽然我们从最基础的参数解析开始,但使用成熟的框架能带来显著好处。

以下是Rust中一些流行的命令行工具框架:

  • clap:功能强大、社区活跃,是本教程推荐使用的框架。它提供了参数解析、验证、帮助信息生成等完整功能。
  • structopt:基于clap构建,通过派生宏提供更声明式的参数定义方式。
  • quick-cli / quicli:旨在简化简单CLI工具的创建过程。

对于大多数项目,特别是需要复杂参数和子命令的工具,clap是一个可靠的选择。如果clap显得过于复杂,可以尝试structopt或quick-cli。


依赖管理的优势

使用Rust的另一个显著优势是其依赖管理方式,这与Python等语言形成对比。

在Rust中,通过Cargo管理的依赖会被静态链接到最终生成的二进制文件中。这意味着:

  1. 最终用户无需单独安装或管理这些依赖库。
  2. 解决了“依赖地狱”和跨平台、跨架构的编译兼容性问题。
  3. 分发时只有一个独立的可执行文件,简化了部署流程。

使用像clap这样的框架,你不仅能获得参数解析的功能,还能自动获得输入验证、错误报告、帮助菜单生成等“重型任务”的支持。


错误处理最佳实践

在构建健壮的命令行工具时,恰当的错误处理至关重要。Rust提供了多种处理错误的机制,与某些语言中通过异常“快速失败”的模式不同。

需要避免在代码中随处调用panic!。虽然panic!在示例或快速原型中很方便,但在生产级工具中,它会导致程序突然终止,无法提供友好的错误信息或进行清理操作。

我们应利用Rust的Result类型进行错误传播,并为用户提供清晰、具体的错误上下文。这样即使操作失败,用户也能明白原因,工具也能以可控的方式退出。核心模式是使用 Result<T, E>? 操作符进行错误传播。


开发工作流与工具

遵循Rust开发的最佳实践能显著提升代码质量和开发效率。这涉及到在开发流程中集成一系列工具。

以下是开发过程中应频繁使用的Cargo命令:

  • cargo fmt:自动格式化代码,保持风格一致。
  • cargo clippy:运行Clippy lint工具,捕捉常见错误和改进建议。
  • cargo check:快速检查代码能否编译,而不生成可执行文件,节省时间。

此外,为你的编辑器(如VS Code)安装rust-analyzer扩展至关重要。它能提供实时的代码补全、类型提示和错误检查,将这些反馈融入开发工作流可以持续改进代码质量。


工具的可扩展性与模块化

从处理单个命令行参数到使用完整框架,你的工具在处理用户输入时会变得更加灵活和健壮。框架能帮助你防范常见错误,并为未来功能扩展奠定基础。

随着工具功能增长,将其拆分为模块是很好的做法。我们之前通过添加lib.rs文件浅尝了模块化的方法。你可以创建更多模块来组织不同的功能域。

同时,引入外部库来处理复杂功能(如我们使用的clapserde_json)是非常直接且被鼓励的。通过Cargo.toml文件管理依赖非常简单。这与Python中需要谨慎选择兼容性依赖的情况不同。

需要注意的是,引入的库越多,项目的编译时间可能会相应增加。但在发布和分发时,Rust生成的二进制文件通常仍然非常紧凑,这是Rust的主要优势之一。


总结

本节课中我们一起学习了使用Rust构建命令行工具的核心要点。我们对比了Rust与Python的差异,强调了Rust在性能和分发上的优势。我们探讨了clap等框架的选择,以及Rust依赖管理带来的便利。在错误处理上,我们应避免滥用panic!,转而使用Result提供更好的用户体验。集成cargo fmtcargo clippy和rust-analyzer等工具能优化开发流程。最后,我们了解了通过模块化和引入外部库来保持工具的可扩展性,同时认识到Rust在最终二进制文件大小上的优势。掌握这些最佳实践,将帮助你构建出高效、健壮且易于维护的Rust命令行工具。

018:引言

在本节课中,我们将要学习如何更好地处理依赖关系。我们将分别探讨Python和Rust两种语言在这方面的应用。课程前半部分将聚焦于Python,学习如何构建模块以及在更复杂的场景下处理导入,而不仅仅是编写一个典型的单文件Python脚本。我们还将了解如何管理依赖,并介绍Python包索引(PyPI),这是发布和分发工具的平台。课程后半部分将转向Rust,我们将理解如何以更复杂的方式使用模块,超越简单的main.rslib.rs文件。最后,我们会介绍Crates.io,这是Rust的包注册中心,类似于Python的PyPI,用于发布和共享你的库或工具。

课程概述

上一节我们介绍了Python和Rust构建命令行工具的基础知识。本节中,我们来看看如何更有效地管理项目依赖,并构建更复杂的项目结构。

Python部分:模块、依赖与发布

现在我们已经了解了Python的基础知识,以及如何构建一些命令行工具(无论是否使用依赖项),Rust也是如此。

让我们看看如何更好地处理依赖关系。我们将同时探讨Python和Rust的用例。

因此,本节课将分为两半,先深入探讨Python,研究如何构建模块以及在更高级的场景下处理导入,而不是仅仅使用一个执行某些功能的单文件(即典型的Python脚本)。然后,我们还将学习如何处理依赖。

接下来,我们将了解Python包索引。本质上,如果你以后想分发你的工具,让用户能够使用,你需要知道如何将工具发布到Python包索引。

Rust部分:模块与Crates.io

然后,我们将对Rust进行同样的操作。我们将尝试以更复杂的方式理解模块的使用,不仅仅是简单的main.rslib.rs文件,而是更进一步,看看它是如何工作的。

之后,我们将了解Crates.io。这与Python包索引是同样的概念,是一个你可以发布你的工具、库或任何用Rust构建的东西的地方,以便与世界分享,让其他用户能够以非常直接的方式在他们的系统中安装使用。

总结

本节课中,我们一起学习了如何在Python和Rust中更有效地管理依赖和构建模块化项目。我们探讨了Python的模块系统、依赖管理以及通过PyPI发布工具。对于Rust,我们深入了解了超越基础文件的模块组织方式,并介绍了用于共享代码的Crates.io平台。掌握这些概念将帮助你构建更复杂、更易于分发和维护的软件项目。

019:使用模块和包组织Python项目 🐍

概述

在本节课中,我们将学习如何使用模块和包来组织Python项目。我们将通过分析一个成熟的生产级命令行工具项目结构,来理解如何有效地组织代码、处理异常、使用装饰器,以及掌握绝对导入和相对导入的技巧。这些知识对于构建可维护、可扩展的Python项目至关重要。

分析成熟项目的组织结构

我们之前已经了解过如何添加模块(即Python文件)到我们构建的工具中,但我们还没有接触过更复杂的结构。因此,我们将分析一个已经非常成熟、规模庞大的命令行工具项目。这个项目包含大量文件和子目录,是学习项目组织方式的绝佳范例。

这是一个实际的生产级Python命令行工具项目,代码规模庞大,包含多个不同的组件。我们可以看到这里有大量的子目录和相当多的Python模块。

即使你目前没有如此庞大的命令行工具需要构建,也可以借鉴这里的一些思路来实现你自己的工具。这是一个我们可以使用的坚实可靠的例子。

异常处理模块 🚨

首先,让我们看看exceptions模块。当你的项目中有多个异常,或者有可供其他模块使用的异常时,拥有一个专门的异常模块是一个好做法。

以下是该模块中的一些异常示例:

  • ConfigurationError:当配置不正确时可能发生。
  • ConfigurationSectionError
  • KeyError
  • ParsingError
  • SuperUserError
  • SizeAllocationError

这是一种很好的组织方式。当异常数量较多时,将它们集中在一个文件中是合理的。这个文件只有63行,可以让你轻松地了解所有异常情况。浏览60行Python代码并不困难,而且文件顶部只有一个导入语句,结构非常清晰。这是一种你可以轻松实现的组织方式。

装饰器模块 ✨

接下来,我想展示decorators模块。如果你开始构建装饰器(这是Python一个稍微高级的特性),可以看到这个项目是如何将项目的不同需求分离到独立模块中的。

例如,这里有一个needs装饰器,它似乎用于检查用户是否是超级用户(如root用户)。如果不是,它会抛出一个SuperUserError

我们之前看到了exceptions模块,这里它被导入并使用,抛出了SuperUserError。这是一种非常有趣的代码组织方式,绝对值得尝试。同样,这个模块可能不超过100行Python代码,并且包含大量文档,这很好。

始终探索各种命令行工具和项目,观察它们如何组织代码,是值得牢记的一点。

处理导入:绝对导入与相对导入

现在,让我们通过一些更实际的例子,来看看如何处理导入,以及如何进行绝对导入和相对导入。

为了搭建一个简单框架,我们只关注modules这个目录。假设这是你的项目,里面有一个__init__.py文件、一个constants.py文件和一个名为sub的子目录,子目录里有一个command.py文件,其中包含一个函数。

让我们探索在Python项目中工作时可能会遇到的一些情况。

探索导入行为

modules目录中,我们有__init__.pyup.pyconstants.py。让我们看看如果导入modules会发生什么。

导入modules后,你可以调用modules.__file__来查看它来自哪里。这非常有用,可以让你知道这个模块存在于文件系统的哪个位置。你也可以直接打印它,在交互式终端中会得到输出。

现在,假设你在modules中,导入了modules,并且想要访问sub子目录下的run函数。你可能会尝试modules.sub,但会得到错误提示:modules没有sub属性。

问题在于,我们有一个名为sub的子目录,里面有一个__init__.py文件,按理说应该可以工作,比如modules.sub.command.run()。但为什么不行呢?

这里有两种主要的处理方式。我们可以使用import modules.sub,然后通过modules.sub来访问。这样,导入系统就将sub作为modules的一部分了。

但接着,如果我们尝试modules.sub.command,又会发现没有command属性。如果你不确定如何继续,或者不想使用完整的导入语句,这会变得很烦人。

我们可以尝试import modules.sub.command,然后modules.sub.command.run()就能工作了。但如果你不想一直输入完整路径,有什么办法可以简化呢?

__init__.py中添加导入

我们可以在__init__.py文件中添加一些导入语句。进入modules目录下的__init__.py文件,尝试添加import sub

保存后,需要重启Python shell(因为我们在动态修改文件,需要重新加载)。然后尝试import modules,但会得到一个ModuleNotFoundError。为什么这不行呢?sub明明存在。

问题在于我们尝试进行绝对导入。这意味着sub应该被视为命名空间中一个独立的、绝对的模块。但它并不是一个独立的模块,它是这个名为modules的项目目录的一部分。

使用相对导入

为了解决这个问题,我们需要使用相对导入。具体做法是,在__init__.py中写from . import sub。这个点.表示“从这里”,即从当前路径、当前目录导入sub

让我们清除之前的尝试,重启Python,然后执行import modules。现在,modules.sub就能正常工作了。这是需要牢记的一点。

你可以灵活决定将哪些内容作为模块的一部分。这里的modules可以是你的项目、命令行工具、库、子模块或目录。你可以在里面放入任何你想要的东西。

导入常量并暴露到顶层

我们可以进一步扩展。例如,在constants.py中,我们可能有verboselog这样的常量。你可能会有上百个这样的常量,将它们组织在constants.py中是可以的,但你可能也希望其中一些能在库的顶层直接访问,而不仅仅是在constants模块内部。

你可以在__init__.py中这样写:from .constants import verbose, log。注意,这里我们再次使用了相对导入(.)。

让我们再次退出并重启Python解释器(因为文件已被修改)。然后执行import modules,现在modules.logmodules.verbose就可以正常工作了。

总结

本节课中,我们一起学习了如何组织Python项目。我们通过分析一个真实项目,了解了将异常集中管理、使用独立装饰器模块的好处。更重要的是,我们深入探讨了Python的导入系统,理解了绝对导入相对导入的区别与用法。记住使用from . import ...进行相对导入,是组织项目内子模块的关键。这些技巧将帮助你更好地组织你的命令行工具或其他Python模块,使项目结构更清晰、更易于维护。

020:处理Python依赖项与库 📦

在本节课中,我们将深入学习如何管理Python项目的依赖项,特别是通过requirements.txt文件。我们将探讨如何精确锁定依赖版本,以及如何灵活地指定版本范围,以适应不同的开发需求。


概述

我们已经初步了解了requirements.txt文件及其基本要求。然而,在处理这些文件时,还有更多细节可以深入探讨,使其用法更加清晰。本节将使用一个名为block_pi的Python CI示例项目进行演示,其requirements.txt文件目前仅包含一个依赖项。


精确锁定依赖版本

requirements.txt文件中,当您以click==7.1.2这样的格式声明依赖时,您正在执行锁定操作。这意味着click框架库将始终尝试安装版本7.1.2

这种做法非常可靠,但有时您可能需要更多灵活性,而不必严格指定确切的版本。


灵活指定版本范围

您可以放松版本限制。例如,将click==7.1.2改为click==7,这表示将安装任何主版本号为7的版本,但不会安装低于或高于7的版本。

为了验证环境,请确保您已激活虚拟环境。使用系统Python解释器时可能无法找到依赖。激活虚拟环境后,在Python中执行import clickprint(click),可以确认click库来自正确的虚拟环境路径。

让我们进行一些实际操作。首先清除当前环境,然后查看requirements.txt文件。使用pip install click命令,即使不修改requirements.txt文件,我们也可以进行一些测试。


验证已安装的包

在尝试新操作之前,另一种验证方法是使用pip freeze命令。该命令会列出当前虚拟环境中安装的所有包。

以下是使用pip freeze的示例:

pip freeze

此命令将输出当前环境中所有已安装包及其精确版本。


处理版本冲突

现在,让我们尝试安装click。执行命令pip install "click==7"。您可能会遇到一个错误,提示click==7不兼容。这是因为在虚拟环境中,我们的项目block_pi_demo已声明需要click==7.1.2

为了解决这个冲突,我们可以先卸载项目本身:

pip uninstall block-pi-demo

确认卸载后,再次运行pip freeze,会发现除了click==7外没有其他安装项。接着,卸载click

pip uninstall click

现在环境已清理干净。


指定版本范围约束

让我们重新开始,尝试更灵活的版本指定。执行pip install "click>=7.1.2"。您会发现,系统选择了版本8.1.3,因为它满足“大于等于7.1.2”的条件。

如果您不希望安装版本8,可以添加更多约束。再次卸载click,然后执行:

pip install "click>=7.1.2, <8"

现在,系统将匹配并安装版本7.2。这为您提供了灵活性。


使用requirements.txt文件安装

您还可以直接在requirements.txt文件中添加依赖。例如,添加一行click(不指定版本)。然后,使用以下命令根据该文件安装依赖:

pip install -r requirements.txt

由于没有指定精确版本(即没有使用==),pip将安装最新的可用版本,在本例中是8.1.3


开发与生产环境管理

以下是处理依赖时的一些关键考虑点:

  • 开发灵活性:在开发过程中,您可能希望尝试其他库或不同版本,而不与生产级或发布级命令行工具冲突。
  • 版本控制技巧:利用pip freeze生成精确的依赖列表,或使用宽松的版本约束来平衡稳定性和灵活性。
  • 环境隔离:始终使用虚拟环境来隔离项目依赖,避免全局Python环境混乱。

总结

本节课中,我们一起学习了如何通过requirements.txt文件管理Python依赖。我们探讨了精确锁定版本与灵活指定版本范围的方法,解决了可能出现的版本冲突,并了解了如何在开发和生产环境中妥善管理依赖。掌握这些技巧将帮助您更高效地构建和维护Python项目。

021:Python包索引(PyPI) 📦

在本节课中,我们将要学习Python包索引(PyPI)是什么,以及它如何作为Python生态系统中依赖包的主要来源。我们将了解如何从PyPI查找和安装包,并初步探索如何将自己的项目发布到PyPI上。


我们一直在安装和卸载依赖项,但这些依赖项来自哪里呢?它们来自Python包索引(PyPI)。Python包索引默认允许你访问这个包注册中心,并从那里获取这些包。当然,你也可以配置你的工具集指向内部的索引,例如,如果你不想公开你的包。但默认情况下,所有的包都来自这里。

如果我来这里搜索项目,例如搜索我们一直在使用的框架 click,会出现几个不同的结果。虽然会显示很多内容,但我们想查看的框架肯定是 click。有时开发者会遵循这种命名约定,以区分版本并避免向后兼容性问题。这就是我们想要的包。

项目描述包含了很多信息,所有这些都来自于Python的打包机制。我们之前只是浅尝辄止地介绍了Python打包,我知道这非常复杂,但我们会尽量保持简单明了。你可以看到安装过程同样直接,使用 pip 命令。-U 标志是 pip 的一个选项,它允许你更新你的 click 安装,或者你正在安装的任何库或框架。-U 表示“更新到最新版本”。这实际上意味着卸载旧版本,安装最新版本,然后继续。

这为什么有用呢?一旦你将项目发布到索引,你就能够与世界上任何地方的其他人协作和分发你的项目。一旦它在这里,任何人都可以安装它,这非常酷。

你可以看到发布历史。当前版本发布于2022年4月28日,所以它非常稳定,尽管已经有一段时间了。你还可以看到这里有一些预发布版本,你当然可以获取一些预发布版。对于 click 这个Python主要命令行工具框架之一,这里有非常详尽的发布历史。这里还有一堆项目链接,我们可以查看,比如主页和文档页面。

让我们返回。另一件我想向你展示的事情是,我在这里有一个账户。如果你想发布到Python包索引,你必须创建一个账户。我想在这里展示我的一些项目。让我们看看我的一些演示命令行工具,比如 csbli。这是你作为项目所有者会看到的视图。你可以在那边看到你的账户设置。让我们管理我的演示 csbli 工具。你可以看到我只有一个版本,发布于2022年。这里有安全历史、协作者等文件。如果你想让他人为你的项目做贡献,你也可以设置维护者。你可以看到那是我,我已经为自己设置好了一切。还有一些项目角色和基本信息。你可以设置某些配置,比如API令牌、各种不同的东西,还可以启用双因素认证。你甚至可以继续删除项目,但要明白这可能相当危险,因为很难恢复,并且这些东西将不复存在。

再次强调,创建一个账户对于我们即将涵盖的内容肯定是有用的,因为我们将能够尝试设置令牌。一旦我们想要尝试将一些包发布到Python包索引(我们在这门课程中肯定会做这件事),我们就会看到如何操作。

022:在Rust中创建和使用模块

在本节课中,我们将要学习如何在Rust中使用mod关键字来创建和组织模块。模块是Rust中管理代码结构、控制可见性的核心机制。我们将从基础定义开始,逐步探讨模块的嵌套、可见性控制,以及它与use关键字的区别。

模块基础定义

上一节我们介绍了模块的概念,本节中我们来看看如何使用mod关键字来定义一个模块。

mod关键字允许你创建并定义一个Rust模块。通过模块,你可以将相关的代码组织在一起,并控制其对外部的可见性。例如,在一个库文件(如lib.rs)中,你可以这样定义一个模块:

mod utilities {
    pub struct Color {
        pub red: u8,
        pub green: u8,
        pub blue: u8,
    }
}

在这个例子中,我们创建了一个名为utilities的模块,并在其中定义了一个公开的Color结构体。pub关键字用于控制可见性。如果移除pub,结构体及其字段将变为私有,无法在模块外部访问。

使用模块中的项

定义了模块之后,我们来看看如何在其他代码中使用它。

要使用模块中定义的项,你需要通过模块路径来引用它们。路径使用双冒号::作为分隔符。以下是如何在主函数中使用上述utilities模块中的Color结构体:

fn main() {
    let color = utilities::Color {
        red: 255,
        green: 0,
        blue: 0,
    };
    println!("Red: {}, Green: {}, Blue: {}", color.red, color.green, color.blue);
}

这里,utilities::Color表示从utilities模块中访问Color类型。这种方式让你能够清晰地组织代码,并通过模块路径来访问特定功能。

嵌套模块与代码组织

除了基础模块,Rust还支持创建嵌套模块,这有助于进一步组织复杂的代码结构。

你可以在一个模块内部定义子模块。例如,创建一个shapes模块,并在其中定义squarerectangle子模块:

mod shapes {
    pub mod square {
        pub fn area(side: f64) -> f64 {
            side * side
        }
    }

    pub mod rectangle {
        pub fn area(length: f64, width: f64) -> f64 {
            length * width
        }
    }
}

在嵌套模块中,你同样可以定义公开的函数或结构体。使用嵌套模块时,访问路径会相应变长:

fn main() {
    let square_area = shapes::square::area(5.0);
    let rect_area = shapes::rectangle::area(4.0, 6.0);
    println!("Square area: {}, Rectangle area: {}", square_area, rect_area);
}

通过嵌套模块,你可以将代码逻辑划分得更加细致,提高代码的可读性和可维护性。

moduse关键字的区别

在组织代码时,你可能会遇到moduse两个关键字,理解它们的区别很重要。

mod关键字用于定义一个新的模块。它创建了一个包含代码、类型、函数或结构体的命名空间。

use关键字则用于将模块中的项引入当前作用域,从而简化路径书写。例如,不使用use时,你需要写完整的路径:

let color = utilities::Color { ... };

使用use关键字后,你可以直接使用项的名称:

use utilities::Color;

fn main() {
    let color = Color { ... };
}

以下是两者的核心区别总结:

  • mod:定义和组织代码结构。
  • use:引入路径到当前作用域,避免重复书写长路径。

总结

本节课中我们一起学习了Rust模块系统的核心知识。我们首先使用mod关键字来定义模块,并了解了如何通过pub控制可见性。接着,我们探讨了如何使用模块路径来访问其中定义的项。然后,我们介绍了如何创建嵌套模块来更好地组织复杂代码。最后,我们区分了mod(用于定义模块)和use(用于引入路径)这两个关键字的用途。掌握这些概念,能够帮助你编写出结构清晰、易于维护的Rust代码。

023:高级模块使用指南 🧩

在本节课中,我们将学习Rust中模块系统的一个高级特性:如何将代码组织到独立的文件中,而非全部放在默认的 lib.rs 中。我们将了解如何声明和引用这些外部模块,以及控制其可见性(公开或私有)。

到目前为止,我们的所有模块定义都位于 lib.rs 文件中。lib.rs 在Rust中是一个特殊的文件,其中声明的所有内容都会自动成为包的一部分。但是,如果我们希望将代码放在其他文件中,例如 utils.rs,该如何操作呢?

从其他文件导入模块

以下是如何从 utils.rs 这样的外部文件导入模块的步骤。

首先,假设我们在 src/utils.rs 文件中定义了一个模块:

// 文件:src/utils.rs
pub fn example() {
    println!("This is an example from utils.");
}

如果我们直接在 main.rslib.rs 中使用 use crate::utils;,编译器会报错,提示“unresolved import”。这是因为,与某些语言(如Python)不同,Rust不会自动引入项目中的所有文件。

lib.rs 中声明模块

要让Rust识别 utils.rs 文件,我们需要在 lib.rs 中显式声明它。这是最直接的方法。

lib.rs 文件中,添加以下声明:

// 文件:src/lib.rs
mod utils;

这行代码告诉Rust编译器:“请将 utils.rs 文件的内容作为一个模块引入到当前包中”。声明之后,我们就可以在库内部使用这个模块了。

模块的可见性与使用

模块的可见性由其声明和内部项的 pub 关键字控制。

  • 在库内部使用:即使在 utils.rs 中函数没有标记为 pub(即它是私有的),我们仍然可以在 lib.rs 内的其他函数中使用它,因为它们在同一个包内。

    // 在 lib.rs 的某个函数内
    fn some_function() {
        utils::example(); // 可以调用,因为同属一个crate
    }
    
  • 对外部代码公开:如果我们希望其他crate(比如在 main.rs 中)也能使用 utils 模块,需要做两件事:

    1. lib.rs 中,使用 pub mod utils; 将其公开声明。
    2. utils.rs 中,将需要公开的函数或结构体标记为 pub
    // 文件:src/lib.rs
    pub mod utils; // 公开声明模块
    
    // 文件:src/utils.rs
    pub fn example() { // 公开函数
        println!("Public example.");
    }
    
    // 文件:src/main.rs
    use your_crate_name::utils; // 现在可以从外部引用了
    fn main() {
        utils::example();
    }
    

核心要点总结

本节课我们一起学习了Rust模块系统的高级用法,主要涵盖以下两点:

  1. 文件即模块:在Rust中,每个 .rs 文件都可以被视为一个模块,但必须通过 mod 关键字在父模块(通常是 lib.rs)中声明,编译器才会将其纳入编译。
  2. 可见性控制:使用 pub 关键字精细控制模块、函数、结构体等是否对包外代码可见。在 lib.rs 中声明为 pub mod 是模块对外公开的必要步骤。

记住,仅仅把代码放在不同的文件里并不会自动生效,必须在 lib.rs(或通过模块树向上追溯的某个文件)中进行声明。通过 mod 声明引入,再通过 pub 控制暴露范围,你就能灵活地组织大型Rust项目的代码结构了。

024:处理Rust依赖项与库

概述

在本节课中,我们将学习如何使用Cargo创建和管理Rust库,以及如何更精细地控制项目依赖项。我们将探讨二进制包与库的区别,并深入了解在Cargo.toml文件中指定依赖项的各种方法。

创建库与二进制包的区别

到目前为止,我们已经了解了如何使用Cargo创建二进制包。然而,您可能还需要创建库,以便发布并在其他二进制项目间共享代码,从而避免重复编写相同功能。

让我们看看它们之间的区别。默认情况下,当我们使用cargo new cli命令时,会创建一个二进制应用程序包。但我们可以使用cargo new --lib命令来创建库。

主要区别在于输出类型从二进制变为库。具体来说,在源代码结构中,二进制包会包含src/main.rs文件,而库则包含src/lib.rs文件。库没有main函数或程序入口点,因为它的组件将被其他项目复用。

配置Cargo.toml文件

除了项目类型,我们还需要讨论依赖项的配置。我们已经见过基本的依赖项列表写法,例如依赖项名称 = "版本号"。但还有更多配置选项值得了解。

值得注意的是,[package]部分下的edition字段定义了您将使用的Rust版本。例如,您可以设置为2018,但当前最新版本之一是2021,因此我们通常保留此设置。

在定义部分,您还可以添加作者信息,这在发布项目时尤为重要。例如:

authors = ["Your Name <your.email@example.com>"]

指定依赖项版本范围

回到依赖项配置,除了固定版本,您还可以指定版本范围。这与其他包管理器(特别是Python的pip)的做法相似,可能更容易理解。

例如,您可以这样写:

依赖项名称 = ">=0.8, <0.9"

这表示允许使用从0.8到小于0.9的任何版本。这种方式让您能更精确地约束所使用的依赖项版本。

从Git仓库安装依赖项

另一个有趣的选项是直接从Git仓库安装依赖项。例如,假设您想从GitHub仓库获取serde_json,而不是已发布的版本。

您可以这样配置:

serde_json = { git = "https://github.com/serde-rs/json" }

您甚至可以指定特定分支:

serde_json = { git = "https://github.com/serde-rs/json", branch = "develop" }

使用本地路径作为依赖项

还有一种有用的方法是指定本地路径作为依赖项。假设您是serde_json的作者或贡献者,正在本地修复一个尚未发布的bug。

您可以这样配置:

serde_json = { path = "../serde_json" }

这表示从上一级目录安装serde_json。这种方式非常适合在本地开发和测试依赖项时使用。

总结

本节课中,我们一起学习了如何创建和管理Rust库,以及多种配置项目依赖项的方法。我们探讨了二进制包与库的区别,了解了如何指定版本范围、从Git仓库获取依赖项,以及如何使用本地路径作为依赖项。这些技能将帮助您更灵活地管理Rust项目及其依赖关系。

025:使用Crates.io管理Rust包 📦

在本节课中,我们将学习如何使用Crates.io来管理Rust的包。Crates.io是Rust的官方包注册中心,类似于Python的PyPI。我们将了解如何在这个平台上查找、探索和验证Rust库,以及如何利用它来管理项目的依赖关系。

概述

Crates.io是Rust生态系统的核心组成部分。它是一个集中式的仓库,开发者可以在这里发布自己的库(无论是命令行工具还是库包),也可以下载他人发布的代码。我们之前在Cargo.toml文件中定义依赖时,Cargo工具就是从Crates.io拉取这些包的。它的工作方式与Python的包索引非常相似。

探索Crates.io

上一节我们概述了Crates.io的作用,本节中我们来看看如何实际使用它来查找和了解库。

我们可以直接在Crates.io上搜索我们想用的库。例如,我们一直在使用的一个命令行参数解析框架叫做clap。在搜索框中输入“clap”进行搜索。

以下是搜索结果中需要注意的几个关键信息:

  • 正确的包:在众多结果中,我们需要识别出目标包。描述为“简单易用、高效且功能齐全的命令行参数解析器”的通常就是我们想要的clap
  • 下载量clap拥有超过1.15亿的下载量,这是一个非常可观的数据,表明了其流行度和稳定性。
  • 版本信息:包页面会显示最新的版本号(例如4.3.24)以及总共发布过的版本数量(例如343个),这反映了该库的活跃维护历史。

点击进入clap的页面,我们可以看到更多详细信息。

包页面详解

进入一个包的页面后,我们可以获得丰富的信息来帮助我们评估和使用这个库。

首先,页面会显示包的README文件内容,这通常是库的简要介绍和使用说明。

其次,页面提供了安装指南,告诉我们如何在Cargo.toml中添加依赖。例如,对于clap,添加依赖的代码如下:

[dependencies]
clap = "4.3.24"

此外,页面还会列出该包的依赖项。这对于理解包的复杂性和潜在影响非常重要。依赖项分为:

  • 实际依赖:运行时所必需的库。
  • 开发依赖:仅在开发、测试或构建时需要的库。

一个非常有用的功能是,你可以直接点击这些依赖项的名字,跳转到它们在Crates.io上的页面,从而方便地进行链式探索。

查阅文档

当我们不确定某个库的具体模块或功能如何使用时,文档是必不可少的资源。

在Crates.io的包页面上,通常会有一个指向docs.rs的文档链接。docs.rs是Rust官方自动为Crates.io上所有库生成的文档网站。点击这个链接,我们会离开Crates.io,进入该库专属的、结构清晰的API文档页面。

例如,在clap的文档页面上,我们可以使用搜索功能。如果我曾经想查找关于终端颜色输出的功能,可以搜索“color”。搜索结果可能会显示ColorChoice这个枚举类型,它包含了AutoAlwaysNever三个变体。通过浏览这些文档,我们可以准确地了解如何配置和使用这个功能。

文档页面还允许我们查看和选择库的不同历史版本,这对于解决版本兼容性问题很有帮助。

账户与发布

最后,我们来了解一下Crates.io的账户系统。

登录Crates.io非常简单,直接使用GitHub账户进行授权即可。登录后,你会进入个人仪表板。

在仪表板中,你可以:

  • 管理账户设置
  • 查看你已发布的所有Crate(如果你发布过的话)。
  • 查看你的Crate的总下载量统计数据。

这对于维护自己库的开发者来说是一个中心化的管理界面。

总结

本节课中我们一起学习了如何使用Crates.io。我们了解到Crates.io是查找、验证Rust包的一站式平台。通过它,我们可以搜索包、查看详细信息(如README、版本、依赖)、并直接跳转到官方文档(docs.rs)进行深入学习。同时,使用GitHub账户即可轻松登录,管理自己发布的包。掌握Crates.io的使用,是有效利用Rust丰富生态系统的基础。

026:引言

在本节课中,我们将学习使用Python和Rust构建命令行工具所需的所有基础知识和核心概念。

上一节我们介绍了课程的整体目标,本节中我们将详细概述课程的具体内容结构。

我们将首先学习如何添加选项和标志,包括一些用于开启或关闭特定行为的布尔标志,并了解如何将这些功能集成到我们将要构建的工具中。

接下来,我们将探讨如何处理更复杂的参数和标志。

我们将学习添加子命令。子命令是一种很好的方式,用于分离工具将要执行的不同任务。在编写代码时,它能让你更清晰地划分职责,并明确在每个部分中希望采取的操作。

然后,我们将通过查看环境变量来增加一些额外的灵活性。我们将能够修改我们正在处理的命令行工具,使其能够接受选项和值(而不仅仅是标志)。这样,我们就可以通过读取与特定任务相关的环境变量,来调整和微调命令行工具的行为。

以下是本课程将涵盖的核心技能点:

  • 掌握添加选项和标志(包括布尔标志)的方法。
  • 学习处理复杂参数和实现子命令结构。
  • 了解如何利用环境变量来配置和调整工具行为。

所有这些内容将为你提供极大的灵活性,让你能够根据未来想要构建的命令行工具的具体需求,选择和组合最佳的实现策略。

本节课中,我们一起学习了构建命令行工具的核心知识框架,包括参数处理、子命令设计和环境变量运用,为后续的实践打下坚实基础。

027:在Rust中创建带子命令的命令行工具

在本节课中,我们将学习如何使用clap库的derive特性,为我们的Rust命令行工具添加子命令功能。我们将重构现有代码,使其结构更清晰、更模块化。

概述

我们将对一个现有的Rust命令行工具进行重构。该工具是一个lsblk命令的包装器,用于查询设备信息并以JSON格式输出。目前,它使用clap库的基本方式来解析命令行参数。我们的目标是将其升级,使用clapderive特性来支持子命令,使代码结构更优、更易于维护。

重构为使用Derive特性

首先,我们需要更新Cargo.toml文件,为clap库启用derive特性。这是后续所有操作的基础。

[dependencies]
clap = { version = "4", features = ["derive"] }

完成配置后,我们开始修改主程序文件。我们将移除原有的基于App::new的参数构建方式,转而使用结构体(struct)和枚举(enum)来定义命令行接口。

定义主选项结构体

我们创建一个名为Opts的结构体,并使用#[derive(Parser)]宏。这个结构体将代表我们程序的根命令。

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about)]
struct Opts {
    #[command(subcommand)]
    cmd: Command,
}

在上面的代码中:

  • #[command(author, version, about)]会自动从Cargo.toml中读取程序的元信息。
  • #[command(subcommand)]属性表明cmd字段将包含一个子命令。

定义子命令枚举

接下来,我们定义一个枚举Command,它列出了所有可用的子命令。目前我们只有一个info命令。

#[derive(Subcommand)]
enum Command {
    /// Get info about a device
    Info(InfoOptions),
}

Info变体关联了一个InfoOptions结构体,这个结构体定义了info子命令特有的参数。

定义子命令选项

我们创建InfoOptions结构体,用于定义info子命令需要接收的参数,例如要查询的设备名。

#[derive(Parser)]
struct InfoOptions {
    /// The device to query
    device: String,
}

现在,我们的命令行结构已经定义完毕。主程序逻辑需要相应调整,以匹配新的参数解析方式。

调整主函数逻辑

main函数中,我们首先解析参数,然后根据匹配到的子命令执行相应的逻辑。

fn main() -> Result<()> {
    let opts = Opts::parse();

    match opts.cmd {
        Command::Info(info_opts) => {
            let output = get_device_info(&info_opts.device)?;
            let json_output = serde_json::to_string_pretty(&output)?;
            println!("{}", json_output);
        }
    }
    Ok(())
}

代码解析如下:

  1. Opts::parse() 会自动处理命令行输入,并填充我们定义的结构体。
  2. 使用match语句匹配opts.cmd
  3. 如果匹配到Command::Info,则从其关联的info_opts中取出device参数。
  4. 调用核心函数get_device_info获取数据,序列化为格式化的JSON字符串并打印。

测试与验证

让我们测试一下新的命令行工具。

首先,运行帮助命令查看结构:

cargo run -- --help

你会看到输出中包含了info子命令及其描述。

然后,运行info子命令来查询一个设备(例如vda1):

cargo run -- info vda1

程序应该能正确运行,并输出该设备的JSON信息。

关于子命令命名

你可以轻松地修改子命令的名称。例如,将枚举中的Info改为Part

#[derive(Subcommand)]
enum Command {
    /// Get info about a device
    Part(InfoOptions),
}

那么对应的调用命令就变成了:

cargo run -- part vda1

注意:为了保持代码清晰,建议子命令枚举变体的名称与其关联选项结构体的用途保持一致,避免造成混淆。

总结

本节课中,我们一起学习了如何使用clap库的derive特性为Rust命令行工具添加子命令。我们通过以下步骤完成了重构:

  1. Cargo.toml中启用clapderive特性。
  2. 使用#[derive(Parser)]定义主选项结构体(Opts)。
  3. 使用#[derive(Subcommand)]定义子命令枚举(Command)。
  4. 为每个子命令定义其专属的选项结构体(如InfoOptions)。
  5. main函数中解析参数并使用match语句根据不同的子命令执行相应逻辑。

这种方法的优势在于代码结构清晰、类型安全且易于扩展。当需要添加新的子命令时,只需在Command枚举中添加新的变体并实现其处理逻辑即可,极大地提升了代码的可维护性。

028:在Python中创建带子命令的命令行工具

在本节课中,我们将学习如何使用Python的click框架,为现有的命令行工具添加子命令功能。我们将从一个简单的工具开始,逐步将其改造为支持多个子命令的结构。

概述

我们一直在使用一个名为lsblk的Linux命令行工具,并将其功能导入到Python中,创建了blockpi工具。目前,blockpi接受一个设备名作为位置参数。本节的目标是为其添加子命令(例如infonice),使工具结构更清晰、功能更模块化。

创建命令组

首先,我们需要将现有的单一命令转换为一个可以包含子命令的命令组。这是通过click.group()装饰器实现的。

以下是创建命令组main的步骤:

import click

@click.group()
def main():
    pass

我们将原来的main函数重命名为info,并将其作为main命令组的一个子命令。同时,需要将@click.command()装饰器替换为@main.command(),以表明infomain组下的一个子命令。

@main.command()
@click.argument('device')
def info(device):
    # 原有的命令逻辑
    run_lsblk(device)

完成此更改后,运行blockpi --help,你将看到info被列为一个可用的子命令。要获取info子命令的帮助信息,可以运行blockpi info --help

添加全局选项

有时,我们希望某些选项(如--verbose)对所有子命令都可用,而不是仅对某个特定子命令有效。这可以通过将选项添加到命令组(main)上来实现。

@click.group()
@click.option('--verbose', is_flag=True, help='启用详细输出模式。')
def main(verbose):
    pass

现在,--verbose选项将出现在顶级帮助信息中,作为全局选项供所有子命令使用。

添加更多子命令

我们可以继续为命令组添加更多子命令,以扩展工具的功能。例如,添加一个nice子命令,用于美化输出格式。

以下是添加nice子命令的步骤:

  1. 定义nice子命令函数。
  2. 为其添加必要的参数或选项。
  3. 在函数内部实现特定的逻辑,例如使用pprint模块来美化输出。
from pprint import pprint

@main.command()
@click.argument('device')
def nice(device):
    # 调用底层函数,并传入特定参数以触发美化输出逻辑
    result = run_lsblk(device, nice=True)
    pprint(result)

在底层函数run_lsblk中,我们需要接收nice参数,并根据其值决定是否使用美化打印。

def run_lsblk(device, nice=False):
    # ... 执行命令获取输出 ...
    if nice:
        pprint(output)
    else:
        print(output)

测试与验证

完成代码修改后,需要在终端中进行测试,以确保所有功能按预期工作。

以下是测试步骤:

  1. 运行blockpi --help,确认infonice子命令已列出,并且--verbose作为全局选项出现。
  2. 分别运行blockpi info <设备名>blockpi nice <设备名>,验证它们能正确执行并输出结果。
  3. 如果遇到错误(例如参数传递问题),根据错误信息调整代码,确保函数签名和参数传递正确。

总结

本节课中,我们一起学习了如何使用Python的click框架为命令行工具添加子命令。关键步骤包括:使用@click.group()创建命令组,使用@<group_name>.command()定义子命令,以及将全局选项添加到命令组上。通过这种方式,我们可以构建出结构清晰、易于扩展的命令行工具。

029:在Rust中解析复杂命令行参数

在本节课中,我们将学习如何为Rust命令行工具添加复杂的全局标志(flags)。我们将重点介绍如何添加布尔类型的调试标志和具有计数功能的详细级别标志。

概述

上一节我们为ls命令的Rust包装器添加了子命令。本节中,我们来看看如何为其添加全局命令行标志,以增加程序的复杂性和功能性。

添加调试标志

首先,我们为程序添加一个简单的布尔类型调试标志。这个标志将提供用于调试目的的额外输出。

以下是添加debug标志的步骤:

  1. OptionStruct结构体中添加一个字段。
  2. 使用clap属性宏为这个字段定义短格式(-d)和长格式(--debug)选项。
  3. 为该标志设置一个默认值。
#[derive(Parser, Debug)]
struct OptionStruct {
    #[command(subcommand)]
    pub command: Commands,

    #[arg(short, long, help = "提供用于调试的更好输出", default_value_t = false)]
    pub debug: bool,
}

添加此标志后,运行--help命令,可以在帮助菜单中看到新增的-d, --debug选项。

为了验证标志是否正常工作,我们可以在主函数中打印其值。

fn main() {
    let ops = OptionStruct::parse();
    println!("调试标志的值是:{}", ops.debug);
}

运行程序时,如果不带-d参数,debug的值将为false;如果带上-d参数,其值将变为true

添加详细级别标志

接下来,我们添加一个更复杂的标志:详细级别(verbosity level)。这个标志允许用户通过重复使用-v来增加输出的详细程度。

以下是添加verbosity标志的步骤:

  1. OptionStruct结构体中添加一个u8类型的字段。
  2. 使用clap属性宏,并指定action = ArgAction::Count。这个动作允许标志被多次使用,每次使用都会增加计数值。
  3. 这个标志同样支持短格式(-v)和长格式(--verbose)。
use clap::{ArgAction, Parser};

#[derive(Parser, Debug)]
struct OptionStruct {
    #[command(subcommand)]
    pub command: Commands,

    #[arg(short, long, help = "提供用于调试的更好输出", default_value_t = false)]
    pub debug: bool,

    #[arg(short, long, action = ArgAction::Count, help = "设置输出详细级别")]
    pub verbosity: u8,
}

action = ArgAction::Count是关键,它使得-v -v -v等同于-vvv,并且verbosity字段的值会是3

处理详细级别

添加标志后,我们需要在代码中根据不同的详细级别执行不同的操作。这可以通过match语句来实现。

fn main() {
    let ops = OptionStruct::parse();

    match ops.verbosity {
        0 => println!("无详细输出"),
        1 => println!("一些详细输出"),
        2 => println("更多详细输出"),
        _ => println!("详细程度过高"),
    }
}

现在,运行程序时:

  • 不使用-v:输出“无详细输出”。
  • 使用一次-v:输出“一些详细输出”。
  • 使用两次-v:输出“更多详细输出”。
  • 使用三次或更多次-v:输出“详细程度过高”。

总结

本节课中我们一起学习了如何在Rust命令行工具中添加复杂的全局标志。我们实践了两种主要类型:

  1. 布尔标志:如debug,用于开关某项功能。
  2. 计数标志:如verbosity,通过action = ArgAction::Count实现累加计数,非常适合用于表示级别或强度。

通过clap库,我们可以方便地定义这些标志的短格式、长格式、帮助文本和默认值,从而构建出功能丰富且用户友好的命令行接口。你可以根据实际需求,在此基础上构建更复杂的标志逻辑。

030:在Python中解析复杂命令行参数

在本节课中,我们将学习如何使用Click库处理命令行工具中的一些复杂场景。我们将重点探讨如何使标志全局可用、如何为标志关联特定类型,以及如何使用上下文对象在命令间传递数据。

使verbose标志全局可用

上一节我们介绍了基本的命令行参数解析。本节中我们来看看如何使一个标志(如verbose)在多个子命令中全局可用。目前,verbose标志仅在main函数中定义,子命令info无法直接访问它。

为了解决这个问题,Click提供了一个特殊的上下文对象。我们可以通过pass_context装饰器注入一个名为ctx的上下文对象。这个对象本质上是一个字典,可以用于存储和传递数据。

以下是实现步骤:

  1. 在主命令函数上使用@click.pass_context装饰器。
  2. 在函数内部,通过ctx.ensure_object(dict)确保上下文对象是一个字典。
  3. verbose标志的值存入这个字典:ctx.obj[‘verbose’] = verbose
  4. 在子命令函数上同样使用@click.pass_context装饰器。
  5. 在子命令函数内部,通过ctx.obj[‘verbose’]来获取全局的verbose值。

通过这种方式,verbose标志的状态就可以在主命令和所有子命令之间共享和访问了。

为标志关联特定类型和约束

除了传递数据,我们还可以为命令行参数指定类型并添加约束,这能有效提升程序的健壮性和用户体验。

Click允许我们使用type参数来指定输入值的类型,例如整数(int)、浮点数(float)或字符串(str)。更重要的是,我们可以使用click.IntRange来定义一个数值范围。

以下是定义带类型和范围约束的标志的方法:

@click.option(‘--debug-level‘, ‘-d‘, type=click.IntRange(0, 3), default=0)

这段代码定义了一个--debug-level选项:

  • -d是其短格式。
  • type=click.IntRange(0, 3)指定其值必须是0到3之间的整数。
  • default=0设置了默认值为0。

如果用户输入了范围之外的值(例如100),Click会自动提供一个清晰的错误提示:“Error: Invalid value for ‘–debug-level‘: 100 is not in the range 0<=x<=3.”。这省去了我们手动编写验证逻辑的麻烦。

指定类型后,在代码中可以直接将该参数当作相应的Python类型(如int)使用,进行数学比较或运算,这使内部逻辑处理更加方便和安全。

使用上下文对象传递多个数据

如果需要传递多个全局变量,逐个设置会显得繁琐。Click提供了一种便捷的方法,可以一次性将函数的所有局部变量注入到上下文对象中。

在主命令函数中,我们可以使用以下代码:

ctx.obj.update(locals())

locals()函数会返回当前函数作用域内所有局部变量及其值的字典。通过ctx.obj.update()方法,我们可以将这些变量全部添加到上下文对象中。

这样,所有在main函数中定义的参数(如verbose, debug_level等)都会自动成为上下文对象的一部分,无需再手动逐个添加。子命令通过ctx.obj即可访问所有这些共享数据。

总结

本节课中我们一起学习了使用Click库处理复杂命令行参数的三个高级技巧:

  1. 我们学会了使用@click.pass_context装饰器和ctx.obj字典使标志(如verbose)在不同命令间全局可用。
  2. 我们探索了如何使用type参数(特别是click.IntRange)为命令行参数指定类型和添加输入约束,从而提升程序的健壮性。
  3. 我们掌握了利用ctx.obj.update(locals())一次性将多个数据注入上下文对象的高效方法,简化了命令间的数据传递。

这些功能共同帮助开发者构建出更强大、更灵活、更用户友好的命令行工具。

031:为Rust命令行工具添加环境变量支持

在本节课中,我们将学习如何为基于clap库的Rust命令行工具添加环境变量支持。这将允许用户通过环境变量来配置程序,而不仅仅是通过命令行参数,从而提供更大的灵活性。

概述

上一节我们介绍了如何为命令行工具添加标志(flags)。本节中,我们来看看如何扩展工具的功能,使其能够读取环境变量。我们将使用clap框架提供的原生支持来实现这一功能。

更新Cargo.toml依赖

首先,为了启用clap的环境变量支持功能,我们需要更新Cargo.toml文件中的依赖项。我们需要在clap的特性(features)列表中添加env

代码示例:

[dependencies]
clap = { version = "4", features = ["derive", "env"] }

之前的配置可能只包含derive特性。添加env特性是启用原生环境变量支持的必要步骤。如果没有这个特性,后续的代码将无法工作。

修改命令行参数结构体

接下来,我们需要修改定义命令行参数的结构体。我们将为debug标志添加环境变量支持。

假设我们有一个名为Debug2的字段(或标志),我们将使用clapenv属性来指定其对应的环境变量名。

代码示例:

use clap::Parser;

#[derive(Parser, Debug)]
struct Args {
    #[clap(short, long, env = "ELS_BLOG_DEBUG")]
    debug: bool,
    // ... 其他字段
}

在上面的代码中:

  • #[clap(short, long)] 定义了命令行短标志-d和长标志--debug
  • env = "ELS_BLOG_DEBUG" 是关键添加。它告诉clap,如果没有通过命令行提供--debug标志,则尝试从名为ELS_BLOG_DEBUG的环境变量中读取值。

clap会自动将环境变量的值(如"true""1")解析为bool类型。这种方式非常简洁,避免了手动处理环境变量的复杂代码。

替代方案:手动处理环境变量

如果不使用clap的原生支持,我们将需要手动检查环境变量。以下是手动实现的逻辑:

代码示例:

use std::env;

let debug = env::var("ELS_BLOG_DEBUG")
    .map(|v| v.parse().unwrap_or(false)) // 尝试解析为bool,失败则用false
    .unwrap_or(false); // 如果环境变量不存在,也使用false

这种方法需要更多代码,并且要处理错误和默认值。相比之下,clap的原生支持更加直接和安全。

测试功能

修改完成后,我们可以测试环境变量支持是否正常工作。

首先,运行帮助菜单,确认新选项已正确显示:

cargo run -- --help

你应该能在输出中看到与debug标志相关的信息。

以下是测试步骤:

  1. 仅使用命令行参数

    cargo run -- --debug
    

    程序应显示调试模式已启用。

  2. 不使用任何参数

    cargo run
    

    程序应显示调试模式未启用。

  3. 使用环境变量
    在Unix-like系统(Linux/macOS)上:

    ELS_BLOG_DEBUG=true cargo run
    

    在Windows命令提示符(CMD)上:

    set ELS_BLOG_DEBUG=true && cargo run
    

    在Windows PowerShell上:

    $env:ELS_BLOG_DEBUG="true"; cargo run
    

    此时,即使没有在命令行中输入--debug,程序也应显示调试模式已启用,因为它成功从环境变量中读取了值。

应用场景与优势

为命令行工具添加环境变量支持是一个强大的模式,其主要优势包括:

  • 用户友好:为用户提供了更大的灵活性。他们可以选择通过命令行参数或环境变量来配置工具。
  • 便于自动化:在脚本或CI/CD(持续集成/持续部署)流水线中,使用环境变量来传递配置通常比拼接复杂的命令行字符串更清晰、更安全。
  • 管理敏感信息:可以将密码、密钥等敏感信息通过环境变量传递,避免在命令行历史或脚本中明文暴露。
  • 简化默认配置:可以为常用配置设置默认的环境变量,用户无需每次都在命令行中输入。

总结

本节课中我们一起学习了如何为Rust命令行工具集成环境变量支持。关键步骤包括:

  1. Cargo.toml中为clap依赖添加env特性。
  2. 在参数结构体的字段上使用#[clap(env = “VAR_NAME”)]属性。
  3. 通过命令行和环境变量两种方式测试功能是否生效。

利用clap框架的原生支持,我们可以用极少的代码实现这个功能,这比手动解析环境变量更加高效和可靠。这个特性极大地增强了工具的可用性和可集成性。

032:在Python命令行工具中添加环境变量 🛠️

在本节课中,我们将学习如何为Python命令行工具添加环境变量支持。环境变量是一种灵活的方式,允许用户在不直接修改命令行参数的情况下配置工具的行为。

概述

我们将基于一个名为 blockpie 的命令行工具进行演示。该工具目前有一个 --debug-level 选项,用于设置调试级别。我们的目标是允许用户通过环境变量来设置这个值,而不仅仅是通过命令行标志。

实现步骤

上一节我们介绍了基本的命令行参数解析。本节中,我们来看看如何集成环境变量。

1. 修改Click选项定义

为了支持环境变量,我们需要修改 click.option 装饰器的参数。具体来说,是添加一个 envvar 关键字参数。

以下是修改后的代码示例:

@click.option(
    '-d', '--debug-level',
    default=1,
    type=int,
    help='Set the debug level. Optionally, use environment variable BLOCKPIE_DEBUG_LEVEL.',
    envvar='BLOCKPIE_DEBUG_LEVEL'  # 新增的环境变量参数
)

在这段代码中:

  • envvar='BLOCKPIE_DEBUG_LEVEL' 指定了环境变量的名称。
  • 当用户运行命令时,Click会首先检查命令行是否提供了 --debug-level 参数。如果没有,它会去查找名为 BLOCKPIE_DEBUG_LEVEL 的环境变量,并使用其值。
  • 如果两者都未提供,则使用 default=1 作为默认值。

2. 更新帮助信息

为了让用户知道可以使用环境变量,我们在帮助文本中明确说明了这一点。这通过 help 参数实现。

3. 测试环境变量功能

修改完成后,我们需要验证功能是否按预期工作。以下是几种测试方法:

首先,我们通过命令行标志测试基本功能是否正常。

python blockpie.py --debug-level 3

如果一切正常,这将打印出调试级别为3。

接下来,我们测试环境变量。有两种方式设置环境变量:

方法一:在命令前临时设置(仅对该命令生效)

BLOCKPIE_DEBUG_LEVEL=4 python blockpie.py

运行此命令,工具将打印出调试级别4,因为它读取了环境变量的值。

方法二:使用 export 命令设置(对当前终端会话生效)

export BLOCKPIE_DEBUG_LEVEL=5
python blockpie.py

运行后,工具将打印出调试级别5。你可以使用 env 命令查看当前所有环境变量,确认 BLOCKPIE_DEBUG_LEVEL=5 已存在。

若要临时更改,可以回到方法一,或在同一个终端会话中使用新的 export 命令覆盖。

总结

本节课中我们一起学习了如何为Python Click命令行工具添加环境变量支持。

  1. 我们通过在 @click.option 装饰器中添加 envvar 参数,将选项与环境变量绑定。
  2. 我们更新了帮助信息,以指导用户。
  3. 我们实践了两种设置环境变量的方法:临时内联设置和使用 export 命令。
    这是一种非常直接的方式来增强命令行工具的灵活性,允许用户通过环境变量进行配置,这在容器化(如Docker)和脚本化场景中尤其有用。

033:打包与分发工具 🚀

在本节课中,我们将学习如何将你开发的Rust命令行工具进行打包和分发。当你构建了一个有用的、能自动化某些任务并提升效率的工具后,下一步就是考虑如何分享它。

打包的重要性 📦

上一节我们探讨了工具的开发,本节中我们来看看如何让它能被他人使用。打包和分发你的工具至关重要。

你可能希望分发它,例如与团队中的其他成员或其他团队分享。或者,你可能希望将这些工具安装到某些系统中,比如持续集成系统或其他服务器,这些系统可以从你从头构建的自动化中受益。

理解打包机制 🔧

以下是打包过程的核心目标:

第一步是理解在Python或Rust中打包的工作原理。两者的打包方式略有不同,但它们都实现同一个目标:如何将所有内容整合在一起

当我们处理依赖项时,这一点尤其重要。如果我们依赖某个框架或其他库来帮助我们的命令行工具实现某些目标或自动化任务,我们如何将所有这些东西整合在一起?

如果你不再构建一个简单的脚本(例如在Python中,你可以使用单个文件分发并到处复制),你如何确保所有依赖项和一切都被整合到一个单一的包中?这个包可以分发给团队成员或其他可能从中受益的系统。

打包与发布的步骤 📤

所以,第一步是打包。我们将看到如何通过打包将所有内容整合起来。

接下来的第二步是尝试进行实际的发布:你如何进行发布?如何将其放入注册表或包索引中(对于Python而言)?这不仅允许你将包放在那里,还允许其他工具从那里安装。对于Rust,对应的平台是Crates.io

这些索引或注册表将使你能够更轻松地分享你的包,前提是你已经使用接下来将要看到的适当打包工具正确地将其整合在一起。

总结 ✨

本节课中我们一起学习了为Rust工具进行打包和分发的基本概念与初步步骤。我们了解到打包是为了整合代码与依赖,而发布则是通过特定平台(如Crates.io)来共享成果,从而使你的自动化工具能在更广泛的场景中被安装和使用。

034:为Rust命令行工具创建包 📦

在本节课中,我们将学习如何为Rust命令行工具创建和构建一个可发布的包。我们将重点理解cargo build命令在调试(debug)和发布(release)模式下的区别,以及如何生成最终的可执行文件。

到目前为止,我们一直在使用Rust开发一个命令行工具。我们所有的代码都位于src目录下,主要包含lib.rsmain.rs文件。这个工具是一个else块的包装器,我们将其命名为block_rs

现在,是时候深入理解构建一个包(package)所涉及的具体步骤了。

理解Cargo的构建过程

在此之前,我们通常使用cargo run命令来运行程序。这个命令实际上一次性完成了两件事:首先构建(build)项目,然后运行生成的可执行文件。

让我们先检查一下项目结构。在项目根目录下,你会发现一个target文件夹。这是Cargo存放所有构建产物的目录。如果你查看里面的内容,会发现大量文件,包括项目本身编译后的库、所有依赖项(例如我们使用的clap库)等。

为了更清晰地理解,我们先看看cargo build命令。运行它(如果代码没有改动,它会很快完成,因为Cargo会利用缓存)。cargo run命令内部就包含了cargo build这一步。

现在,让我们进入target目录看看。在target/debug目录下,我们可以找到block_rs(或block_rs.exe,取决于你的操作系统)。这就是我们命令行工具的调试版本可执行文件。我们可以直接运行它,例如:

./target/debug/block_rs --help

这个文件的大小大约是5MB。目前,我们只需关注这个可执行文件本身。

构建发布版本(Release Build)

那么,如何构建一个准备发布的、经过优化的包呢?答案是使用cargo build --release命令。

在运行这个命令之前,我们先看看cargo build --help,会发现其中提到了--release选项。构建发布版本意味着几件事:

  1. 启用优化:编译器会进行大量优化,使最终代码运行速度更快。
  2. 移除调试信息:例如测试代码、调试符号等会被剥离,使包更精简。
  3. 更长的编译时间:因为编译器需要进行复杂的分析和优化,所以构建过程会比调试模式慢得多。

Rust团队这样设计(区分调试和发布模式)是为了让我们在开发时能快速迭代(使用快速的调试构建),而在准备发布时,再花时间生成一个高度优化的最终产品。

现在,让我们运行cargo build --release。你会立刻注意到,与之前快速的调试构建相比,发布构建需要花费更长的时间(在我的机器上大约花了一分多钟)。

构建完成后,再次查看target目录。现在除了debug文件夹,还多了一个release文件夹。

调试版与发布版的对比

让我们来对比一下两个版本的可执行文件:

以下是关键区别:

  • 文件大小:发布版的可执行文件(约1.1MB)通常比调试版(约4.9MB)更小,因为移除了调试信息。
  • 构建速度:调试版的构建速度极快,适合开发。发布版的构建速度慢,因为它进行了全面的优化。
  • 运行性能:发布版的可执行文件由于启用了优化,运行时性能会更好。

总结

本节课中,我们一起学习了如何为Rust命令行工具创建包。我们掌握了:

  1. cargo run 命令同时完成了构建和运行。
  2. cargo build 命令专门用于构建项目,产物位于 target/debug/ 目录。
  3. cargo build --release 命令用于构建优化的发布版本,产物位于 target/release/ 目录。发布版本构建更慢,但生成的可执行文件更小、运行更快,适合最终分发。

至此,你就掌握了使用Rust和Cargo构建一个可供发布的命令行工具包的基本方法。

035:为Python命令行工具创建包 📦

在本节课中,我们将学习如何将一个Python项目打包成可供分发的格式。我们将重点介绍使用 setup.py 文件创建二进制包(wheel)的方法,以及如何在开发模式下安装包以便快速测试修改。

概述

Python包不仅仅是包含代码的目录。当开发者谈论“包”时,通常指的是经过打包、可以发布和安装的软件单元。本节课将指导你使用 setup.pywheel 工具来创建这样的包。

打包基础与 setup.py

上一节我们介绍了Python项目的基本结构。本节中我们来看看如何通过 setup.py 文件来描述和构建一个可发布的包。

setup.py 文件是Python传统打包生态系统的核心,它定义了项目的元数据和构建指令。需要注意的是,Python的打包生态存在一些挑战,例如 setuptools 未来可能不再支持某些交互方式。不过,目前它仍然是可用的方法之一,我们稍后会探讨其他替代方案。

准备虚拟环境

在开始打包之前,创建一个独立的虚拟环境是至关重要的。这能确保依赖隔离,并且是打包过程的标准实践。

以下是创建和激活虚拟环境的步骤:

  1. 在项目根目录下创建虚拟环境。使用 .venv 作为目录名是一个好习惯。
    python -m venv .venv
    
  2. 激活虚拟环境。在VS Code中,选择该环境作为工作区解释器后,新打开的终端会自动激活它。
  3. 如果需要在终端手动激活,可以运行:
    source .venv/bin/activate
    
    激活后,终端提示符前通常会显示 (.venv)

构建二进制分发包(Wheel)

虚拟环境准备就绪后,我们就可以开始构建包了。构建二进制包(wheel)是一种高效的分发方式,用户无需在本地编译即可安装。

首先,必须确保在虚拟环境中安装了 wheel 包,它是构建二进制分发格式的基础。

pip install wheel

安装完成后,可以使用 setup.py 来查看可用的构建命令。

python setup.py --help-commands

在帮助命令的输出中,找到 bdist_wheel,这个命令就是用来创建wheel格式的二进制分发包的。运行以下命令进行构建:

python setup.py bdist_wheel

命令执行成功后,会在项目目录下生成一个 dist 文件夹,里面包含一个 .whl 文件。这个文件就是打包好的二进制分发包,可以直接用于发布和安装。

理解构建产物

构建过程完成后,让我们查看一下生成的文件,以理解其命名和用途。

生成的 .whl 文件名中包含了系统平台和架构的标签(例如 macosx_10_11_arm64)。这是因为wheel是预编译的二进制分发格式,它针对特定的操作系统和处理器架构进行了优化。这样,用户在其对应的系统上安装时,就无需本地编译,安装速度更快,也避免了编译依赖的问题。

开发模式安装

在开发过程中,频繁地重新构建和安装包效率很低。为此,Python提供了开发模式安装,它允许你直接链接到源代码进行修改和测试。

使用以下命令进行开发模式安装:

python setup.py develop

这个命令会将你的包以“开发模式”安装到环境中。它不会将文件复制到Python的 site-packages 目录,而是创建一个指向你项目源代码的链接。这样,你对代码所做的任何修改都会立即生效,无需重新安装。你可以通过 which 命令验证命令行工具是否链接到了你的项目路径。

当你完成开发并希望进行永久性安装时,则应使用标准的 pip install .python setup.py install 命令。

总结

本节课中我们一起学习了为Python命令行工具创建包的核心流程。我们首先了解了 setup.py 文件的作用,然后通过创建虚拟环境来隔离依赖。接着,我们安装了 wheel 包并使用 python setup.py bdist_wheel 命令成功构建了二进制分发包。最后,我们探讨了开发模式安装(python setup.py develop)的便利性,它允许开发者在修改代码后立即看到效果,极大地提升了开发效率。掌握这些步骤,你就能有效地打包和分发自己的Python工具了。

036:Python打包替代方案 🐍

在本节课中,我们将探讨Python打包生态中一些替代传统setuptools的工具。我们之前提到,Python的打包方式存在一些问题,有多种不同的工具可以实现相同的打包目标。我们之前创建了一个二进制文件,并使用setuptools来完成打包所需的多种操作。现在,我们将介绍一些新的工具作为潜在的替代方案。

安装build工具 🔧

首先介绍的新工具是build。它被提议作为未来可能替代setuptools的工具。在使用之前,我们需要先安装它。

安装命令如下:

pip install build

这里的build就是工具的名称。

安装完成后,我们就可以使用它了。例如,我们可以运行:

python -m build

为了有更多空间,我们移除了文件浏览器视图。运行上述命令后,我们可以查看帮助菜单,了解build工具提供的所有功能。这确实是一个命令行工具,专门用于构建包。

使用build构建Wheel包 📦

如果我们想构建一个Wheel包,可以使用bdist_wheel命令。这与setuptools中的命令类似,但使用的是不同的工具。

具体命令是:

python -m build --wheel

这个命令的执行过程略有不同。你会看到它为我们创建了一个虚拟的隔离环境。当我们运行这个命令时,它会创建一个虚拟环境,并在其中安装必要的包,包括setuptoolswheel。它还会获取所有依赖项,并完成所有必需的步骤。

之前我们需要手动安装wheel包,但使用build时则不需要。命令完成后,所有生成的文件都会放在同一个位置。你可以看到它创建了一个类似build/on a bid_mac_1011_arm64/wheel/的子目录,并将我们所有的包和所需文件放在那里。这无疑是一个可行的替代方案。

可编辑模式安装的替代方案 🔄

过去,我们还使用过python setup.py develop命令。这个命令仍然可用且有效。但我们现在有一个替代方案,即使用pip install-e标志。

以下是查看-e标志帮助信息的方法:

pip install --help

然后我们可以自己查找-e标志。不过,这种方法可能会遇到一些问题,因为帮助信息中有很多标志。但-e标志确实存在,它的描述是:“以可编辑模式安装项目”。这与setuptoolsdevelop模式功能相同,可以从本地项目路径或版本控制系统(VCS)URL安装。

因此,我们可以运行:

pip install -e .

这意味着在当前目录以可编辑模式安装包。它会构建依赖项,并实现与之前develop模式相同的行为。这些都是你可以考虑的替代方案。

使用pyproject.toml配置文件 ⚙️

最后一个要介绍的工具是pyproject.toml。它允许我们通过一个配置文件来实现之前setuptools的功能。

pyproject.toml文件看起来很像一个配置文件。它实际上不是可执行文件,但如果我们没有setup.py,它仍然可以让我们仅通过一个.toml文件来安装依赖项并确保一切就绪。

在文件中,我们可以描述项目所需的一切。例如,我们可以指定所需的Python版本和包依赖。以下是一个示例内容:

[project]
name = "my_project"
version = "0.1.0"
requires-python = ">=3.8"
dependencies = [
    "click",
]

在这个例子中,我们不仅指定了库(例如click),还指定了希望使用的Python版本。这提供了一种更现代、更声明式的项目配置方式。

总结 📝

本节课我们一起学习了Python打包的几种替代方案。我们首先介绍了新的build工具,它可以用于构建Wheel包并自动管理环境。然后,我们探讨了使用pip install -e .作为python setup.py develop的替代命令,以实现可编辑模式安装。最后,我们了解了pyproject.toml配置文件,它提供了一种声明式的方法来管理项目元数据和依赖关系,是未来Python打包的重要方向。这些工具共同为Python开发者提供了更灵活、更现代的打包选择。

037:将Rust应用发布到Crates.io 🚀

在本节课中,我们将学习如何将一个Rust命令行工具发布到Crates.io,这是Rust社区的官方包注册中心。我们将了解发布前的准备工作、必要的配置以及发布的具体流程。

概述

发布Rust应用到Crates.io涉及几个关键步骤,包括检查项目配置、确保元数据完整、进行试运行以及最终的认证和发布。我们将使用cargo publish命令来完成这些操作。

项目配置检查

首先,我们需要检查项目的Cargo.toml配置文件。与其他编程语言的工具不同,Rust的发布流程通常不需要在配置文件中进行大量特殊设置。

在我们的示例中,Cargo.toml文件包含了项目的依赖项(如serde_jsonclap)以及一些启用的功能。最重要的是,它定义了项目的版本号。

[package]
name = "ls_block_wrapper"
version = "0.1.0"
...

发布到Crates.io

我们将使用cargo publish命令来发布应用。Crates.io是Rust社区主要的公共包注册中心,所有发布的包都可以在这里被其他开发者找到和使用。

值得注意的是,cargo publish命令中的“registry”参数可以指向不同的注册中心。除了公共的Crates.io,你也可以将包发布到私有注册中心,这在企业内网或受限制的开发环境中非常有用。

准备工作:Crates.io账户与认证

在开始发布之前,你需要在Crates.io上拥有一个账户。你可以使用GitHub账户快速登录。

登录后,你需要生成一个API令牌(API Token),用于在命令行中进行身份验证。这个令牌可以通过两种方式使用:

  1. 将其保存在本地的 ~/.cargo/credentials 文件中。
  2. 通过设置环境变量 CARGO_REGISTRY_TOKEN

试运行发布流程

在正式发布前,强烈建议使用 --dry-run 标志进行试运行。这个操作会模拟整个发布过程,包括打包、编译和检查,但不会真正上传你的包。

执行以下命令:

cargo publish --dry-run

这个过程会执行多项检查,并警告你可能存在的问题,例如未提交的代码更改或缺失的项目元数据。

完善项目元数据

cargo publish 的检查机制非常有用,它能帮助我们发现配置中的遗漏。常见的警告包括缺少描述、许可证文件、文档链接或代码仓库地址。

为了通过检查,我们需要在 Cargo.toml[package] 部分补充这些信息:

[package]
name = "ls_block_wrapper"
version = "0.1.0"
description = "一个用于包装ls命令的示例工具" # 项目描述
license = "MIT" # 许可证
documentation = "https://docs.rs/ls_block_wrapper" # 文档链接(可指向docs.rs)
repository = "https://github.com/yourusername/advanced-rust-ci" # 代码仓库地址

补充信息后,记得提交对 Cargo.toml 的更改到Git。

处理未提交的更改

如果工作目录中存在未提交的更改(例如你刚刚修改了Cargo.toml),cargo publish会发出警告。这是为了防止意外发布未版本控制的代码。

你可以选择先提交这些更改:

git add Cargo.toml
git commit -m “更新项目元数据”

或者,如果你确认这些更改无关紧要,可以在发布时使用相应标志强制继续。

正式发布与身份验证

当你确认所有检查都通过后,就可以移除 --dry-run 标志进行正式发布了。

cargo publish

如果是第一次发布,你会遇到身份验证错误,因为尚未登录。此时,你需要运行:

cargo login

这个命令会提供一个URL,引导你到Crates.io网站获取令牌。将令牌粘贴回终端,即可完成认证。完成登录后,再次执行 cargo publish 命令,你的Rust包就会被上传并发布到Crates.io。

总结

本节课我们一起学习了将Rust应用发布到Crates.io的完整流程。我们了解了Cargo.toml中元数据的重要性,如何使用cargo publish --dry-run进行试运行和检查,以及如何通过cargo login完成身份验证。遵循这些步骤,你可以顺利地将自己的工具或库分享给整个Rust社区。

038:发布到Python包索引(PyPI) 📦

在本节课中,我们将学习如何将你的Python工具发布到官方的Python包索引(PyPI)。我们将介绍传统的发布方法为何已弃用,并详细讲解使用现代工具twine进行发布的全过程,包括如何配置API令牌进行身份验证。

传统方法已弃用

当你准备好发布工具时,需要确保使用Python中特定的专用工具。

过去,如果你查阅一些旧文章或帮助文档,可能会看到类似python setup.py upload的命令。实际上,我们可以尝试运行这个命令,但会得到一个错误代码(如403或401)。这代表了旧有的发布方式,现在它已100%完全弃用,不再被允许。

即使命令本身能运行,它也已过时。这里的错误信息“无效或不存在的身份验证信息”虽然部分正确(因为确实缺少正确的认证),但根本原因在于该方法本身已被禁用。

使用Twine进行发布

因此,正确的发布方式是使用twine工具。首先,我们需要安装它。请确保你已激活虚拟环境(通常命令行提示符会有标识),然后运行安装命令:

pip install twine

安装完成后,你可以查看twine的帮助菜单。它允许你执行上传(upload)、注册(register)或检查(check)等操作,用于向代码仓库发布你的包。你可以通过Twine命令行接口(CLI)或使用python -m twine来调用它。

基本的上传命令结构如下,你需要指定目标仓库。对于PyPI,默认仓库就是pypi

twine upload --repository pypi dist/*

运行上述命令后,我遇到了“未授权”的错误,因为我尚未登录,并且启用了双因素认证。错误信息提示:“必须使用API令牌或受信任的发布者来代替密码进行上传”。这是正确的,信息也详细说明了情况。

配置PyPI API令牌

那么,这个API令牌如何工作呢?让我们转到PyPI网站(pypi.org)。你需要在此创建一个账户(注意,此账户与GitHub账户不关联,这与Rust的crates.io等平台不同)。

登录后,进入你的账户设置。在账户设置页面中,向下滚动直到找到“创建API令牌”的选项。点击创建后,你需要为令牌命名,并选择其作用范围(例如,它可以用于哪些特定的包)。这将帮助你正确地进行上传身份验证。


测试发布流程

让我们回到我们的项目。如果你想在上传和发布前进行测试,可以使用PyPI提供的测试仓库。命令如下:

twine upload --repository testpypi dist/*

这样,系统会提示你输入用户名和密码。这是一种良好且可靠的方式来测试上传流程,确保一切准备就绪。虽然我因为未使用正确密码而得到了403禁止访问错误,但你在测试时应该能预期它正常工作。

首次发布:注册包

对于全新的包,你需要做的第一件事是注册。register子命令的使用方式与upload基本相同。你需要提供你的凭证,构建好所有文件,然后运行命令以确保它在Python包索引(PyPI)中正确注册。

总结

本节课中,我们一起学习了如何将Python包发布到PyPI。我们了解到传统的python setup.py upload方法已被弃用,现代的标准做法是使用twine工具。整个过程包括:安装twine、在PyPI官网创建并配置API令牌以进行安全身份验证、使用测试仓库(TestPyPI)验证发布流程,以及最终使用twine upload命令将包发布到正式的PyPI仓库。对于新包,还需要先使用twine register命令进行注册。

039:容器化你的应用

概述

在本节课中,我们将学习如何使用Docker容器化你的Rust命令行工具。容器化是一种流行的应用打包和分发方式,它能让你的工具在任何支持Docker的环境中稳定运行,无需担心依赖问题。

理解Dockerfile结构

上一节我们介绍了Rust命令行工具的开发,本节中我们来看看如何为它创建Docker容器。Dockerfile是定义容器构建过程的配置文件。

以下是Dockerfile的基本结构:

FROM rust:latest
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release
ENTRYPOINT ["/app/target/release/block_rs"]
CMD ["--help"]

这个Dockerfile从Rust官方镜像开始,设置工作目录,复制项目文件,构建发布版本,最后定义容器的入口点和默认命令。

构建Docker镜像

理解了Dockerfile的结构后,接下来我们看看如何实际构建镜像。

在终端中执行以下命令:

docker build -t block_rs:latest .

这个命令会:

  • 使用当前目录的Dockerfile
  • 构建名为block_rs的镜像
  • 标记为latest版本
  • 点号表示使用当前目录作为构建上下文

运行容器化应用

镜像构建完成后,我们可以运行容器来测试我们的工具。

以下是运行容器的基本命令:

docker run block_rs:latest

由于我们在Dockerfile中设置了CMD ["--help"],运行不带参数的容器会显示帮助信息。

传递命令行参数

容器化的工具应该像本地安装的工具一样接受参数。

以下是传递参数的示例:

docker run block_rs:latest --version
docker run block_rs:latest -v

这些命令会调用容器内编译好的block_rs工具,并传递相应的参数。

发布到容器注册表

构建好的镜像可以发布到容器注册表,方便其他人使用。

以下是常见的发布目标:

  • Docker Hub
  • GitHub Container Registry
  • 企业内部私有注册表

发布后,用户可以通过简单的docker pull命令获取你的工具。

总结

本节课中我们一起学习了如何将Rust命令行工具容器化。我们了解了Dockerfile的编写方法,学会了构建Docker镜像,运行容器化应用,以及如何发布到容器注册表。容器化为你的工具提供了另一种分发方式,特别适合需要复杂依赖或跨平台部署的场景。

040:命令行工具中的日志与错误处理 🛠️

在本节课中,我们将学习如何在命令行工具及其他应用中实现日志记录与错误处理。我们将探讨如何优雅地处理错误,以及如何通过日志来诊断问题,从而构建更健壮、更易用的工具。

概述

错误处理与日志记录是构建可靠应用程序的基石。对于命令行工具而言,当用户遇到一个仅显示“发生意外错误”的模糊提示时,会感到非常沮丧,因为他们无法得知问题所在、如何纠正或如何达成目标。因此,我们不仅要妥善处理错误,还要提供清晰的日志来帮助理解和调试问题。

上一节我们介绍了命令行工具的基础结构,本节中我们来看看如何为其注入强大的错误处理和日志记录能力。

错误处理的重要性

在初学一门编程语言时,你可能会寻找一种让错误“消失”的方法。然而,更重要的是学会优雅地处理这些错误,并建立一种机制来审视错误的具体内容。有效的错误处理能提升工具的健壮性和用户体验。

日志记录的基础

接下来,我们不仅会探讨错误处理本身,还会学习如何使用日志来辅助我们。我们将介绍日志记录的基础知识,以及如何在命令行工具中实现和添加日志语句与配置。

日志的配置与调整

我们将看到如何修改工具中的日志设施,以便调整日志级别或详细程度。通过这种方式,我们可以让工具的输出更加灵活和实用。

日志的输出类型

除了在终端输出日志,我们还将探讨其他可以实现和使用的日志类型。其中之一是文件日志。

文件日志策略

过去,我在从头构建应用程序和命令行工具时,采用过一个很好的策略:将高度详细的日志记录到一个文件中,与面向用户的终端日志分开。这样,在需要调试问题时,你可以查看这个包含详尽输出的文件。

以下是这种策略的优势:

  • 界面整洁:终端界面可以保持简洁明了,只向用户展示更精炼、更友好的信息。
  • 调试方便:文件日志提供了完整的上下文和细节,便于深入分析问题。
  • 提升健壮性:这是一种使工具更加健壮的可靠方法,能将工具的整体质量提升到一个新的水平。

总结

本节课中我们一起学习了在命令行工具中实施错误处理与日志记录的关键性。我们明白了优雅处理错误的重要性,探索了日志记录的基础、配置方法以及不同的输出类型(特别是文件日志)。掌握这些技能将使你能够构建出更健壮、更易于维护和调试的命令行工具。

041:在Rust中实现基础日志 📝

概述

在本节课中,我们将学习如何在Rust命令行工具中实现基础日志功能。我们将使用两个核心库来构建一个灵活的日志系统,它可以控制日志输出的详细程度,并替代简单的println!宏。

日志库介绍

在Rust中,可以通过几个库的组合来实现基础日志功能。我已经将这些库添加到了我的Cargo.toml文件中。

以下是Cargo.toml文件中的相关依赖项:

[dependencies]
log = "0.4"
env_logger = "0.10"

logenv_logger这两个crate协同工作,为我们的工具提供日志记录能力。

现有代码的问题

现在,我们转到main.rs文件开始实现日志功能。如果向下滚动查看代码,会发现我们一直在使用println!宏来输出信息,并且这种用法遍布各处。

println!的问题在于它不允许你调整日志的详细级别。如果你希望某些消息处于特定的级别(例如调试信息),这是无法直接实现的。你当然可以使用if-else语句来控制,但你不希望到处都是这样的条件判断。实现日志系统将为你提供诸多特性,这只是其中之一。

导入必要的模块

首先,我们需要在文件顶部添加实现日志功能所需的一些导入项。

第一个导入是LevelFilter,它将允许我设置正确的日志级别。

use log::LevelFilter;

接下来,我不使用simple_logger,而是使用一个叫做env_logger的模块及其Builder

use env_logger::Builder;

env_logger为我提供了构建日志格式和所有我们想要的功能的能力。虽然添加两个crate可能看起来有些繁琐,但你会看到它对我们来说工作得非常好。

配置日志构建器

保存更改后,我们滚动到main函数。在main函数中,我们将开始创建构建器,以便在这里建立我们的日志设施。

我声明一个可变的builder对象,并使用Builder::new()来创建它。

let mut builder = Builder::new();

然后,我们设置builder的过滤器级别。

builder.filter_level(LevelFilter::Debug);

这里我们将级别设置为Debug(而不是Info)。Debug级别将允许我们至少看到所有调试级别的消息。

这样,我们就为想要做的事情做好了准备。请注意,这里的方法是filter_level,而不是filter

测试日志功能

首先,让我们确保一切正常工作。切换到终端,运行cargo run info v1。一切正常。

快速检查help命令是否正常工作,它确实运行正确。关闭这些输出,我们仍然到处使用println!

现在,让我们开始添加一些日志语句。例如,在这里我们可以使用log::debug!宏。

log::debug!("Debug message: logging is enabled");

添加分号后,我们再次切换到终端。这次不运行help,而是运行info vd1,你会看到我们的消息开始出现。

我们得到了时间戳、调试级别(显示为蓝色和粗体)、R2标识,以及实际的消息。这就是我们启用简单日志的方式。

不仅如此,我们还可以开始调整设置。例如,将LevelFilterDebug改为Info,然后再次尝试运行终端命令。

我们将看不到那条调试消息。在输出中,没有像之前那样的调试信息。原因是我们已经更改了过滤器级别。

填充日志消息

你可以开始围绕你想要的内容进行调整,并开始填充这里的消息。例如,在if options.debug的条件块中,我们可以使用log::info!

log::info!("Debug mode enabled");

所有这些都可以更改,然后你开始看到生成更多消息的能力。

让我们再尝试一次终端命令,并确保调试级别正常工作。如果我们使用-t -D选项(来自那个参数),让我们看看它是否工作。

现在我们同时得到了两条信息:“info: Debug mode enabled” 和 “debug: Debug message: logging is enabled”。我或许应该做得更好,不要在一个信息消息中添加调试消息以免混淆大家,但你应该明白了:在你的Rust命令行工具中添加和使用日志功能并不复杂。

总结

本节课中,我们一起学习了如何在Rust中实现基础日志系统。我们引入了logenv_logger库,配置了日志构建器并设置了日志级别,最后用日志宏替换了原有的println!语句。这个系统提供了按级别过滤消息的能力,使得调试和控制输出变得更加灵活和高效。

042:在Python中实现基础日志 📝

在本节课中,我们将学习如何在Python应用程序中添加基础的日志记录功能。Python的logging模块是标准库的一部分,无需额外安装,这使得入门变得非常简单直接。我们将从最基本的配置开始,逐步了解如何定制日志记录器,以便更清晰地追踪程序执行过程中的信息。

概述

Python的logging模块提供了一个灵活且强大的日志记录系统。通过简单的配置,我们可以快速开始记录信息,这对于调试和理解程序流程非常有帮助。本节将演示如何设置基础日志,并解释日志记录器的层级结构。

基础配置与使用

首先,我们来看如何使用logging模块进行最基本的配置。logging.basicConfig函数可以处理日志模块的大部分默认设置,提供一个良好的起点。

以下是使用基础配置记录日志的步骤:

  1. 导入logging模块。
  2. 调用logging.basicConfig()进行配置。
  3. 使用logging.debug()等函数记录消息。
import logging

logging.basicConfig()
logging.debug("This is a debug message from main.py")

运行上述代码,日志消息会默认输出到标准错误流(终端)。这是一种快速开始记录日志的方式。

使用命名记录器

上一节我们介绍了基础配置,本节中我们来看看如何创建和使用命名记录器,以获得更清晰的日志来源信息。

默认情况下,日志使用“根记录器”(root logger)。为了更好地标识日志来源,我们可以创建具有特定名称的记录器。

以下是创建和使用命名记录器的步骤:

  1. 使用logging.getLogger(__name__)获取或创建一个记录器。__name__变量会自动设置为当前模块的名称。
  2. 使用这个记录器对象来调用日志方法,如log.debug()
import logging

# 创建一个命名记录器,通常使用 __name__
log = logging.getLogger(__name__)

def main():
    logging.basicConfig()
    # 使用根记录器
    logging.debug("This is from the root logger")
    # 使用我们创建的命名记录器
    log.debug("This is a message from main function")

if __name__ == "__main__":
    main()

运行修改后的代码,你会发现日志输出中包含了模块名(例如__main__),这能帮助我们快速定位日志语句的来源。

在多个模块中记录日志

理解了单个模块中的日志记录后,我们可以在应用程序的其他模块中也添加日志功能。这有助于追踪跨模块的程序执行流程。

假设我们有一个工具模块utility.py,我们想在其中添加日志。

utility.py中:

import logging

# 为这个模块创建一个记录器
log = logging.getLogger(__name__)

def some_function():
    log.info("Loading utility module")
    # ... 其他代码

然而,这里有一个关键点需要注意:日志配置必须在所有日志语句之前设置。如果utility.py中的代码在main.py调用basicConfig()之前就执行了,那么它的日志可能不会被输出。

因此,最佳实践是在程序的主入口点(通常是main()函数)尽早完成日志配置。

# main.py
import logging
import utility

def main():
    # 首先进行日志配置
    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger(__name__)

    log.debug("Starting the application...")
    utility.some_function()
    # ... 其他代码

if __name__ == "__main__":
    main()

这样,所有模块的日志都会按照统一的配置进行输出,并且每条日志都带有其模块名称,使得调试和追踪变得非常清晰。

总结

本节课中我们一起学习了在Python中实现基础日志记录。我们从最简单的logging.basicConfig()开始,了解了如何快速输出日志。接着,我们引入了命名记录器的概念,使用logging.getLogger(__name__)来创建能标识来源的日志器,这比使用根记录器提供了更多的上下文信息。最后,我们探讨了如何在多模块应用程序中组织日志,强调了在主入口点统一配置的重要性。通过这些步骤,你可以为你的Python程序添加有效且易于管理的日志功能。

043:在Rust中使用不同类型的日志

在本节课中,我们将学习如何为Rust命令行工具添加日志功能,特别是如何通过命令行选项来控制日志是输出到终端还是写入到文件。我们将使用clap库来解析命令行参数,并使用env_logger库来配置日志输出目标。

概述

有时,将日志写入文件比输出到终端更有用。本节将演示如何添加一个命令行选项来启用日志文件功能。默认情况下,日志输出到标准错误。我们将添加一个布尔选项,当启用时,日志将被写入文件。

添加日志文件选项

上一节我们介绍了基本的日志配置。本节中,我们来看看如何通过命令行参数动态控制日志的输出目标。

首先,我们需要使用clap库添加一个命令行选项。以下是具体步骤:

  1. 在定义命令行参数的结构体中,添加一个布尔类型的字段。
  2. 为该字段添加描述信息,说明其用途是启用日志文件输出。
  3. 设置其默认值为false,即默认不写入文件。

对应的代码修改如下:

#[derive(Parser)]
struct Opts {
    // ... 其他字段
    /// 启用日志写入文件
    #[clap(long)]
    log_file: bool,
}

实现日志文件功能

添加了选项之后,我们需要在main函数中根据该选项的值来配置日志记录器。

以下是实现逻辑:

  • 如果log_file选项为true,则创建一个文件并将日志记录器的输出目标指向该文件。
  • 我们使用std::fs::OpenOptions来创建或追加写入文件。
  • 通过env_logger::Buildertarget方法,将输出目标设置为一个Box包装的文件句柄。

以下是核心实现代码:

use std::fs::OpenOptions;
use env_logger::Target;

fn main() {
    let opts = Opts::parse();
    // 初始化日志
    let mut builder = env_logger::Builder::from_default_env();

    if opts.log_file {
        // 创建或打开日志文件
        let file = OpenOptions::new()
            .create(true)
            .append(true)
            .open("blocker.log")
            .unwrap();
        // 将日志目标设置为文件
        builder.target(Target::Pipe(Box::new(file)));
    }

    builder.init();
    // ... 程序后续逻辑
}

测试功能

代码修改完成后,我们需要测试新功能是否按预期工作。

以下是测试步骤:

  1. 不带--log-file参数运行程序,日志应正常输出到终端。
  2. 带上--log-file参数运行程序,终端将不再显示日志信息。
  3. 检查当前目录下是否生成了名为blocker.log的文件,并且日志内容已写入该文件。

你可以通过以下命令进行测试:

# 测试终端输出
cargo run -- info video1
# 测试文件输出
cargo run -- --log-file info video1
# 查看日志文件内容
less blocker.log

总结

本节课中我们一起学习了如何在Rust命令行工具中灵活配置日志输出。我们通过clap库添加了一个--log-file选项,并在代码中根据该选项的值,使用env_loggerstd::fs将日志动态地输出到终端或文件。这种方法使得程序更易于调试和监控。如果你的main函数因此变得复杂,可以考虑将这些配置逻辑提取到独立的函数中,以保持代码清晰。你可以继续添加更多日志语句,以便为用户提供更丰富的运行时信息。

044:在Python中使用不同类型的日志

在本节课中,我们将学习如何在Python中配置和使用不同类型的日志处理器,以实现更灵活、更强大的日志记录功能。我们将超越基础的basicConfig方法,探索如何为根日志记录器添加多个处理器,从而将日志信息同时输出到控制台和文件,并设置不同的日志级别。

概述

之前我们使用basicConfig来配置日志记录。虽然这种方法简单,但它存在局限性。本节我们将采用一种更灵活、可扩展的方式,手动配置根日志记录器,并为其添加控制台处理器和文件处理器。这种方法允许我们为不同的输出目标设置不同的日志级别,例如,在文件中记录详细的调试信息,而在控制台仅显示关键的信息。

配置根日志记录器

首先,我们需要获取并配置根日志记录器。根日志记录器是所有其他日志记录器的父级,通过配置它,我们可以影响整个应用程序的日志行为。

import logging

# 获取根日志记录器
root_logger = logging.getLogger()
# 设置根日志记录器的默认级别为DEBUG
root_logger.setLevel(logging.DEBUG)

在上面的代码中,我们使用logging.getLogger()获取了根日志记录器,并将其默认日志级别设置为DEBUG。这意味着所有低于或等于DEBUG级别的日志消息最初都会被捕获。

创建并配置处理器

接下来,我们将创建两个处理器:一个用于控制台输出,另一个用于文件输出。处理器负责将日志记录器产生的消息发送到指定的目的地。

以下是创建和配置这两个处理器的步骤:

1. 控制台处理器

控制台处理器(StreamHandler)将日志消息输出到终端(默认是标准错误输出stderr)。

# 创建控制台处理器
console_handler = logging.StreamHandler()
# 设置控制台处理器的日志级别为INFO
console_handler.setLevel(logging.INFO)

2. 文件处理器

文件处理器(FileHandler)将日志消息写入到指定的文件中。

# 创建文件处理器,指定日志文件名
file_handler = logging.FileHandler('blockpy.log')
# 设置文件处理器的日志级别为DEBUG
file_handler.setLevel(logging.DEBUG)

通过以上设置,我们实现了差异化的日志记录:文件blockpy.log将记录所有DEBUG及以上级别的详细信息,而控制台只显示INFO及以上级别的关键信息。这种配置在开发时非常有用,既保留了完整的调试轨迹,又保持了用户界面的简洁。

将处理器添加到根日志记录器

创建好处理器后,我们需要将它们添加到根日志记录器中,这样日志消息才能被正确地分发到各个处理器。

# 将控制台处理器添加到根日志记录器
root_logger.addHandler(console_handler)
# 将文件处理器添加到根日志记录器
root_logger.addHandler(file_handler)

现在,根日志记录器配置了两种处理器。任何通过这个记录器(或其子记录器)生成的日志消息,都会同时被发送到控制台和日志文件,并根据各自处理器的级别设置进行过滤。

测试配置效果

让我们来测试一下配置是否生效。运行一个简单的日志记录脚本:

# 记录不同级别的日志消息
root_logger.debug('这是一条调试信息。')
root_logger.info('这是一条普通信息。')
root_logger.warning('这是一条警告信息。')

执行上述代码后,你可能会注意到两个现象:

  1. 控制台输出:由于控制台处理器级别为INFO,你将只看到“这是一条普通信息。”和“这是一条警告信息。”,而不会看到DEBUG级别的消息。
  2. 文件输出:打开blockpy.log文件,你会看到所有三条消息(DEBUGINFOWARNING)都被记录在内。

此外,你可能还会发现,日志消息的格式(如时间戳)与使用basicConfig时不同。这是因为我们没有配置格式化器。你可以通过为每个处理器设置formatter属性来自定义日志输出的格式,例如添加时间戳、日志级别和模块名称。

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 将格式化器应用到处理器
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

总结

在本节课中,我们一起学习了如何在Python中构建更高级的日志记录系统。我们首先介绍了根日志记录器的概念,然后逐步创建了控制台处理器和文件处理器,并为它们设置了不同的日志级别。最后,我们将这些处理器添加到根日志记录器,并验证了日志消息能够被正确地分流到不同的输出目标。

这种方法比简单的basicConfig提供了更大的灵活性和控制力,允许你根据需求精细地调整日志行为,例如为不同环境(开发、生产)或不同用户(开发者、最终用户)定制日志输出。你可以进一步探索,例如通过命令行参数动态配置日志级别,或者为不同的模块创建子记录器并分配特定的处理器。

045:在Rust中处理错误 🛠️

概述

在本节课中,我们将学习如何在Rust程序中优雅地处理错误。我们将看到如何避免使用unwrap()导致的程序崩溃,转而使用模式匹配和日志记录来更稳健地管理错误情况。

我们的工具目前已经具备了登录、命令行标志解析等多种功能,运行良好。然而,尽管我们已经设置了将日志输出到文件的能力,但我们尚未利用日志系统来处理程序运行中出现的错误。这是我们现在需要探索和改进的地方。

问题:unwrap()导致的程序崩溃

lib.rs文件中,我们目前使用了一些unwrap()调用。如果不加注意地使用它们,可能会引发问题。为了演示这一点,我们将故意触发一个错误。

具体来说,我们将通过修改系统配置,使lsblk命令返回无效的JSON数据,从而在解析时引发错误。

让我们在终端中运行程序来观察会发生什么:

cargo run --info vd1

程序运行后,虽然日志功能正常,信息也已输出,但程序因为unwrap()而进入了恐慌状态。我们看到了类似“expected ident or , at line 1 column 2”的错误信息。

这是一个典型的JSON解析错误报告,但信息对用户来说并不友好。如果用户看到这个,很难理解问题所在。错误信息指向了lib.rs文件的第28行,这正是我们之前提到的可能出问题的unwrap()调用。

我们知道错误根源是lsblk输出了无效的JSON。问题的具体细节并不重要,关键在于我们遇到了错误,但当前代码没有妥善处理它。

解决方案:使用模式匹配和日志处理错误

既然我们已经在main.rs中设置好了日志构建器,整个应用程序的各个模块都可以使用日志功能,那么我们就可以利用它来处理这里的错误。

我们需要停止使用unwrap(),并转而处理可能发生的错误。以下是修改步骤:

首先,我们将注释掉原来的unwrap()调用,然后使用match表达式来处理serde_json::from_strResult类型。

// 原来的代码
// let devices: Vec<Device> = serde_json::from_str(&output).unwrap();

// 修改后的代码
let devices: Vec<Device> = match serde_json::from_str(&output) {
    Ok(devs) => devs,
    Err(e) => {
        // 处理错误
    }
};

Err分支中,我们有两件事要做:

  1. 使用log crate提供的error!宏记录错误详情。
  2. 返回一个值以停止当前函数的继续执行。

以下是完整的错误处理逻辑:

use log::error; // 需要导入 error 宏

let devices: Vec<Device> = match serde_json::from_str(&output) {
    Ok(devs) => devs,
    Err(e) => {
        error!("解析并序列化 lsblk 输出时出错: {}. 完整输出: {}", e, output);
        return serde_json::json!({}); // 返回一个空的 JSON 对象
    }
};

在上面的代码中:

  • error!宏用于记录错误日志,它会将信息输出到我们配置的日志目标(如文件)。
  • 我们选择返回一个空的JSON对象(serde_json::json!({}))作为错误发生时的默认返回值。当然,你也可以根据业务逻辑返回其他值,例如None或一个默认的Vec

验证改进

现在让我们再次运行程序,看看错误处理是否生效:

cargo run --info vd1

这次,程序没有崩溃。相反,我们在日志中看到了清晰的错误信息:“error parsing and serializing lsblk output: expected ident or , at line 1 column 2. full output: ...”。

我们甚至将导致问题的原始输出(output变量)也记录了下来,这为调试提供了极大的便利。例如,输出中可能包含一个无效的字符“F”,这正是导致JSON解析失败的根源。

通过这种方式,我们优雅地处理了错误:记录了详细的上下文信息供开发者排查,并且让程序得以继续运行或安全地退出当前操作,而不是突然崩溃。

总结

本节课我们一起学习了如何在Rust中处理错误。

  1. 识别问题:我们首先指出了不加甄别地使用unwrap()方法会导致程序在遇到错误时直接崩溃,用户体验差且不利于调试。
  2. 引入解决方案:我们利用Rust强大的match表达式和Result枚举类型,对可能失败的操作(如JSON解析)进行模式匹配。
  3. 优雅处理:在Err分支中,我们使用log crate的error!宏记录下错误的详细信息(包括错误对象本身和原始数据),这比简单的恐慌信息有用得多。
  4. 控制流程:我们通过return语句在发生错误时提前退出函数,并返回一个合理的默认值(如空JSON对象),保持了程序的健壮性。

这种错误处理模式是Rust编写可靠软件的核心实践之一。它要求开发者显式地处理所有可能出现的错误情况,从而使程序更稳定、更易于维护。记住,使用match处理Result,并用好日志,是构建高质量Rust应用的关键步骤。

046:在Python中处理错误 🐍

在本节课中,我们将学习如何在Python程序中处理运行时错误。我们将重点介绍使用 try...except 块来捕获异常,并探讨如何通过日志记录来优雅地处理错误,而不是简单地忽略它们。

概述

Python中处理错误通常采用 try...except 的方式。这种方式允许我们“尝试”执行可能出错的代码,并在发生异常时“捕获”它,从而防止程序崩溃。然而,简单地捕获所有异常而不记录具体信息是一种不良实践。本节我们将学习如何正确、有效地处理错误。

错误处理的常见问题

让我们通过一个终端命令的例子来观察一个典型的错误。我们尝试执行一个会引发异常的命令。

lsblk -J /dev/vda1

执行此命令会引发一个大的异常。这是因为我们在命令中故意添加了无效的参数 -J。在后台,这个错误源于一个Python脚本 utility.py 的第33行,该行尝试解析JSON数据。

不完善的错误处理方式

对于Python初学者,一种常见的错误处理方式是简单地捕获所有异常并打印一条通用信息。

try:
    # 可能出错的代码
    devices = json.loads(output)
except:
    print("There was an error")

这种方式存在几个问题:

  1. 变量 devices 可能未被定义,导致后续代码出现 NameError
  2. 我们完全不知道具体发生了什么错误,丢失了所有调试信息。
  3. 这只是“吞掉”了异常,并没有真正处理问题。

即使我们尝试捕获特定异常并给变量赋一个默认值(如空列表),如果处理不当,依然不是好的做法。

try:
    devices = json.loads(output)
except json.JSONDecodeError:
    devices = []

这种方式虽然避免了程序崩溃,但同样没有记录错误的任何细节,对于调试和问题排查毫无帮助。

改进的错误处理:结合日志记录

上一节我们看到了不完善的错误处理方式,本节我们来看看如何利用Python的 logging 模块进行改进。

logging 模块提供了一个强大的方法 logger.exception(),它不仅能记录我们自定义的错误信息,还能自动捕获并记录完整的异常回溯信息。

以下是改进后的代码示例:

import logging
import json

# 配置日志
logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

try:
    devices = json.loads(output)
except json.JSONDecodeError:
    logger.exception("We weren‘t able to process the JSON output from lsblk.")
    devices = []

当我们再次运行程序时,终端不仅会显示我们的自定义错误信息,还会打印出完整的异常堆栈跟踪。这让我们能清晰地看到错误发生在哪一行、是什么原因导致的。

添加更多有用的上下文信息

我们可以进一步丰富错误日志,添加更多上下文信息,使调试更加容易。

例如,我们可以记录引发错误的原始输出:

try:
    devices = json.loads(output)
except json.JSONDecodeError:
    logger.error(f"We had issues with the output. Output was:\n{output}")
    logger.exception("We weren‘t able to process the JSON output from lsblk.")
    devices = []

这样,日志中就会包含导致解析失败的具体数据,极大地便利了问题复现和修复。

高级配置:将错误日志写入文件

在实际生产环境中,我们可能不希望将所有详细的错误信息都打印到终端上,以免干扰用户。我们可以将日志配置为写入文件。

以下是配置日志写入文件的示例:

import logging

# 创建一个文件处理器
file_handler = logging.FileHandler('app_error.log')
file_handler.setLevel(logging.ERROR)

# 设置日志格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)

# 获取logger并添加处理器
logger = logging.getLogger(__name__)
logger.addHandler(file_handler)

# 现在,logger.exception() 的信息将写入 ‘app_error.log‘ 文件,而不会显示在终端。

通过这种方式,我们可以保持终端输出的整洁,同时将所有错误细节完整地保存在日志文件中供后续分析。

总结

本节课中我们一起学习了在Python中处理错误的正确方法。我们首先了解了简单使用 try...except 并打印通用信息的不良实践及其弊端。接着,我们学习了如何结合 logging 模块,使用 logger.exception() 来捕获和记录完整的异常信息。最后,我们还探讨了如何为错误添加上下文信息以及如何将错误日志重定向到文件中。这是一种为Python应用程序添加健壮错误处理策略的非常有效的方法。

047:时机与意义 🚀

在本节课中,我们将探讨在生成式AI时代,如何将Rust与Python这两种语言进行集成。我们将分析两种语言各自的优势,确定最佳的集成时机与方式,并理解生成式AI如何进一步强化这两种语言的能力。

Python的优势与定位 🐍

上一节我们介绍了课程主题,本节中我们来看看Python语言的核心优势。Python以其可读性庞大的生态系统极高的开发效率而闻名。它被设计为一种以人为本的语言,非常适合快速构建原型、Web应用、命令行工具或脚本。

以下是Python的主要特点:

  • 可读性强:语法简洁明了,易于学习和理解。
  • 生态丰富:拥有海量的第三方库支持,覆盖数据科学、机器学习、Web开发等众多领域。
  • 开发高效:提供极佳的交互式反馈循环,能快速验证想法。
  • 流行度高:是全球最流行的编程语言之一。

Rust的优势与定位 ⚙️

然而,Python也存在一些局限性,而Rust恰好能解决这些问题。Rust是一种现代系统编程语言,它带来了Python所不具备的先进特性。

以下是Rust解决的关键问题:

  • 真正的并发:Rust支持真正的多线程,而Python由于全局解释器锁(GIL)的限制,在多线程CPU密集型任务上效率不高。
  • 内存与线程安全:Rust编译器在编译时就能检查数据竞争等并发错误,确保代码的健壮性。其所有权系统保证了内存安全,无需垃圾回收。
  • 卓越性能:Rust能提供接近机器码级别的运行性能,同时能耗更低。
  • 现代化的包管理:Rust的包管理工具Cargo统一且健壮,解决了Python历史上包管理方案分散的问题。

Rust的公式可以概括为:安全 + 并发 + 性能

集成时机:强强联合 🤝

了解了两种语言的特点后,我们来看看它们的最佳集成点。两者的交集体现在工具链和性能优化领域。

当需要结合两者的优势时,集成变得非常有意义:

  • 性能关键的工具:例如,用纯Python编写的代码检查(Linting)工具可能运行缓慢。将其核心部分用Rust重写(如Ruff工具),性能可提升十倍或更多,这在大型代码库上收益显著。
  • 高性能服务包装:对于推理服务器、Web服务器或其他任何类型的后端服务,用Rust编写核心逻辑能保证高性能与安全,然后通过Python包装提供易用的接口。这利用了Rust的性能和Python的快速开发与丰富生态。

生成式AI带来的新变化 🤖

生成式AI的兴起为这两种语言的集成带来了新的变数。它能够自动生成大量代码(可能高达80%),从而放大了两种语言的核心特性。

  • 对Python的增强:Python本就为人类生产力而设计,生成式AI能进一步提升其开发效率,让开发者更快速地构建应用。
  • 对Rust的增强:Rust虽然学习曲线较陡,但其严格的编译器能有效捕获生成式AI可能产生的“幻觉”代码(如内存错误、数据竞争)。Python没有编译器进行此类深度检查。因此,生成式AI与Rust的结合异常强大——AI负责生成代码草稿,Rust编译器则充当一个极其严格的代码审查员,确保最终产出的代码既高效又安全。

我们可以这样看待这种变化:在生成式AI的辅助下,可读性/开发效率(Python侧)性能/安全性(Rust侧) 的天平发生了微妙的倾斜。如果你正在使用生成式AI辅助编程,那么在代码库中更多地使用Rust开始变得更具吸引力。

总结 📝

本节课中我们一起学习了在生成式AI时代集成Rust与Python的时机与意义。关键要点如下:

  1. Python擅长快速原型开发、拥有丰富生态,是以人为本的高效语言。
  2. Rust在性能、内存安全、真正并发和现代化工具链方面具有绝对优势。
  3. 最佳集成点在于用Rust构建性能关键的核心组件(如工具、服务),并用Python进行包装和快速集成。
  4. 生成式AI放大了两者的优势,尤其与Rust结合时,能通过严格的编译器检查,生产出既高效又安全的可靠代码。

在决定使用哪种语言或如何集成时,应基于项目对性能、安全性和开发速度的具体需求进行权衡。

048:安装与配置 🚀

在本节课中,我们将学习如何搭建PyO3的开发环境。PyO3是一个用于创建Python扩展模块的Rust库,它允许你在Rust中编写高性能的Python代码。我们将从安装必要的工具链开始,一步步配置好开发环境。

环境准备

首先,你需要确保系统中安装了三个核心组件:Rust工具链、Python环境以及一个Python虚拟环境管理工具。以下是具体的检查与安装步骤。

1. 检查Python安装

打开终端,输入以下命令来验证Python是否已安装:

python3 --version

如果系统返回了Python 3的版本号(例如 Python 3.10.6),说明Python已就绪。如果未安装,请访问Python官网下载并安装。

2. 检查Rust安装

接下来,验证Rust是否已安装:

rustc --version

如果看到Rust的版本信息,则安装成功。如果未安装,请访问Rustup官网,按照指引安装Rust工具链。

3. 创建Python虚拟环境

为了隔离项目依赖,我们创建一个Python虚拟环境。一个便捷的技巧是将虚拟环境创建在用户主目录下,便于记忆和管理。

python3 -m venv ~/.venv

如果系统提示 venv 模块未找到,你可能需要先安装 python3-venv 包(例如在Ubuntu上使用 sudo apt install python3-venv)。

创建完成后,激活虚拟环境:

source ~/.venv/bin/activate

为了更便捷,你可以将 source ~/.venv/bin/activate 这行命令添加到你的 ~/.bashrc~/.zshrc 文件中,这样每次打开终端时都会自动激活该环境。

安装构建工具

上一节我们准备好了基础环境,本节中我们来安装项目构建所需的关键工具。

在激活的虚拟环境中,使用 pip 安装 maturinmaturin 是一个用于构建和发布基于PyO3的Rust-Python混合项目的工具。

pip install maturin

创建PyO3项目

工具安装完毕后,现在我们可以创建一个新的PyO3项目。

遵循官方教程,我们使用 maturin 初始化一个名为 pyo3_example 的项目:

maturin init pyo3_example

此命令会创建一个同名目录。进入该目录:

cd pyo3_example

进入项目目录后,再次运行 maturin 命令。它会引导你进行项目设置,这里我们选择默认的PyO3配置即可。

maturin init

项目结构解析

现在我们已经完成了基础设置,接下来看看项目的核心文件结构,理解PyO3是如何连接Python和Rust的。

查看项目根目录下的 src/lib.rs 文件,这是Rust代码的主入口。你可以看到一个基本的PyO3项目结构,其中定义了一个可供Python调用的函数。

同时,查看 Cargo.toml 文件,你会发现它已经自动配置了 pyo3 作为依赖项,并将库类型设置为 cdylib,这是创建Python扩展模块所必需的。

构建与测试

一切配置就绪后,最后一步是构建项目并在Python中测试它。

在项目根目录下,运行以下命令来编译Rust代码并生成Python模块:

maturin develop

命令执行成功后,启动Python解释器:

python

在Python交互环境中,导入我们刚刚构建的模块并测试其功能:

import pyo3_example
pyo3_example.sum_as_string(1, 2)

如果一切正常,你将看到输出 ‘3’。这证明你已经成功地从Python调用了用Rust编写的函数,获得了性能提升。

最后,你可以将本项目添加到版本控制中,例如使用Git:

git init
git add .
git commit -m “Adding pyo3_example project”

总结

本节课中我们一起学习了PyO3的入门安装与配置。我们逐步检查并安装了Rust、Python和虚拟环境,使用 maturin 工具创建并初始化了一个PyO3项目,剖析了项目的基本结构,并最终成功构建模块,在Python中验证了Rust函数的调用。这个过程为你在Rust中编写高性能Python扩展打下了坚实的基础。

049:使用PyO3创建基础Rust库 🦀🐍

在本节课中,我们将学习如何利用PyO3库,将用Rust编写的高性能代码集成到Python项目中。我们将创建一个计算圆周率π的Rust库,并在Python脚本中调用它。


概述

混合使用Python和Rust可以让我们在保持Python开发便捷性的同时,利用Rust在性能和内存安全方面的优势。PyO3是一个强大的工具,它允许我们创建Rust库,并将其作为Python模块直接导入和使用。本节我们将通过一个计算π的示例,一步步演示这个过程。

项目结构

首先,我们来看一个混合了Python和Rust的项目。当你使用cargo new等命令初始化项目时,PyO3会自动生成一些基础结构。项目的核心是一个Rust库文件,它将被编译成Python可以导入的共享对象文件。

Rust函数实现

上一节我们了解了项目结构,本节中我们来看看具体的Rust函数实现。

以下是我们将在Rust中实现的计算π的函数。这个函数接收一个参数,指定计算π的迭代次数,并返回一个f64类型的π的近似值。

use pyo3::prelude::*;

/// 计算圆周率π的近似值
/// `iterations`: 迭代次数,次数越多结果越精确
#[pyfunction]
fn calculate_pi(iterations: u64) -> f64 {
    let mut pi = 0.0;
    let mut denominator = 1.0;
    let mut operation = 1.0;

    for _ in 0..iterations {
        pi += operation * (4.0 / denominator);
        denominator += 2.0;
        operation *= -1.0;
    }
    pi
}

代码解释

  1. 我们定义了一个可变的pi变量来存储结果。
  2. 使用莱布尼茨公式进行迭代计算:π/4 = 1 - 1/3 + 1/5 - 1/7 + ...
  3. 每次迭代,我们根据公式更新pi的值、分母和运算符号。
  4. 循环结束后,返回计算得到的pi值。

创建Python模块

现在,我们需要将这个Rust函数包装成一个Python模块。这是通过PyO3的#[pymodule]宏实现的。

/// 将Rust函数暴露为Python模块
#[pymodule]
fn lib_digits_pi(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(calculate_pi, m)?)?;
    Ok(())
}

代码解释

  1. #[pymodule]宏标记了lib_digits_pi函数,这将是生成的共享对象文件的名字。
  2. 在函数内部,我们使用m.add_function将之前定义的calculate_pi函数添加到模块中。
  3. 这个模块编译后,可以被Python脚本直接import

配置Cargo.toml

项目的依赖和构建配置都在Cargo.toml文件中定义。以下是关键部分:

[package]
name = "lib_digits_pi"
version = "0.1.0"
edition = "2021"

[lib]
name = "lib_digits_pi"
crate-type = ["cdylib"] # 指定生成C动态库,这是Python可导入的关键

[dependencies]
pyo3 = { version = "0.18", features = ["extension-module"] }

配置说明

  • [lib]部分的crate-type = ["cdylib"]至关重要,它指示Cargo生成一个动态链接库(如.so文件),而不是默认的Rust静态库。
  • dependencies中声明了对pyo3库的依赖,并启用了extension-module特性。

构建与清理

为了简化构建流程,我们可以使用一个Makefile。以下是构建和清理的步骤。

以下是Makefile中的关键命令:

build:
    cargo build --release
    cp target/release/liblib_digits_pi.so ./lib_digits_pi.so

clean:
    cargo clean
    rm -f ./lib_digits_pi.so

命令说明

  • make build:使用--release模式编译Rust项目以获得优化性能,然后将生成的共享库文件复制到项目根目录,方便Python导入。
  • make clean:清理cargo构建的产物和复制的共享库文件。建议将生成的.so文件加入.gitignore,不要提交到版本控制。

在Python中调用

最后,我们来看如何在Python脚本中导入并使用我们刚刚构建的Rust模块。

首先,创建一个Python脚本(例如pi.py),并使其可执行:

chmod +x pi.py

脚本内容如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import lib_digits_pi

def main():
    # 调用Rust函数计算π,迭代次数为1,000,000
    pi_approximation = lib_digits_pi.calculate_pi(1_000_000)
    print(f"计算得到的π近似值为: {pi_approximation}")

if __name__ == "__main__":
    main()

脚本解释

  1. 第一行是shebang行,指定用Python 3解释器执行此脚本。
  2. 使用import lib_digits_pi导入我们编译好的Rust模块。
  3. 直接调用模块中的calculate_pi函数,就像调用普通Python函数一样。
  4. 运行脚本:./pi.py,即可看到输出的π的近似值(例如:3.1415916535897743)。

总结

本节课中我们一起学习了使用PyO3桥接Rust与Python的全过程。我们从编写一个计算π的Rust函数开始,将其封装为Python模块,配置项目并完成构建,最后在Python脚本中成功调用。这种方法非常适合将计算密集型或内存敏感的任务用Rust实现,同时享受Python生态的灵活性与易用性。通过Docker等工具,你可以轻松地将这种混合应用打包和部署。

050:Rust到Python 🦀➡️🐍

在本节课中,我们将学习如何将Rust中的数据类型转换为Python数据结构。这是一个在Python和Rust之间进行交互时非常常见的任务,尤其适用于将Rust中计算密集型操作的结果传递给Python环境使用。

项目结构与核心概念

首先,我们来看一个具体的项目示例。项目的核心逻辑位于 src/lib.rs 文件中,在一个名为 data_conversion 的模块内。

以下是该模块的导入部分,它引入了必要的PyO3库,使我们能够在Rust中操作Python对象。

use pyo3::prelude::*;
use pyo3::types::PyDict;

上一节我们介绍了项目的基本结构,本节中我们来看看具体的函数实现。

核心函数实现

我们定义了一个名为 data_types_example_py 的Python函数,它负责执行主要的转换工作。该函数返回一个 PyResult 对象,这是PyO3中用于处理可能出错的Python对象返回值的类型。

#[pyfunction]
fn data_types_example_py() -> PyResult<&PyDict> {
    // 函数体将在下文展开
}

在函数内部,我们首先定义了一些Rust原生数据类型。

let text = "Hello from Rust!".to_string(); // Rust字符串
let integer = 42_i32;                       // 32位有符号整数
let floating = 3.14159_f64;                 // 64位浮点数
let boolean = true;                         // 布尔值

接下来,我们将在Rust中创建一个Python字典,并将上述Rust数据插入其中。

创建并填充Python字典

为了在Rust中构建一个Python字典,我们使用 Python::with_gil 来获取Python的全局解释器锁(GIL),这是安全操作Python对象所必需的。

Python::with_gil(|py| {
    let dict = PyDict::new(py);
    // 接下来填充字典
})

获取字典对象后,我们需要将Rust数据逐一插入。以下是向字典中添加键值对的方法。

dict.set_item("text", text)?;
dict.set_item("integer", integer)?;
dict.set_item("floating", floating)?;
dict.set_item("boolean", boolean)?;
Ok(dict)

set_item 方法用于插入键值对。每对键值都对应我们之前定义的Rust变量。? 操作符用于传播可能发生的错误。

将函数暴露为Python模块

为了使这个Rust函数能被Python调用,我们需要将其包装在一个模块中并导出。

#[pymodule]
fn lib_data_conversion(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(data_types_example_py, m)?)?;
    Ok(())
}

#[pymodule] 宏将 lib_data_conversion 函数标记为模块的入口点。m.add_function 将我们定义的 data_types_example_py 函数添加到模块中,使其在Python中可用。

构建与编译项目

为了将Rust代码编译成Python可以导入的共享库,我们需要使用Cargo进行构建。一个 Makefile 可以简化这个过程。

以下是构建命令的示例:

build:
    cargo build --release
    cp target/release/libdata_conversion.so ./

运行 make build 命令会执行两个步骤:

  1. 使用 cargo build --release 以发布模式编译Rust项目。
  2. 将生成的 .so 共享库文件复制到当前工作目录。

在Python中调用Rust函数

构建成功后,我们可以在Python脚本中导入并使用这个Rust模块。

首先,导入以 lib 开头的共享库文件(不包含 .so 后缀)。

import libdata_conversion

然后,直接调用我们在Rust中暴露的函数。

result = libdata_conversion.data_types_example_py()
print(result)

执行这段Python代码,将会输出一个包含我们定义的文本、整数、浮点数和布尔值的Python字典。

总结

本节课中我们一起学习了如何将Rust数据类型转换为Python数据结构。整个过程可以概括为以下几个关键步骤:

  1. 在Rust中定义函数并使用PyO3库。
  2. 在函数内部创建Python字典并插入Rust数据。
  3. 将函数包装为Python模块。
  4. 使用Cargo将Rust代码编译为 .so 共享库。
  5. 在Python中导入该库并调用函数。

这种方法非常适用于将Rust中执行的高性能计算或复杂操作的结果,无缝传递给Python生态系统进行后续处理或展示。

051:理解PyO3中的Rust所有权模型 🦀

在本节课中,我们将学习如何通过PyO3库,将Rust独特的所有权模型与Python结合使用。我们将通过一个具体的项目示例,展示如何在Python脚本中操作Rust的数据结构,并理解其背后的所有权和可变性规则。

概述

Rust以其独特的所有权系统而闻名,该系统确保了内存安全和无数据竞争。然而,Python的动态特性意味着其变量默认是可变的。通过PyO3,我们可以将Rust的安全特性暴露给Python,从而在Python环境中利用Rust的强类型和所有权优势。本节将解析一个名为ownership_py_rust的项目,演示如何实现这一集成。

项目结构解析

首先,让我们查看项目的核心代码文件src/lib.rs。以下是该文件的主要内容及其功能。

Rust结构体与实现

我们使用#[pyclass]宏定义了一个可供Python使用的Rust结构体。

#[pyclass]
struct NumbersList {
    numbers: Vec<i32>,
}

这个结构体包含一个i32类型的向量。接下来,我们为其实现方法。

#[pymethods]
impl NumbersList {
    #[new]
    fn new() -> Self {
        NumbersList { numbers: Vec::new() }
    }

    fn add_number(&mut self, number: i32) {
        self.numbers.push(number);
        println!("插入数字: {}", number);
    }

    fn len(&self) -> usize {
        self.numbers.len()
    }

    fn clear(&mut self) {
        self.numbers.clear();
        println!("列表已清空");
    }
}

在上面的代码中,我们实现了几个关键方法:

  • new: 构造函数,用于创建新的NumbersList实例。
  • add_number: 向列表中添加一个数字。注意其参数&mut self,这明确表示该方法需要可变引用来修改结构体内部数据。这是Rust所有权模型的核心体现,与Python的隐式可变性形成对比。
  • len: 返回列表当前长度。它使用&self不可变引用,因为此操作不修改数据。
  • clear: 清空列表。同样需要可变引用&mut self

创建Python模块

最后,我们需要将这些Rust代码包装成一个Python可以导入的模块。

#[pymodule]
fn ownership_py_rust(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<NumbersList>()?;
    Ok(())
}

#[pymodule]宏定义了一个名为ownership_py_rust的模块,并将NumbersList类添加到其中。这是使用PyO3向Python暴露功能的经典方式。

构建与配置

上一节我们介绍了核心的Rust代码,本节中我们来看看如何构建项目以及Python端如何调用。

Cargo.toml 配置

项目的Cargo.toml文件定义了库的名称和依赖。

[lib]
name = "ownership_py_rust"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.15", features = ["extension-module"] }

关键配置是crate-type = ["cdylib"],这指示Cargo构建一个动态链接库(如.so文件),供Python加载。

Makefile 构建步骤

项目包含一个Makefile,其中定义了构建命令,用于编译Rust库并将其复制到合适的位置以便Python脚本导入。

Python脚本示例

现在,让我们查看如何使用这个已编译的Rust模块的Python脚本owner.py

以下是脚本的主要操作步骤:

import ownership_py_rust

# 1. 创建 NumbersList 实例
list_instance = ownership_py_rust.NumbersList()

# 2. 向实例中添加数字
list_instance.add_number(5)
list_instance.add_number(10)

# 3. 获取并打印列表长度
print(f"列表长度: {list_instance.len()}")

# 4. 清空列表
list_instance.clear()

# 5. 再次验证列表长度
print(f"清空后列表长度: {list_instance.len()}")

这个脚本清晰地演示了集成流程:

  1. 导入模块:导入我们编译好的ownership_py_rust模块。
  2. 创建实例:实例化NumbersList类。
  3. 调用方法:调用add_number方法修改列表,调用len方法获取信息,调用clear方法清空列表。所有这些操作都通过PyO3桥接到我们之前编写的Rust代码上执行。

运行与验证

要运行此示例,首先需要构建Rust库(例如通过make build),然后执行Python脚本。

运行python owner.py后,预期输出如下:

插入数字: 5
插入数字: 10
列表长度: 2
列表已清空
清空后列表长度: 0

输出结果验证了:

  • Rust代码(包括println!语句)在Python环境中成功执行。
  • 数据在Rust和Python之间正确传递和修改。
  • Rust的所有权和可变性规则在后台得到了严格执行(例如,通过&mut self确保安全修改)。

总结

本节课中我们一起学习了如何利用PyO3将Rust的所有权模型集成到Python中。我们通过一个实际项目,看到了如何:

  1. 使用#[pyclass]#[pymethods]宏定义Python可用的Rust类和其方法。
  2. 在Rust方法中显式声明可变性(&mut self),这是Rust安全模型的关键。
  3. 使用#[pymodule]宏创建Python模块。
  4. 在Python脚本中导入并使用该模块,像操作普通Python对象一样操作Rust数据结构。

这种集成方式非常直接,它允许你将Rust为安全性和不可变性而设计的强大功能,通过严格控制的接口暴露给Python脚本。这对于构建需要高安全性或高性能计算的库,并希望其核心逻辑能被Python灵活调用的场景非常有价值。

052:PyO3项目架构图解 🧩

在本节课中,我们将学习如何构建一个结合Rust高性能计算与Python便捷性的项目。我们将通过一个具体的PyO3项目架构图,来理解如何将Rust代码封装为Python模块,并使用Python Fire库轻松创建命令行工具。


项目架构概述

上图展示了一个典型的PyO3(Rust与Python交互)项目结构。这种模式适用于需要将计算密集型逻辑(如数值运算、复杂数据处理或机器学习运维任务)用Rust实现,同时保留Python的易用性和丰富生态的项目。

Rust核心逻辑部分

上一节我们介绍了项目的整体架构,本节中我们来看看Rust部分的具体实现。

Rust代码负责实现核心的重型逻辑。这利用了Rust作为全球性能最佳语言之一的优势。在Rust代码中,你需要构建业务逻辑,然后通过几行代码将其暴露为Python模块。

以下是构建Python模块的关键代码示例:

#[pymodule]
fn lib_pcalc_ci(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_numbers, m)?)?;
    m.add_function(wrap_pyfunction!(add_string, m)?)?;
    m.add_function(wrap_pyfunction!(subtract_string, m)?)?;
    m.add_function(wrap_pyfunction!(divide_string, m)?)?;
    Ok(())
}

在这个计算器示例中,我们构建了四个函数:sum_numbersadd_stringsubtract_stringdivide_string。每个函数的功能都清晰明了。

构建与自动化

一旦Rust代码编写完成,下一步是构建项目并将其集成到Python环境中。手动操作(如复制共享对象文件)可能很繁琐,因此使用Makefile来自动化这一流程是明智的选择。

以下是推荐的Makefile命令:

build:
    cargo build --release
    cp target/release/libpcalc_ci.so ./pcalc_ci.so

运行make build命令可以自动完成Rust代码的编译,并将生成的共享库文件(如libpcalc_ci.so)复制到指定位置,为Python调用做好准备。

Python集成与命令行工具

现在,我们已经准备好了Rust模块,接下来看看如何将其集成到Python应用中。我推荐使用Google的Python Fire库,它可以极大地简化命令行工具的创建过程。

Python Fire库能够自动将Python类和函数转化为命令行接口,无需编写任何样板代码。以下是如何使用它:

首先,创建一个Python类来封装Rust模块的功能:

import fire
import pcalc_ci  # 导入我们编译好的Rust模块

class Calculator:
    def add(self, num1, num2):
        return pcalc_ci.add_string(num1, num2)
    
    def subtract(self, num1, num2):
        return pcalc_ci.subtract_string(num1, num2)
    
    def divide(self, num1, num2):
        return pcalc_ci.divide_string(num1, num2)

if __name__ == '__main__':
    fire.Fire(Calculator)

通过fire.Fire(Calculator)这一行代码,Calculator类中的所有方法就都变成了命令行命令。

工作流总结

本节课中我们一起学习了构建PyO3项目的完整工作流。

我认为,最有效的Rust与Python集成公式是:让Rust负责其擅长的重型计算任务,让Python负责其擅长的快速开发和生态集成

通过结合Python Fire与Rust,你可以用极少的代码创建一个高性能的命令行工具。例如,在终端中你可以这样使用:

./calculator.py add 2 2
./calculator.py subtract 5 2

这种工作流充分发挥了两种语言的优势,对于希望提升性能的Python项目来说,是一个极佳的组合,我强烈推荐采用。

053:使用Python Fire和Rust编写计算器CLI 🧮

在本节课中,我们将学习如何结合Rust和Python的优势,创建一个命令行计算器工具。我们将使用Rust编写高性能的计算核心,然后利用Python的Fire库轻松构建一个命令行界面。

概述

本教程将展示一个名为pcalc的Rust-Python混合项目。其核心思想是:用Rust编写高性能的计算逻辑,用Python构建易用的命令行接口。这种方法结合了Rust的执行效率与Python的开发便捷性。

项目结构

以下是该项目的整体工作流程示意图。

首先,你需要在Rust中构建所需的核心部件。在本例中,核心部件是计算功能。这可以是任何内容,例如使用第三方库、执行MLOps代码,或是编写某种在Rust中运行效率极高的自定义算法。

接着,你需要将这些功能放入add等函数中,并将它们全部封装起来。

完成上述步骤后,我倾向于使用一个Makefile来将.so库文件构建到正确的目录中。我推荐的最佳实践之一是使用像Python Fire这样的库来包装它。

这样一来,你几乎不需要编写任何额外代码就能让你的程序运行起来。然后,你就可以从终端调用这些Rust代码了。这真正结合了Python和Rust两者的优点。

理论部分就到这里,接下来我们进入演示环节。

代码详解

现在,让我们深入这个示例项目pcalc。我们首先要查看的是源代码。

Rust核心库

如果我进入lib.rs文件,会看到一个计算器函数。我首先使用了pyo3库,然后包装了一系列Rust代码。

这是一个传统的Rust函数,但我为其添加了#[pyfunction]属性,使其能够被pyo3调用。

请注意,我对add_stringsubtract_stringdivide_string等函数也做了同样的处理。最后,在构建.so库时,需要通过#[pymodule]来暴露这些函数。

以下是关键代码结构示意:

use pyo3::prelude::*;

#[pyfunction]
fn add_string(a: &str, b: &str) -> PyResult<String> {
    // Rust计算逻辑
    let result = a.parse::<i32>()? + b.parse::<i32>()?;
    Ok(result.to_string())
}

#[pymodule]
fn pcalc(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(add_string, m)?)?;
    // ... 暴露其他函数
    Ok(())
}

构建脚本(Makefile)

接下来,我们看看Makefile。在这里,我定义了一个build命令。这样做的好处是,原始的构建命令通常很繁琐,而使用Makefile后,我只需记住make build即可。

Makefile中的命令依次执行以下操作:

  1. 运行cargo build --release编译Rust项目。
  2. 使用cbindgen生成C语言头文件(如果需要)。
  3. 将编译好的动态库(如libpcalc.so)复制到当前工作目录。

Python接口层

完成上述设置后,我需要做的最后一件事是进入calc目录,实际使用Python Fire库。

首先,查看requirements.txt文件,其中固定了Python Fire的版本(例如0.5.0)。固定版本是个好习惯,可以确保创建可复现的示例。

回到calc目录下的Python脚本,我在这里导入那些Rust函数。这个过程非常直接,就像导入任何常规的Python模块一样。

最后,我利用了Python Fire CLI库的强大功能。这是一个非常精巧的库,因为我只需要在这里创建一个名为Calculator的类,然后将那些Rust函数作为这个类的方法来使用。

我在此创建了一个清晰的抽象层,定义了addsubtractmultiplydivide等方法。美妙之处在于,一旦我将这个类传递给fire.Fire()Calculator就能通过终端被调用。

我认为这是Rust与Python集成的绝佳方式。下面是一个简化的Python包装器示例:

import fire
from pcalc import add_string, subtract_string # 导入Rust编译的函数

class Calculator:
    def add(self, a, b):
        return add_string(str(a), str(b))
    def subtract(self, a, b):
        return subtract_string(str(a), str(b))
    # ... 其他方法

if __name__ == '__main__':
    fire.Fire(Calculator)

运行演示

我已经使用过这个工具了,但让我们再演示一次加深理解。

在终端中,我们可以这样使用:

# 调用加法
calc add 2 2
# 调用减法,例如5减2
calc subtract 5 2

设计理念回顾

如果我们回顾最初的结构图,审视其中发生的一切,我认为这是一种更为精巧的Rust与Python集成方式。

其核心是利用各自所长

  • 发挥Rust的优势:计算性能、能效和内存安全。
  • 发挥Python的优势:构建优雅的抽象层和便捷的工具链。

通过将两者结合,我们有望在未来看到许多新兴的数据工程和MLOps工作流采用这种模式。

总结

本节课中,我们一起学习了如何构建一个Rust-Python混合的命令行计算器。我们了解了如何使用pyo3将Rust函数暴露给Python,如何用Makefile自动化构建流程,以及如何用Python Fire库快速创建命令行接口。这种架构充分利用了Rust的高性能和Python的易用性,为开发高效的命令行工具提供了强大范式。

054:异常、转换与属性 🚀

在本节课中,我们将学习PyO3的核心高级特性。PyO3是一个强大的库,它允许Rust和Python代码无缝地协同工作。我们将重点探讨三个关键方面:如何在Rust中处理Python异常、如何在两种语言间转换数据类型,以及如何操作Python对象的属性。


概述

PyO3提供了多种功能,使开发者能够将Rust的高性能与Python的易用性和庞大生态系统结合起来。我们将逐一探索这些功能,理解它们如何在实际项目中发挥作用。


Rust扩展Python 🛠️

首先,我们来看看PyO3如何允许开发者用Rust编写Python扩展。

这意味着你可以编写高性能且安全的代码,并能在Python环境中无缝使用它。此外,使用Rust进行打包通常效果极佳。

以下是Rust扩展Python的几个关键点:

  • 允许开发者使用Rust编程语言编写Python扩展。
  • 可以编写高性能的安全代码。
  • 能在Python环境中无缝使用。
  • 使用Rust时,打包效果通常很好。

Rust调用Python 📞

上一节我们介绍了用Rust扩展Python,本节中我们来看看Rust如何调用Python代码。

这样做的原因有很多:Python代码通常易于阅读和理解;Python拥有大量遗留代码,你可能需要与之交互;并且由于Python已有30多年的历史,其库生态系统非常广泛和流行。

因此,允许Rust代码直接调用Python,使你能够执行诸如调用脚本、函数以及操作Python对象等任务。

PyO3也提供了处理原生类型的能力。对于原生类型,你可以使用PyDictPyListPyString等。这本质上是对你熟悉的Python类型的封装,你可以将它们当作原生的Rust类型来使用。这为Rust和Python之间的数据转换提供了一种无缝的方式,让你感觉像在家一样熟悉。

以下是Rust调用Python的几个关键点:

  • Python代码可读性强,易于理解。
  • Python拥有大量遗留代码。
  • Python的库生态系统非常广泛和流行。
  • 允许Rust代码直接调用Python脚本和函数。
  • 可以操作Python对象。
  • 可以使用PyDictPyListPyString等封装类型。
  • 这些类型可以在Rust中作为原生类型使用。
  • 这为两种语言间的数据转换提供了无缝方式。

处理异常 ⚠️

之前我们探讨了双向调用,现在让我们关注一个重要的集成方面:异常处理。

例如,根据你所做的事情,在Rust中首先构建Python异常处理可能是一个好主意。PyO3提供了从Rust中处理这些Python异常的工具。

这可能意味着将Python异常传播到Rust,然后从Rust代码中引发该Python异常。你可以用这个功能做很多事情,例如,创建一个场景:一个Rust函数调用了一个会引发异常的Python函数,然后你可以在Rust中处理这个异常。

通过将Rust和Python结合使用,这里有很多集成功能。事实上,它可以合法地替代历史上许多通过C语言完成的Python交互。Rust是一种现代编译语言,并且与生成式AI编码工具配合得非常好。

以下是处理异常的几个关键点:

  • 可以在Rust中构建Python异常处理逻辑。
  • PyO3提供了从Rust处理Python异常的工具。
  • 可以将Python异常传播到Rust。
  • 可以从Rust代码中引发Python异常。
  • 例如,Rust函数可以调用引发异常的Python函数,并在Rust中处理。
  • 结合使用Rust和Python具有许多集成优势。
  • 它可以替代许多历史上用C语言完成的Python交互。
  • Rust是一种现代编译语言。
  • Rust与生成式AI编码工具配合良好。

总结

本节课中,我们一起深入探索了PyO3的高级特性。我们学习了如何用Rust编写高性能的Python扩展,如何让Rust代码调用并操作Python对象和函数,以及如何在两种语言间无缝地处理和传播异常。这些功能使得Rust和Python能够强强联合,充分利用双方的优势来构建更强大、更安全的应用程序。

055:使用PyO3进行异常处理 🐍

在本节课中,我们将学习如何在Rust和Python的混合编程环境中处理异常。具体来说,我们将探讨如何使用PyO3库在Rust代码中引发Python异常,以及如何在Python中捕获这些异常。

概述

在Rust和Python的交互中,错误处理是一个需要特别注意的环节。Rust使用Result类型来处理错误,而Python则使用异常机制。PyO3库提供了桥梁,允许我们将Rust的错误转换为Python的异常,反之亦然。本节我们将通过一个具体的例子来演示这一过程。

在Rust中引发Python异常

上一节我们介绍了Rust和Python在错误处理机制上的差异。本节中我们来看看如何在Rust代码中主动引发一个Python异常。

在PyO3中,你可以使用PyErr::new::<ExceptionType>PyErr::from_type来创建一个Python异常对象,然后使用.raise()方法在Python中引发它。

以下是创建和引发异常的核心代码示例:

use pyo3::exceptions::PyZeroDivisionError;
use pyo3::prelude::*;

#[pyfunction]
fn divide(a: i32, b: i32) -> PyResult<i32> {
    if b == 0 {
        // 创建并引发一个Python的ZeroDivisionError异常
        return Err(PyZeroDivisionError::new_err("除数不能为零"));
    }
    Ok(a / b)
}

在Python中处理Rust引发的异常

了解了如何在Rust中引发异常后,接下来我们看看如何在Python端捕获并处理这些异常。

在Python中,你可以使用标准的try...except块来捕获从Rust函数中引发的异常。PyO3确保Rust的Err结果会被正确地转换为Python异常。

以下是调用Rust函数并处理异常的Python代码:

import lib_rust_exceptions

try:
    result = lib_rust_exceptions.divide(10, 0)
    print(f"结果是: {result}")
except ZeroDivisionError as e:
    print(f"捕获到异常: {e}")
    # 这里可以执行你的错误处理逻辑

实战演练:一个完整的例子

现在,让我们通过一个完整的项目来实践上述概念。我们将创建一个名为rust_excepts的Rust库,并在Python中调用它。

首先,我们来看Rust库的源代码结构。以下是lib.rs文件的内容:

use pyo3::exceptions::PyZeroDivisionError;
use pyo3::prelude::*;

/// 一个会引发Python异常的除法函数
#[pyfunction]
fn divide(a: i32, b: i32) -> PyResult<i32> {
    if b == 0 {
        // 当除数为零时,返回一个Python的ZeroDivisionError
        return Err(PyZeroDivisionError::new_err("触发来自Rust的异常:除数不能为零"));
    }
    Ok(a / b)
}

/// 将Rust函数暴露给Python的模块
#[pymodule]
fn lib_rust_exceptions(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(divide, m)?)?;
    Ok(())
}

接下来,我们需要构建这个Rust库。通常,我们会使用一个Makefile或构建脚本来完成编译和复制共享库文件的工作。构建成功后,会生成一个.so(Linux)或.pyd(Windows)文件。

然后,在Python中,我们可以像导入普通模块一样导入这个Rust库:

# py_div.py
import lib_rust_exceptions

print("测试正常除法:")
try:
    result = lib_rust_exceptions.divide(5, 1)
    print(f"5 / 1 = {result}")
except ZeroDivisionError as e:
    print(f"异常: {e}")

print("\n测试除数为零:")
try:
    result = lib_rust_exceptions.divide(5, 0)  # 这将引发异常
    print(f"5 / 0 = {result}")
except ZeroDivisionError as e:
    print(f"捕获到异常: {e}")
    # 在这里执行你的错误恢复逻辑

运行这个Python脚本,你将看到以下输出:

测试正常除法:
5 / 1 = 5

测试除数为零:
捕获到异常:触发来自Rust的异常:除数不能为零

总结

本节课中我们一起学习了如何使用PyO3在Rust和Python之间进行异常处理。我们掌握了两个核心技能:

  1. 在Rust中引发Python异常:通过创建PyErr并使用.raise()方法,我们可以将Rust中的错误条件转换为Python的异常。
  2. 在Python中处理Rust异常:使用标准的try...except块,可以无缝地捕获和处理从Rust函数中传播过来的异常。

通过这种机制,我们能够在保持Rust代码安全性和表现力的同时,与Python的异常生态系统完美集成,编写出健壮的混合语言应用程序。

056:从Rust调用Python 🐍➡️🦀

在本节课中,我们将学习如何使用PyO3库,在Rust项目中导入并调用Python代码。这使你能够结合Rust的生态系统优势(如出色的包管理、工具链、安全性和性能)与Python丰富的库资源。

上一节我们介绍了Rust与Python交互的基本概念,本节中我们来看看如何在Rust代码中具体执行Python操作。

项目结构与依赖

首先,我们来看一个示例项目的结构。该项目包含一个Cargo.toml文件和一个main.rs源文件。在Cargo.toml中,我们导入了pyo3库。

以下是main.rs中的核心代码步骤:

代码逐步解析

1. 导入与准备

第一步是导入PyO3的预导入模块,并准备Python解释器环境。

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {
    // 准备Python解释器(支持多线程)
    Python::with_gil(|py| {
        // 代码将在此作用域内执行
        Ok(())
    })
}

Python::with_gil会获取Python的全局解释器锁(GIL),确保线程安全。

2. 传递数据并调用Python内置函数

接下来,我们创建一个Rust向量(类似Python列表),并将其传递给Python的sum函数进行计算。

let values = vec![1, 2, 3];
println!("我们将传递这些值给Python进行求和:{:?}", values);

let sum: i32 = py.eval(&format!("sum({:?})", values), None, None)?.extract()?;
println!("求和结果是:{}", sum);

这里,py.eval用于执行一段Python代码字符串,并提取返回结果。

3. 导入Python标准库

除了内置函数,我们还可以直接导入Python的标准库模块。

let os = py.import("os")?;
let user: String = os.getattr("getenv")?.call1(("USER",))?.extract()?;
println!("当前用户是:{}", user);

这段代码导入了Python的os模块,并调用其getenv函数获取环境变量USER的值。

开发与构建实践

在开发过程中,保持良好的实践非常重要。以下是推荐的步骤:

代码检查与格式化

使用cargo clippy进行代码检查,使用cargo fmt进行代码格式化,可以确保代码质量和风格统一。

# 代码检查
cargo clippy
# 代码格式化
cargo fmt

运行项目

你可以直接使用cargo run来编译并运行项目。

cargo run

运行结果

执行上述程序后,你将在终端看到类似以下的输出:

我们将传递这些值给Python进行求和:[1, 2, 3]
求和结果是:6
当前用户是:vscode

这表明我们成功地在Rust中调用了Python代码,完成了数据传递、函数计算以及标准库的使用。

本节课中我们一起学习了如何在Rust项目中集成Python。我们通过PyO3库,逐步实现了导入Python解释器、调用内置函数、使用标准库模块等功能。这种结合方式让你既能享受Rust的性能与安全,又能利用Python庞大的生态系统,是构建复杂应用的有效策略。

057:在Rust中嵌入Python 🐍

在本节课中,我们将学习如何在Rust程序中嵌入并执行Python代码。通过使用PyO3库,我们可以将现有的Python脚本或业务逻辑集成到Rust应用程序中,从而结合Python的灵活性与Rust的性能和安全性。这种方法特别适用于构建命令行工具、微服务或无服务器函数。


概述与准备工作

上一节我们介绍了Rust与外部系统交互的基本概念。本节中,我们来看看如何具体地在Rust代码中嵌入一个Python解释器来执行Python脚本。

首先,我们需要一个项目结构。以下是一个典型的项目布局,包含必要的配置文件和脚本。

embedded_python/
├── Cargo.toml
├── Makefile
└── src/
    └── main.rs

以下是各文件的核心内容说明。

项目配置文件

我们需要在Cargo.toml中声明对pyo3库的依赖。

[package]
name = "embedded_python"
version = "0.1.0"
edition = "2021"

[dependencies]
pyo3 = { version = "0.18", features = ["auto-initialize"] }

构建辅助文件

Makefile文件用于定义一些便捷的命令,以建立高效的开发反馈循环。它通常包含代码格式化、代码检查和运行命令。

format:
	cargo fmt

lint:
	cargo clippy

run:
	cargo run

all: format lint run

编写Rust嵌入Python代码

现在,让我们进入核心部分,编写在Rust中调用Python逻辑的代码。

首先,我们需要导入必要的类型和模块。PyO3提供了与Python交互所需的所有工具。

use pyo3::prelude::*;
use pyo3::types::PyString;

接下来,我们定义一个Rust函数,它接收一个字符串参数,并将这个参数传递给内嵌的Python代码进行处理。

该函数的主体遵循一个标准模式:首先准备一个可多线程运行的Python环境,然后加载并执行我们的Python代码片段。

fn call_python_script(input: &str) -> PyResult<String> {
    // 准备一个“自由线程”的Python解释器,允许在多线程环境中运行
    Python::with_gil(|py| {
        // 从字符串代码创建Python模块
        let code = r#"
def marco(input):
    if input == "Marco":
        return "Python"
    else:
        return "No Python"
"#;
        let module = PyModule::from_code(py, code, "", "")?;
        
        // 获取模块中的`marco`函数并调用它
        let func = module.getattr("marco")?;
        let result = func.call1((input,))?;
        
        // 将Python的返回结果转换为Rust的String
        result.extract::<String>()
    })
}

在上面的代码中:

  1. Python::with_gil获取Python的全局解释器锁(GIL),这是与Python交互所必需的。
  2. 我们定义了一个简单的Python函数marco,它根据输入返回不同的字符串。
  3. 通过PyModule::from_code,我们将字符串形式的Python代码编译成一个模块。
  4. 然后,我们获取这个模块中的marco函数,并用Rust传入的参数调用它。
  5. 最后,我们将Python函数的返回值提取为Rust的String类型。

调用与运行

定义了核心函数后,我们在Rust的main函数中调用它,并打印结果。

fn main() {
    println!("从Rust调用嵌入的Python...");
    
    match call_python_script("Marco") {
        Ok(result) => println!("结果: {}", result), // 应输出“Python”
        Err(e) => eprintln!("错误: {}", e),
    }
    
    match call_python_script("Pollo") {
        Ok(result) => println!("结果: {}", result), // 应输出“No Python”
        Err(e) => eprintln!("错误: {}", e),
    }
}

使用我们之前在Makefile中定义的命令,可以轻松地运行整个程序。

在终端中执行:

make run

或者直接使用:

cargo run

如果一切正确,你将在终端看到如下输出:

从Rust调用嵌入的Python...
结果: Python
结果: No Python

工作流程与优势

让我们回顾一下这个高效的工作流程。以下是开发过程中可以遵循的步骤:

  1. 编写代码:在Rust中编写嵌入Python的逻辑。
  2. 代码格式化:使用cargo fmtmake format来保持代码风格一致。
  3. 代码检查:使用cargo clippymake lint进行静态分析,捕捉潜在问题。
  4. 运行测试:使用cargo runmake run执行程序,验证功能。

这种将Python嵌入Rust的方法为Python开发者提供了强大的优势:

  • 利用现有代码:可以复用经过充分测试的Python脚本或库。
  • 享受Rust生态:能够利用Rust在性能、内存安全和并发方面的优势。
  • 严格的开发环境:Rust编译器严格的检查机制,结合格式化器和linter工具,能在生成式AI辅助编程时提供高质量的反馈,有助于构建更健壮的应用。

总结

本节课中我们一起学习了如何在Rust中嵌入Python代码。我们了解了PyO3库的基本用法,掌握了将Python脚本作为字符串嵌入到Rust函数中并执行的关键步骤。通过一个简单的“Marco Polo”游戏示例,我们看到了数据如何在两种语言间安全传递。最后,我们探讨了结合使用Makefile、代码格式化器和linter来建立高效、严格的Rust开发工作流程的价值。这种方法让开发者既能利用Python的快速原型能力和丰富库生态,又能获得Rust带来的系统级性能与安全性,是开发现代应用程序的一个强大技术组合。

058:基于Clap的嵌入式Python Rust CLI架构图解

在本节课中,我们将学习如何构建一个结合了Rust和Python的生产级命令行工具。我们将重点介绍使用Clap框架为嵌入式Python代码创建命令行界面,并探讨测试与自动化交付的完整工作流程。

从嵌入式Python到命令行界面

上一节我们介绍了在Rust中嵌入Python代码的基本方法。本节中我们来看看如何为其添加一个命令行界面,使其成为一个真正的生产工具。

这里有一个使用pyo3嵌入Python的Rust工具。Rust作为主框架调用Python。你可以看到,我嵌入了一个Python函数,它接收一个输入。这个函数很简单:如果输入的字符串匹配“Marco”,就返回“Python”;否则,返回“no Python”。这段代码虽然简单,但它通过pyo3进行了封装。这部分在某种程度上是直接明了的。

但许多在生产环境中工作的人可能会考虑的下一步,是让这个工具成为一个真正的生产工具。我认为,对于大多数接口来说,下一步是创建一个命令行界面。在Rust语言中,有许多优秀的命令行工具框架。事实上,它们允许你进行非常复杂的操作,比如二进制部署——这是我最喜欢的部署方式之一。因为你不需要告诉任何人如何打包工具,只需给他们一个二进制文件即可执行。这在某种意义上,弥补了Python的一些局限性,即Python无法进行二进制部署,它是一种需要解释器的脚本语言。

在这个场景中,如果我使用一个CLI框架(例如我使用的Clap)将其包装起来,会发生什么呢?我可以在交互模式下运行并传入“Marco”这个词。这样做的好处是,我可以获得即时反馈循环。这是一种很好的开发方式,因为我利用了Rust的Cargo系统的强大功能,这确实是该语言在打包方面的优势之一,并弥补了Python的弱点(Python有许多相互竞争的打包方案)。

为CLI工具添加测试

在获得命令行界面之后,下一步还需要考虑如何测试你的应用程序。通常,我们需要进行某种形式的测试,包括功能测试和单元测试。这是一个好主意,可以确保业务逻辑是正确的。因此,即使Rust本身具有出色的安全特性,你也需要确保业务逻辑是正确的。这就是为什么对CLI工具进行功能测试通常是一个非常好的想法。

不过,一旦你设置好了这些测试,你希望以自动化的方式进行,这样每次你做出更改时,它都会尝试编译、格式化,如果所有这些都通过了,你就可以创建可交付成果。这将是其持续交付的方面,可交付成果可以是你提供给其他人的二进制工件。

推荐的工作流程

我认为,混合使用Python和Rust的推荐工作流程之一是:快速将其放入CLI中进行测试,然后编写测试,最后自动化测试,以便你可以将工件提供给你一起工作的其他人。或者,如果你在一家需要交付产品的初创公司或企业工作,你可以快速交付一个工件,例如在GitHub上,这些GitHub工件随后可以交付给任何用户,甚至可以打包到像RPM或Debian这样的包管理系统中,供人们使用你的产品。

总结

本节课中我们一起学习了如何构建一个结合Rust与Python的生产级命令行工具。我们探讨了使用Clap框架为嵌入式Python代码创建CLI界面的方法,理解了二进制部署的优势,并介绍了为工具添加测试以及建立自动化构建与交付流水线的重要性。这套工作流程能有效结合Rust的性能、安全性与Python的生态及灵活性,是构建可交付、可维护混合语言应用的推荐实践。

059:基于Clap的嵌入式Python Rust CLI实现 🐍🔧

在本节课中,我们将学习如何将一个嵌入了Python代码的Rust项目,包装成一个功能完整的命令行界面工具。我们将使用clap库来构建CLI,并演示如何通过命令行参数与内部的Python代码进行交互。


项目结构概览 📁

首先,我们来看看这个项目的文件结构。了解结构有助于理解代码的组织方式。

以下是项目的主要文件和目录:

  • Cargo.toml: Rust项目的配置文件,其中声明了项目依赖。
  • Makefile: 可选的构建辅助文件。
  • src/: 源代码目录。
    • lib.rs: 库文件,包含了嵌入Python的核心逻辑。
    • main.rs: 程序入口文件,包含了基于clap的命令行工具实现。

提示:你可以使用命令 tree -I target 来清晰地查看一个Rust项目的结构,而无需浏览target构建目录。


核心库:嵌入Python逻辑 🧠

上一节我们查看了项目结构,本节中我们来看看核心的库代码是如何嵌入并调用Python的。

打开src/lib.rs文件,其核心功能如下:

  1. 定义Python代码: 在Rust代码中直接定义了一个Python函数。这个函数接收一个输入参数,如果输入是“Marco”,则返回字符串“Polo”。
    // 嵌入的Python代码字符串
    let python_code = r#"
    def marco_polo(input_str):
        if input_str == "Marco":
            return "Polo"
        else:
            return "No Polo"
    "#;
    
  2. 初始化Python解释器: 使用pyo3库初始化一个Python解释器。
    let py = pyo3::Python::acquire_gil(); // 获取Python全局解释器锁
    
  3. 执行与交互
    • 将Python代码载入解释器。
    • 获取其中定义的marco_polo函数。
    • 使用从Rust传入的参数调用该函数。
    • 将Python函数的返回值转换回Rust的String类型并返回。
    let result: String = py.python().run(python_code, None, None)?
        .getattr("marco_polo")?
        .call1((input,))?
        .extract()?;
    Ok(result)
    

这段代码为我们提供了一个可以在Rust中调用的安全接口,它内部封装了Python的逻辑。


命令行包装:使用Clap构建CLI 🛠️

核心的Python交互逻辑已经就绪,接下来我们需要一个方便的方式来调用它。本节我们将使用clap库为它创建一个命令行界面。

首先,需要在Cargo.toml文件中添加clap依赖。

[dependencies]
clap = { version = "3", features = ["derive"] } # 使用derive特性简化代码
pyo3 = "0.15"

然后,在src/main.rs中构建CLI工具。以下是关键步骤:

以下是构建CLI的主要步骤:

  1. 定义命令行参数结构体: 使用clap的派生宏来定义程序接受的参数。这里我们只需要一个input参数。
    #[derive(Parser)]
    #[clap(about = "一个包装了pyo3嵌入式Python代码的CLI工具")]
    struct Args {
        /// 输入字符串,传递给内部的Python函数
        input: String,
    }
    
  2. 解析参数并调用库函数: 在main函数中,解析用户输入的命令行参数,并将其传递给我们在lib.rs中定义的函数。
    fn main() -> Result<()> {
        let args = Args::parse(); // 解析命令行参数
        let output = your_library::marco_polo(&args.input)?; // 调用核心逻辑
        println!("{}", output); // 输出结果
        Ok(())
    }
    
  3. 生成帮助菜单clap会自动根据结构体的定义生成格式美观的帮助信息。

这种将嵌入式代码包装成CLI的做法是一个最佳实践,它使得测试和交互变得非常容易。


运行与测试 🚀

现在,让我们来运行和测试这个工具,看看它是如何工作的。

我们可以使用cargo run命令来直接运行程序。为了与我们的CLI工具交互,需要在命令后加上--来传递参数。

首先,查看自动生成的帮助菜单:

cargo run -- --help

这将编译代码并显示使用说明、参数解释等信息。

接着,我们可以测试不同的输入。根据lib.rs中的逻辑,输入“Marco”会得到特定响应:

cargo run -- Marco
# 输出:Polo

cargo run -- Alice
# 输出:No Polo

通过命令行,我们成功地将参数传递给了Rust,Rust又将其传递给了内嵌的Python代码,并最终将结果打印出来。


总结 📝

本节课中我们一起学习了如何构建一个结合了Rust与Python优势的混合应用。我们首先在Rust中安全地嵌入了Python代码片段,然后使用强大的clap库为其创建了一个用户友好的命令行界面。

这种模式的优势非常明显:

  • 易于分发: 你可以将最终产品编译成单个二进制文件,无需用户单独安装Python环境,这比纯Python应用的分发要简单得多。
  • 强大安全: 你既可以利用Rust的性能和内存安全特性来构建程序主体,又能灵活调用丰富的Python生态库来处理特定任务。
  • 交互友好: 通过CLI工具,可以非常方便地对封装的功能进行测试、调试和集成到自动化脚本中。

这就是将嵌入式Python的Rust代码包装成命令行工具的一个完整而实用的例子。

060:增强版嵌入式Python Rust CLI测试 🧪

在本节课中,我们将学习如何为嵌入Python代码的Rust命令行工具编写单元测试。我们将探讨为何测试至关重要,并演示一个具体的测试案例,展示如何通过测试发现并修复业务逻辑中的错误。


上一节我们介绍了构建嵌入式Python工具的基本框架。本节中我们来看看如何通过单元测试来确保业务逻辑的健壮性。

上图展示了我认为构建嵌入式Python工具的最佳实践。我们不仅需要一个命令行界面,还需要确保业务逻辑在面对代码变更或其他开发者协作时,依然能保持正确性。不能仅仅因为工具能编译运行,就假设业务逻辑一定正确。可能存在潜在问题。因此,我们需要通过编写单元测试来验证输入和逻辑。

接下来,让我们看看具体如何操作。首先,我们再次看到这段代码。我们将进入一个Github代码空间来仔细查看其逻辑。

这里是一个封装了Python 3嵌入式代码的CI工具,它接收输入。那么,它具体接收什么输入,又会做什么呢?如果我们浏览代码,会发现它期望输入“Marco”,但在这个案例中,它返回了“Bob”。也许某个开发者认为这是一个不错的逻辑。让我们运行一下这段代码看看。

运行命令 cargo run --help,可以看到帮助菜单。如果想实际传入输入,我们可以输入“Marco”。结果显示“Bob”。这看起来很奇怪,假设我是产品经理,我会觉得这不对。那么,让我们运行测试看看。

如何运行测试呢?如果我们查看Makefile,会发现它非常简单,就是 cargo test。我们可以输入 make testcargo test。让我们执行 cargo test

查看测试结果,我们发现这里确实存在问题。这正是编写单元测试的意义所在。我们看到一个名为 test_marco 的测试失败了,但有一个测试是成功的。我们甚至可以追溯到具体的代码行:sourcelib 的第35行。

让我们回到第35行,滚动查看。事实上,问题就在这里,我们需要仔细调查。如果我们向下滚动代码,会发现实际发生的情况是:代码期望返回“Python”,但实际返回了其他内容。我们需要修复这个问题,确保代码不再返回错误的结果。

回到我们的代码位置,我们看到问题在于:我本应返回“Python”。测试本身告诉我们期望的结果是“Python”。因此,我们需要修复这段代码。

修复代码后,让我们快速回顾一下测试是如何帮助我们调试的。编写一个简单单元测试的方法是使用以下样板代码:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_marco() {
        let input = "Marco"; // 模拟命令行输入
        let expected_output = "Python"; // 期望的输出
        let actual_output = your_business_logic_function(input); // 调用业务逻辑函数
        assert_eq!(actual_output, expected_output); // 断言结果是否符合预期
    }
}

每个以 #[test] 标注的函数都是一个测试。测试本身很直接:设置输入为“Marco”,就像命令行工具那样,然后声明我们期望得到“Python”。这就是问题所在:测试本身告诉我们代码是错误的。可能是其他开发者在修改代码时出了错,但我们通过测试捕捉到了它。

然后我们查看输出,并使用断言(assert_eq!)来验证实际输出是否符合预期。在这个案例中,测试告诉我们它期望“Python”,但实际得到了“Bob”。这样我们就获得了调试这个应用所需的所有信息。

现在我已经修复了代码,让我们再次运行 cargo test。它会重新编译并运行测试。很好,所有测试都通过了。这里的核心逻辑就是:要构建并集成这些单元测试。

尽管Rust是一门非常出色的语言,但它无法预知未来业务逻辑会如何变更。这就是单元测试如此重要的原因。如果我们再次运行修复后的工具,它会完全按照我们的预期执行。

因此,花一点时间编写测试是一个非常好的做法,尤其是在生成式AI可以帮助你编写测试的今天。为什么不构建一些业务逻辑测试呢?甚至可以将它们放在你的库(Libs)中,使其更简单。

这样,你不仅能确保通过持续交付获得可复现的部署,还能验证你的代码逻辑与其著名的安全性和性能一样健壮。

😊


本节课中我们一起学习了为嵌入式Python的Rust CLI工具编写单元测试的重要性与方法。我们通过一个具体案例,演示了如何利用测试发现业务逻辑错误、如何定位问题代码,以及如何修复并验证修复结果。记住,结合Rust的安全性与全面的单元测试,是构建健壮、可靠应用程序的关键。

061:Rust构建的Python工具链 🛠️

在本节课中,我们将探讨一系列由Rust构建的、能显著提升Python性能的工具。我们将重点介绍两个关键工具:用于大数据处理的Polars数据框架库,以及用于代码规范检查的Ruff Linter。同时,我们也会讨论Rust语言在能效方面的优势,以及为何这些工具正越来越多地被集成到Python工作流中。


高性能数据框架:Polars 📊

上一节我们提到了Rust工具对Python性能的提升。本节中,我们来看看第一个核心工具——Polars。

Polars是一个用Rust编写的数据框架库,专为并行和高效的数据处理而设计。它是大数据和分布式系统中的关键组件。Polars利用了Rust语言的内存安全和高性能特性,并具备先进的多线程能力。它通常可以与Pandas等数据框架库竞争,但性能更优。

从性能对比图中可以看出,Polars在处理大数据时拥有最佳的性能表现之一。与Spark、Dask甚至Pandas相比,它能带来极大的性能提升。事实上,对于百万行、50GB量级的数据,Pandas可能无法运行,而Polars凭借Rust语言的特性可以轻松处理。

以下是Polars的一些关键集成特点:

  • 它提供了Python接口,因此你仍然可以从Python语言中使用它。

极速代码检查工具:Ruff ⚡

了解了数据处理工具后,我们转向代码质量工具。Ruff是一个用Rust设计的Linter,用于强制执行编码标准。

Ruff旨在使你的代码更具可读性、可维护性且无错误。其性能令人惊叹,在某些场景下速度可以提升10到1000倍。从性能对比图中可以直观看到,Ruff检查仅需三分之一秒,而纯Python方案可能需要60秒。

这直观地说明了性能的重要性。如果你需要检查一个大型代码库,并且速度能快上150倍(达到亚秒级),你会更倾向于使用这些工具,并将其集成到各处。反之,如果一个工具运行需要一分钟,你可能会避免运行它,从而降低了使用Lint工具提升代码安全性和规范性的效力。


Rust工具的优势:性能与能效 🔋

前面我们介绍了Polars和Ruff在速度上的巨大优势。本节中,我们来深入探讨Rust工具带来的另一个关键优势:能效。

语言效率与系统性能及环境影响相关。我们知道环境正在发生变化,低能耗的语言可以扮演新的角色。Python、C++、Java等语言各有取舍,但就能源效率而言,Python是能效最差的语言之一。从图表中可以看到,Python消耗的能源是其他语言的75倍之多。

因此,在选择使用何种工具以及这些工具的运行频率时,能效也是一个重要的考量因素。一个新兴的趋势是,即使你主要使用Python,也会看到越来越多基于Rust的工具被集成到你的工作流中。这得益于Rust语言在时间、内存和能源效率方面的优势,以及其安全性和并发性优势。


总结 📝

本节课中,我们一起学习了由Rust构建的高性能Python工具链。我们首先介绍了Polars,这是一个能极大提升大数据处理效率的数据框架库。接着,我们探讨了Ruff,一个能极速进行代码规范检查的Linter工具。最后,我们分析了这些Rust工具的核心优势:卓越的性能和优异的能源效率。正是这些优势,使得基于Rust的工具正日益成为提升Python项目质量和效率的关键选择。

062:使用Rust Ruff进行Python代码检查

在本节课中,我们将要学习如何使用Ruff,一个用Rust编写的、极其快速的Python代码检查工具。我们将了解其性能优势,并通过实际演示学习如何在项目中和持续集成流程中集成Ruff。

概述:什么是Ruff?

Ruff是一个用Rust语言编写的、速度极快的Python代码检查工具。它展示了如何使用Rust为Python生态开发高性能工具。其性能提升非常显著,例如,一次检查耗时0.29秒,而传统工具Pylint可能需要60秒。如此巨大的差异使得选择更慢的检查工具变得缺乏说服力。此外,Ruff还具备缓存等高级功能。

在线体验Ruff

上一节我们介绍了Ruff的基本概念,本节中我们来看看如何快速上手体验。一个简单的方法是访问其在线Playground。

通过访问Playground链接,我们可以直接与检查器交互。例如,界面会提示“移除对未使用变量的赋值”这类错误。我们可以根据提示进行修改,比如将未使用的变量用于打印语句来修复问题。同时,我们也可以注意到,Python代码中不需要分号。

这里的核心思想是,得益于Ruff的速度,我们可以获得实时的代码格式化和检查反馈。

在项目中集成Ruff

在线体验很方便,但实际开发中我们需要将Ruff集成到自己的项目中。接下来,我们将学习如何在Github项目模板和Codespace环境中使用Ruff。

我创建了一个名为“Python Ruff模板”的Github仓库。Github生态系统的一个优点是允许用户创建项目模板。在仓库设置中将其标记为模板后,未来就可以基于此模板快速创建新项目。

现在,让我们在Codespace中启动这个项目。这个项目已经配置好了Ruff检查器。

如果我们查看项目中的requirements.txt文件,会发现我已经固定了依赖版本,并且注释掉了Pylint。这是因为基于Ruff的检查器性能要高得多。

使用Makefile运行检查

为了测试Ruff,我们来看一些代码。我喜欢将检查命令放在Makefile中,这样操作更简单,也避免了记忆复杂命令或输入错误。

以下是一个Makefile的示例片段:

lint:
    ruff check .

现在,我们可以在终端中运行make lint命令。执行后,可能会发现一个问题:“一行上有多个语句”。这会导致构建失败。

要修复这个问题,我们可以回到源代码文件。例如,在main.py中,可能有一行包含了两个语句(如import click和一个不必要的分号)。我们可以删除或注释掉多余的部分。

修复后,再次运行make lint,就不会再报错了。

我们也可以在库代码中进行测试。例如,故意写一个不完整的语句var =,然后运行检查。Ruff会立即提示第5行存在语法错误,无法解析不完整的语句。这证明了Ruff能快速有效地发现潜在错误。

集成到Github Actions CI/CD流程

代码检查是保证代码质量的重要环节,将其集成到自动化流程中效果更佳。上一节我们学会了在本地使用Ruff,本节我们将其整合到Github Actions中,实现持续集成。

Github Actions允许我们通过定义一系列步骤来实现经典的持续集成和持续交付,例如安装软件、代码检查、测试、格式化和部署。这些步骤能显著提升代码质量。

由于我们使用了速度极快的Ruff工具,它将大大加速我们的构建流程。

要触发这个流程,我只需要执行git push推送代码。然后,在Github仓库的“Actions”标签页下,就能看到一个新的工作流正在运行。

我们可以实时观察工作流执行每个步骤的过程。持续集成的经典组成部分就是确保项目中的每个步骤都是可复现的,并且有日志记录问题发生的具体情况。

当然,运行速度越快越好。可以看到,Ruff检查步骤速度极快,几乎无法察觉。这使我们能够获得更快的反馈,因此将其纳入CI流程至关重要。

添加状态徽章

我还喜欢为项目创建状态徽章。这能直观地展示构建状态。

我们可以在项目模板的README文件中添加状态徽章。在这个案例中,这就是我们新的状态徽章。提交更改后,如果未来构建失败,徽章状态会改变,我们可以据此快速发现问题并修复。

总结

本节课中我们一起学习了Ruff工具。总而言之,Ruff是一个能极大提升构建速度的优秀代码检查工具。在某些场景下,传统检查可能需要数分钟,而Ruff则是瞬间完成的亚秒级操作。它是提升代码构建质量、并利用Github Actions增强持续集成流程的绝佳方式。

063:在Python和Rust中使用Polars

概述

在本节课中,我们将学习如何在Rust项目中使用Polars数据框库。我们将从一个实际项目的角度出发,了解如何设置项目、编写数据处理代码,并执行一些典型的数据操作,如过滤、分组和聚合。

项目结构与设置

上一节我们介绍了Polars库的概况,本节中我们来看看如何在一个具体的Rust项目中设置和使用它。

首先,一个Rust项目生态系统包含Cargo包管理系统。Cargo会负责安装Polars以及项目可能需要的其他库,例如用于命令行解析的clap或用于性能基准测试的criterion

项目设置完成后,下一步通常是添加集成测试、单元测试和基准测试。将所有部分整合在一起,可以验证工具的性能、业务逻辑以及交付的二进制工具的契约。

以下是项目设置的核心步骤:

  1. 初始化项目:使用cargo new命令创建一个新的Rust项目。
  2. 添加依赖:在Cargo.toml文件中添加polars依赖项。
  3. 组织代码:在src目录下编写主要的Rust代码。

核心代码解析

现在,让我们深入代码,看看如何使用Polars进行具体的数据操作。

在项目源代码目录中,我们有一个数据文件(例如经典的鸢尾花数据集iris.csv)和一个主文件main.rs

main.rs文件中的代码将执行以下操作:

  1. 过滤:筛选出sepal_length列值大于5的行。
  2. 分组:根据species列对数据进行分组。
  3. 聚合:为每个分组计算其他列(如sepal_width, petal_length, petal_width)的总和。

本质上,我们将执行数据框的典型基本操作。

以下是代码实现的关键步骤:

  1. polars crate中导入必要的模块。
  2. 定义main函数。在Rust中,一个简单的项目只需要一个main函数。
  3. 读取iris.csv文件。
  4. 执行过滤操作。
  5. 执行分组操作。
  6. 执行聚合操作,对每个组的特定列求和。
  7. 触发计算并将结果收集到数据框中。
  8. 输出最终结果。

这是一个相当直接的项目,能够初步体验Polars的功能。

// 示例代码结构示意
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 读取CSV文件
    let df = CsvReader::from_path("iris.csv")?.finish()?;

    // 过滤: sepal_length > 5
    let filtered = df.filter(&df.column("sepal_length")?.gt(5)?)?;

    // 分组与聚合: 按species分组,并求和
    let grouped = filtered.groupby(["species"])?
        .select(["sepal_width", "petal_length", "petal_width"])
        .sum()?;

    // 输出结果
    println!("{:?}", grouped);
    Ok(())
}

运行与结果

我喜欢使用Makefile来管理构建、运行和测试等任务。不过,对于这个简单的项目,我们直接使用cargo run命令即可。

进入项目目录,执行cargo run命令。程序运行速度极快,几乎瞬间完成。

运行结果会将数据整理成一个紧凑的3行×5列的结构(假设有3个物种)。输出列包括:

  • species (字符串类型): 显示鸢尾花种类(如Setosa, Versicolor, Virginica)。
  • sepal_length_sum: 萼片长度总和。
  • sepal_width_sum: 萼片宽度总和。
  • petal_length_sum: 花瓣长度总和。
  • petal_width_sum: 花瓣宽度总和。

这些分组结果清晰地展示了我们查询的聚合数据。

总结

本节课中我们一起学习了Rust生态下的Polars库。它是一个出色的高性能库,让你能够以极快的方式进行数据操作。我们通过一个实际示例,演示了如何设置项目、读取数据、执行过滤、分组和聚合等常见数据框操作,并见证了其闪电般的执行速度。

064:构建Rust版Polars命令行工具 🛠️

在本节课中,我们将学习如何利用Rust和Polars库构建一个高性能的命令行工具。我们将重点介绍如何使用clap库来创建命令行界面,并了解如何将数据处理逻辑打包成一个可执行、易分发的工具。


项目结构概览

上一节我们介绍了Rust与Polars结合的性能优势。本节中,我们来看看如何为数据处理逻辑添加一个命令行界面。首先,让我们了解一下项目的基本结构。

在Rust项目中,了解文件结构是很有帮助的。以下命令可以清晰地展示项目目录(排除target构建目录):

tree -I target

通过这个命令,我们可以看到项目包含以下核心部分:

  • Cargo.toml:项目配置和依赖管理文件。
  • data/:存放数据集(例如Iris数据集)的目录。
  • src/:源代码目录,包含lib.rsmain.rs
  • benches/:性能基准测试代码。
  • tests/:集成测试代码。
  • Makefile:构建脚本。

我们接下来的重点将放在Cargo.tomlsrc/lib.rssrc/main.rs上。


配置项目依赖

要构建命令行工具,我们需要引入必要的库。这通过在Cargo.toml文件中声明依赖项来完成。

打开Cargo.toml文件,我们可以看到两个关键依赖:

  • polars:用于高性能数据处理的库。
  • clap:用于解析命令行参数的库,我们将用它来构建用户界面。

实现核心逻辑

src/lib.rs文件中,我们定义了工具的核心功能。通常,这里会包含一个或多个执行具体数据处理任务的函数。

例如,这里可能有一个函数,用于对Iris数据集执行分组和聚合计算。这部分是纯粹的业务逻辑,与用户界面无关。


构建命令行界面

现在,我们进入最关键的部分:如何让用户通过命令行方便地调用我们实现的功能。这需要在src/main.rs文件中使用clap库。

使用clap构建一个基本的命令行工具非常简洁。以下是实现步骤:

首先,我们使用clapParser特性来定义一个结构体,用于描述命令行参数和程序的元信息(如名称、版本、描述等)。

use clap::Parser;

#[derive(Parser)]
#[command(name = "polars_cli", version = "1.0", about = "A fast data processing tool using Polars", long_about = None)]
struct Cli {
    // 这里可以定义具体的命令行参数,例如输入文件路径、输出格式等
    // 本例中我们暂时不定义额外参数,仅展示基础框架
}

接着,在main函数中,我们解析用户输入的命令行参数,并调用lib.rs中实现的业务逻辑函数。

fn main() {
    // 解析命令行参数
    let _args = Cli::parse();

    // 调用lib.rs中定义的核心处理函数
    // 例如:polars_cli::process_data();
    println!("Tool executed successfully!");
}

整个过程大约只需要10行左右的样板代码。这样,我们就将一个函数封装成了一个拥有完整帮助菜单的命令行工具。


运行与测试工具

构建好界面后,让我们看看如何运行和测试这个工具。

要运行工具,最直接的方式是使用cargo run命令。在项目根目录下执行:

cargo run -- --help

cargo run后面的--用于将后续参数传递给我们的程序。--help参数会触发clap自动生成的帮助菜单。首次运行时会进行编译。

编译完成后,你将看到一个格式清晰的帮助信息,展示了工具的用法、描述和可用的参数选项。这是使用命令行框架最大的优势之一——它自动为你处理了这些繁琐的界面工作。

要真正执行工具的功能,只需运行:

cargo run

或者,在工具开发测试过程中,你可以直接运行编译生成的二进制文件,而无需每次都通过cargo run。编译后的二进制文件位于target/debug/目录下。

./target/debug/polars_cli

你可以直接运行这个二进制文件进行测试。当你构建发布版本(使用cargo build --release)时,优化后的二进制文件会位于target/release/目录下。这个独立的可执行文件可以轻松地分发给其他用户或客户。


总结

本节课中我们一起学习了如何将一个Rust数据处理程序打包成专业的命令行工具。我们首先了解了项目结构,然后通过Cargo.toml引入了clap依赖。接着,我们在src/main.rs中使用clap定义了命令行界面,将核心业务逻辑与用户交互分离开来。最后,我们掌握了如何使用cargo run和直接执行二进制文件的方式来运行和测试工具。使用命令行工具是封装和分发Rust程序最高效的方式之一。

065:Rust版Polars CLI测试 🧪

在本节课中,我们将学习如何为Rust项目构建一个命令行界面(CLI)。我们将使用clap库来创建一个高效、易用的命令行工具,并集成polars库进行数据处理。通过这个过程,你将了解如何将Rust代码打包成一个可执行工具。


项目结构概览

首先,让我们了解一下Rust项目的典型结构。这对于理解代码组织和构建过程非常重要。

以下命令可以清晰地展示项目目录结构,排除target构建目录:

tree -I target

运行后,你会看到类似的结构:

  • benches/: 基准测试代码
  • Cargo.toml: 项目配置文件
  • data/: 数据集文件(如Iris数据集)
  • Makefile: 构建脚本
  • src/: 源代码目录
    • lib.rs: 库代码
    • main.rs: 主程序入口
  • tests/: 集成测试代码

这种结构有助于分离关注点,使项目更易于维护。


依赖配置

上一节我们查看了项目结构,本节中我们来看看项目的核心依赖是如何配置的。这主要在Cargo.toml文件中完成。

打开Cargo.toml文件,你可以看到项目依赖:

[dependencies]
clap = { version = "4.0", features = ["derive"] }
polars = "0.30.0"
  • clap: 这是一个功能强大的命令行参数解析器。我们使用它来构建CLI。
  • polars: 这是一个高性能的数据处理库,类似于Python的Pandas,但用Rust编写,速度更快。

核心库函数

配置好依赖后,我们来看看实际的数据处理逻辑。这部分代码通常放在src/lib.rs文件中。

lib.rs中,我们定义了一个函数,它对Iris数据集执行基本的分组聚合计算:

use polars::prelude::*;

pub fn calculate_group_stats() -> Result<DataFrame, PolarsError> {
    // 1. 读取CSV数据
    let df = LazyCsvReader::new("data/iris.csv")
        .has_header(true)
        .finish()?
        .collect()?;
    
    // 2. 按物种分组并计算平均萼片长度
    let result = df
        .lazy()
        .groupby(["species"])
        .agg([col("sepal_length").mean()])
        .collect()?;
    
    Ok(result)
}

这个函数执行以下操作:

  1. 从CSV文件加载Iris数据集。
  2. 按照species列进行分组。
  3. 计算每个物种的sepal_length(萼片长度)的平均值。

构建命令行界面

我们已经有了数据处理的核心逻辑,现在需要一种简单的方式来调用它。这就是命令行界面的作用。我们将使用clap库来实现。

src/main.rs文件中,我们设置CLI:

use clap::{Arg, Command};
use polars_cli::calculate_group_stats;

fn main() {
    // 定义命令行应用
    let matches = Command::new("polars-cli")
        .version("1.0")
        .author("Your Name")
        .about("Performs group-by aggregation on Iris dataset")
        .get_matches();
    
    // 执行计算
    match calculate_group_stats() {
        Ok(df) => println!("Result:\n{}", df),
        Err(e) => eprintln!("Error: {}", e),
    }
}

这段代码的作用是:

  • 使用clap::Command定义了一个名为polars-cli的应用。
  • 设置了版本、作者和描述信息。
  • 调用我们在lib.rs中定义的calculate_group_stats函数。
  • 打印结果或错误信息。

clap库自动为我们生成了帮助菜单和参数解析功能,这大大简化了CLI开发。


运行与测试

构建好CLI后,我们需要知道如何运行和测试它。Rust的Cargo工具使这个过程变得非常简单。

以下是运行和测试CLI的步骤:

  1. 使用Cargo运行:
    这是开发过程中最常用的方式。它会自动编译并运行程序。

    cargo run -- --help
    

    这里的--用于将参数传递给我们的程序,--help会显示自动生成的帮助菜单。

  2. 直接运行二进制文件:
    编译后,你可以在target/debug/目录下找到可执行文件。

    ./target/debug/polars-cli
    

    这种方式适合测试最终的可执行文件。

  3. 构建发布版本:
    当你准备分发工具时,可以构建一个优化的发布版本。

    cargo build --release
    

    发布版本的可执行文件位于target/release/目录下,运行速度更快,文件体积更小。

使用命令行工具是打包和分发Rust程序最有效的方式之一。你可以轻松地将单个可执行文件分享给他人,无需他们安装复杂的依赖环境。


总结

本节课中我们一起学习了如何为Rust数据处理项目构建一个完整的命令行界面。

我们首先了解了典型的Rust项目结构,然后通过Cargo.toml文件配置了clappolars依赖。接着,我们编写了核心的数据处理函数,并使用clap库将其包装成一个用户友好的CLI工具。最后,我们探讨了如何使用Cargo运行、测试以及构建最终的可分发版本。

通过将Rust代码与强大的CLI框架结合,你可以创建出高性能、易用且易于分发的数据处理工具,非常适合自动化任务或提供给其他用户使用。

066:构建Polars与Clap集成测试 🧪

在本节课中,我们将学习如何为Rust命令行工具构建集成测试。集成测试能够验证程序的最终输入和输出行为,是确保项目在交付前按预期工作的关键步骤。


概述

集成测试不同于单元测试,它直接测试命令行工具的完整执行流程。这对于验证最终用户(如客户或开源工具使用者)的实际体验至关重要。在Rust这类安全且构建特性良好的语言中,集成测试是项目收尾阶段的重要环节,能确保每次构建都按计划执行。

上一节我们介绍了项目的基本结构,本节中我们来看看如何为其添加集成测试。


项目结构

首先,我们来看一下包含集成测试的Rust项目结构。该项目包含了命令行工具、基准测试等组件。

.
├── Cargo.toml
├── src/
│   ├── lib.rs
│   └── main.rs
├── benches/
├── data/
└── tests/
    └── integration_test.rs

我们的集成测试文件位于 tests/integration_test.rs 目录下。


编写集成测试

集成测试的核心思想是模拟用户调用命令行工具的过程,并验证其输出结果。

以下是编写集成测试的关键步骤:

  1. 导入待测试功能:首先,从我们的库中导入需要测试的函数。
  2. 准备并运行命令:使用标准库的 std::process::Command 来构建和运行我们的命令行工具,并传入必要的参数。
  3. 验证输出:最后,断言命令的执行结果(如退出状态、标准输出)符合我们的预期。

这种方法非常强大,因为它测试的是最终结果,而不仅仅是孤立的函数单元。

让我们通过代码来具体了解。

// tests/integration_test.rs
use my_cli_tool::some_polars_function; // 导入待测试的功能
use std::process::Command; // 导入标准库的命令模块

#[test]
fn test_cli_basic_functionality() {
    // 准备并运行命令
    let output = Command::new("cargo")
        .arg("run")
        .arg("--")
        .arg("--input")
        .arg("data/input.csv")
        .arg("--output")
        .arg("data/output.csv")
        .output()
        .expect("Failed to execute command");

    // 验证命令执行成功
    assert!(output.status.success());

    // 验证输出中包含特定内容(示例)
    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(stdout.contains("Processing complete"));
}

在这段代码中,我们构建了一个 cargo run 命令,并传递了工具所需的输入输出文件参数。测试会检查命令是否成功执行,并验证标准输出中是否包含预期的信息。


运行与验证测试

现在,让我们运行测试来验证一切是否正常。

在终端中执行以下命令来运行所有测试(包括单元测试和集成测试):

cargo test

这个命令会编译项目并运行 tests 目录下的所有集成测试以及 src 目录中的单元测试。如果所有测试通过,则意味着我们应用程序的整个功能层面都工作正常。

如果测试失败(例如,我们故意更改了输出格式),cargo test 会清晰地报告错误。这能有效帮助我们捕获代码中的拼写错误、逻辑错误或其他意外行为,确保在持续集成和持续交付流程中,我们的二进制文件行为始终符合预期。


总结

本节课中我们一起学习了为Rust命令行工具构建集成测试的方法。我们了解了集成测试的重要性,它是对项目最终功能的验证。我们查看了集成测试在项目结构中的位置,学习了如何利用 std::process::Command 来模拟和测试命令行工具的完整执行流程,并掌握了运行和验证这些测试的方法。

为命令行工具添加至少一个集成测试是一个很好的实践,它能确保在自动化构建和部署流程中,你的工具行为始终是可预测和正确的。

067:构建性能基准测试 📊

在本节课中,我们将学习如何为Rust代码构建性能基准测试。我们将通过一个具体的项目,对比Rust、Python pandas以及Python polars库在处理相同数据操作时的性能差异。课程将涵盖两种基准测试方法:使用简单的Makefile进行宏观对比,以及使用专业的criterion库进行微观、数据驱动的性能分析。


项目概述与代码结构

首先,我们有一个Rust polars项目,其中包含多种不同的组件。本节课的重点是性能基准测试。我们将进行两种不同类型的基准测试:首先,使用相同接口对比Python与Rust的性能,同时也对比基于pandas的接口;其次,我们将深入到更细粒度的层面,使用名为criterion的基准测试库对代码进行详细剖析。

让我们先来看看代码结构。第一步,我创建了这个名为“polars Python rust”的项目。其中有一些关键文件需要查看。

首先,这里有一个主文件(main.rs),它执行了一系列操作,包括聚合和分组等。

// Rust代码示例:执行聚合与分组操作
// ... (具体代码逻辑)

在pandas中,我也做了非常类似的事情,同样执行了一些分组操作。此外,我还使用了Polars库编写了一个基于Python的实现。

因此,我们现在有几个不同的版本来进行测试。


使用Makefile进行宏观基准测试

那么,一次性测试所有这些版本的最佳方法是什么?我喜欢使用Makefile。

接下来,让我们查看这里的Makefile。在Makefile中,我定义了几个目标:

  • rust-benchmark:运行cargo build --release构建优化后的Rust二进制文件,然后计时运行它。
  • pandas-benchmark:测试Python pandas版本。
  • polars-benchmark:测试Python polars版本。这个版本应该与Rust版本性能相近,因为它们使用相同的库。
  • benchmark:一个总目标,依次运行以上所有基准测试。

我认为这是测试一些想法(例如“Python的性能总是足够好”)的绝佳方式。与其猜测,不如通过实际编写代码来验证。

现在,让我们来运行这个基准测试。我将执行命令 make benchmark

运行后,我们可以看到结果:

  • Rust版本的运行时间约为 0.55秒
  • Python pandas版本的运行时间约为 4.8秒
  • Python polars版本的运行时间约为 1.6秒

从结果可以看出,pandas版本明显更慢(约5秒),而Rust版本仅需约半秒,这是一个非常显著的差异。同时,尽管Python polars同样使用Python,但由于Polars库内部的优化,其性能(1.6秒)也远优于pandas。这是一个很好的例子,说明在特定场景下(例如大规模运行的AWS Lambda函数),性能差异可能直接转化为巨大的成本差异。


使用Criterion库进行微观基准测试

第二种基准测试代码的方法是使用更专业的工具。我将进入另一个目录 polars-ci,这里我使用了名为criterion的库。让我们看看它是如何工作的。

在这个项目中,我们看到criterion已被安装,并且在Cargo.toml文件中有一个新的[[bench]]部分,它指向我们的基准测试文件。

接下来,让我们查看基准测试文件 benches/benchmark.rs。这个文件使用criterion库,并引入了项目中的calculate函数。它的作用是:对这个calculate函数进行基准测试。

这里的核心思想是,我们不应仅仅猜测“Python性能足够好”或“Rust性能足够好”,而应该使用数据科学的方法,通过实际基准测试这个特定操作来了解真实情况。

我们可以通过运行命令 make bench 来执行这个基准测试。

运行完成后,criterion会在 target/criterion 目录下生成详细的报告。我们可以打开报告文件夹并查看report目录下的内容。这里有一个基准测试报告。

如果我们点击查看calculate函数的报告,可以看到它提供了类似数据科学风格的分析,展示了我们函数的实际性能。报告会显示平均时间(单位为毫秒),我们可以看到这个函数运行速度极快。

此外,我们还可以查看实际的迭代次数,以及其他统计信息,例如标准差、R平方值、斜率等。这些都是观察数据的很酷的方式。简而言之,我们可以看到这个函数的执行时间在毫秒级别,是一个非常快速的函数。


课程总结

本节课中,我们一起学习了为代码进行性能基准测试的重要性。Rust使得这项工作变得非常容易,无论是通过简单的Makefile进行宏观对比,还是使用像criterion这样功能更丰富的库进行细致的、数据驱动的分析。通过实际测试而非猜测,我们可以做出更明智的技术决策,优化性能并控制成本。

068:无服务器与AWS Lambda导论 🚀

在本节课中,我们将要学习无服务器计算的核心概念,特别是AWS Lambda服务。我们将探讨其工作原理、核心优势以及一个典型的使用场景。


概述

无服务器计算是一种云计算执行模型,云提供商动态管理服务器资源的分配和供应。AWS Lambda是这一模型的杰出代表,它允许开发者运行代码而无需预置或管理服务器。本节将解释为何要关注Lambda,并剖析其关键特性。

为何选择AWS Lambda?

许多人在初次接触Lambda时都会问:我们为什么需要它?它的意义何在?以下是几个核心原因。

自动扩展能力

无服务器平台会根据需求自动扩展函数实例的数量。这消除了手动干预以配置或取消配置服务器的需要。对于仅仅是将代码部署到云端并运行而言,这是一个巨大的优势。

这种自动扩展能力使你能够无缝处理多种不同类型的负载,例如从每秒几个请求到每秒数千个请求,而你无需更改任何其他代码。

成本效益

Lambda采用按量付费的定价模式。你只需为实际消耗的资源付费,而无需为预先分配或闲置的服务器容量付费。这种基于事件的定价模型可以带来显著的成本节约。

此外,编程语言的选择也影响成本。如果你能利用世界上运行速度最快的语言之一——Rust,其函数调用所关联的内存消耗可比Python少多达70倍。这意味着,运行相同的代码,你至少可以考虑节省70倍的成本。因此,语言选择是否有助于降低成本,是另一个需要考虑的因素。

运维管理简化

这是一个无服务器环境,云提供商负责服务器的供应、维护、打补丁和容量规划。这种方法使开发人员无需专注于任何形式的基础设施管理。

一个典型场景

下面是一个现实场景的示例。假设你编排了两个不同的Lambda函数(可以是Rust或Python,这并不重要),并且最初有一个有效载荷进入Step Function(步骤函数)。

接下来会发生的是,Rust代码会处理这个有效载荷,然后返回另一个有效载荷。通常的做法是将这些Lambda函数链接在一起。也许为了简单起见,下一个函数会处理有效载荷,例如查找键值“total”然后除以2,最后返回计算结果。

你可以将这些代码片段链接在一起,它们会静置在那里什么都不做,直到被实际调用时才会产生费用。同时,它也可以是事件驱动的,例如响应S3存储桶的变更、SQS队列的变更或其他变化。

这正是使用无服务器模型的魅力所在:如果操作得当,你将节省大量成本,并且从运维角度来看,它也简单得多。

总结

本节课中,我们一起学习了AWS Lambda与无服务器计算的核心概念。我们了解了其自动扩展、按量付费的成本效益以及简化运维的核心优势,并通过一个函数链的示例场景,看到了无服务器模型如何响应事件并高效执行任务,从而实现显著的资源与成本优化。

069:Firecracker项目详解 🔥

在本节课中,我们将深入探讨一个名为Firecracker的开源项目。这是一个用Rust语言编写的虚拟机,专门用于运行多租户容器和基于函数的服务,并优化性能与安全。我们将了解其架构、优势以及为何Rust是构建此类项目的理想选择。

项目概述

Firecracker是一个虚拟机,它专为运行多租户容器和基于函数的服务而设计,并致力于优化性能与安全性。该项目使用Rust语言构建。Rust因其安全性和性能特性,成为解决此类问题的绝佳选择。

核心架构:微虚拟机

上一节我们介绍了Firecracker的总体目标,本节中我们来看看其核心架构——微虚拟机。

这种架构意味着Firecracker是轻量级的,并且启动速度极快。这使得它非常适合无服务器计算场景。实际上,它是AWS Lambda服务的底层引擎。

微虚拟机架构带来了以下关键优势:

  • 开销极低:因为它只包含最基础的必需组件。
  • 安全性高:Rust语言的安全特性有助于最小化风险,并减少可被攻击的表面区域。

主要特性与优势

了解了其轻量级架构后,我们来看看Firecracker具体有哪些强大的特性。

以下是Firecracker的一些其他重要特性:

  • 快速启动与扩展:它能在几分之一秒内启动数千个微虚拟机,从而实现应用程序的快速伸缩。
  • 低内存占用:对内存资源的消耗非常小。
  • 与容器运行时集成:这使其能够与AWS Fargate、Kubernetes及其他类型的容器平台良好协作。

因此,Firecracker不仅是Lambda和Fargate的底层引擎,也是一个成功的开源项目。

总结与启示

本节课中我们一起学习了Firecracker项目。它作为一个展示Rust语言强大能力的案例,很难找到比它更成功的开源项目了。

这充分表明,如果你需要进行大规模、极致轻量级的计算,Rust是你的最佳选择。这也是为什么在数据工程、MLOps和云计算领域,Rust在某种意义上成为一种“秘密武器”——因为它能高效地构建出既安全又高性能的代码。

070:配置VSCode AWS工具包与CodeWhisperer支持Rust 🛠️🤖

概述

在本节课中,我们将学习如何为Visual Studio Code配置AWS工具包,并启用Amazon CodeWhisperer,以支持使用Rust语言进行AWS开发。我们将涵盖在基于云的GitHub Codespaces环境和本地VSCode中安装与配置的步骤。


AWS工具包简介

一个必备的AWS开发工具是AWS Toolkit for Visual Studio Code。它允许你深入连接到为AWS开发的各个方面,特别是在无服务器计算领域。此外,它还允许你使用诸如Amazon CodeWhisperer之类的功能,该功能可以提供生成式AI代码补全。

上一节我们介绍了AWS开发的基本概念,本节中我们来看看如何在实际环境中配置这些工具。


在GitHub Codespaces中配置

如果你使用GitHub Codespaces(一个可以运行在浏览器中的云端开发环境),并在此处创建了一个代码空间,那么你可以启动它。如图所示,工具包已经安装完毕。

如果我想知道这是如何完成的,可以转到“扩展”面板。查看AWS Toolkit,你会发现它已经与基于网页版的Visual Studio Code集成。

进一步观察,可以看到CodeWhisperer也已安装。


启用与使用CodeWhisperer

那么,如何看到它运行呢?我们可以转到AWS面板。

请注意,底部有一个名为“开发者工具”的区域,在“CodeWhisperer”选项下,我们可以执行诸如“恢复自动建议”或运行“安全扫描”等操作。它提供了一些高级功能,可以根据你的工作流程(例如与GitHub Copilot并行使用)来工作。你也可以根据需要临时禁用它。

主要收获是,你可以在基于网页的GitHub Codespaces环境中同时使用这些工具。


其他配置方法:Cargo Lambda

还有其他方法需要注意,其中之一是使用Cargo Lambda。我们可以复制相关命令进行设置。

设置完成后,我们可以在终端中输入命令:

cargo lambda

现在,我们就有了cargo lambda builddeployinitwatchhelp等命令可用。


在本地VSCode中配置

我们同样可以在常规的本地Visual Studio Code中完成此配置。事实上,我已经在此处安装了它。

我本地版本的VSCode中同样有AWS CodeWhisperer。此外,我还连接了我的IAM凭证,使我能够查看整个AWS账户。通常,将开发环境与你的AWS账户集成是一个好主意。

同样,你只需在扩展中搜索“AWS Toolkit”并安装,它就可以设置好整个环境。


集成开发的优势

为使用Rust和AWS Lambda开发而设置好此环境后,一个好处是:当你通过Cargo Lambda部署了某些功能后,你可以右键点击它并直接调用,或者从终端调用。

Visual Studio Code内部的AWS Toolkit以及与CodeWhisperer的集成(我们也可以从中获取代码建议),为开发AWS Rust工具提供了一个出色的界面。


总结

本节课中,我们一起学习了如何在GitHub Codespaces和本地Visual Studio Code中配置AWS Toolkit与CodeWhisperer,以支持Rust语言的AWS开发。我们了解了工具的基本功能、安装步骤以及集成后带来的便捷操作,例如直接调用部署的Lambda函数。这些工具能显著提升开发无服务器应用的效率。

071:Cargo Lambda工具入门

概述

在本节课中,我们将要学习AWS Lambda成本的四个主要驱动因素,并了解Rust运行时如何在这些方面提供显著优势。我们将逐一分析每个成本驱动因素,并对比Python与Rust在Lambda环境中的性能差异。


成本驱动因素一:调用次数

上一节我们介绍了课程概述,本节中我们来看看第一个成本驱动因素——调用次数。

AWS Lambda根据函数被调用的次数进行计费。每次函数被调用,都会产生一定的费用。对于高调用频率的应用,这项成本会快速累积。

以下是调用次数相关的关键点:

  • 每次Lambda函数被调用都会产生费用
  • 高调用频率的应用需要特别注意成本控制
  • 需要监控和优化触发机制以确保成本效益

成本驱动因素二:执行时间

了解了调用次数的影响后,我们来看看第二个成本驱动因素——执行时间。

执行时间指Lambda函数处理单个事件所需的时间。计算速度在这里至关重要,因为更快的执行速度意味着更低的成本。

在Rust与Python的对比中,Rust通常能提供10到1000倍的性能提升。这意味着相同的任务,Rust可能只需要毫秒级的时间,而Python可能需要秒级。


成本驱动因素三:内存使用

除了执行时间,内存使用是另一个重要的成本因素。

Python的内存使用效率远低于Rust。研究表明,Python的内存使用量可能是Rust的70倍。在AWS Lambda的定价模型中,内存分配直接决定了费用。

当配置Lambda函数时,您可以设置函数可用的内存量,这会隐式分配CPU能力和网络带宽等资源。Python的内存低效性限制了您在这些方面的选择。


成本驱动因素四:多线程支持

最后,我们来看看一个较少被讨论但同样重要的因素——多线程支持。

Python不支持真正的线程。这意味着如果您使用具有多核的大型内存Lambda实例,这些核心在大多数时间可能处于空闲状态,除非您使用进程等复杂方法,但这又会增加内存使用。

相比之下,Rust可以轻松利用多核环境。例如,使用rayon库,您可以自动为每个核心分配线程,无需太多额外工作。实际上,您可以通过迭代轻松实现每个迭代对应一个核心线程。

// 使用rayon库进行并行迭代的示例
use rayon::prelude::*;

fn main() {
    let data = vec![1, 2, 3, 4, 5];
    
    // 并行处理每个元素
    data.par_iter().for_each(|&x| {
        println!("Processing {} on thread", x);
    });
}

总结

本节课中我们一起学习了AWS Lambda的四个主要成本驱动因素:调用次数、执行时间、内存使用和多线程支持。通过对比Python和Rust在这些方面的表现,我们看到Rust在服务器无计算环境中具有数量级更高的效率。

虽然理论上Python可以用于大规模数据工程和服务器无计算,但就像“巧克力蛋糕能帮助减肥”这种说法一样,它没有讲述完整的故事。实际上,Rust在服务器无计算中的效率远高于Python,合理地将Rust纳入您的工作流程可以显著节省成本。

072:Rust在AWS Lambda中的成本优势 💰

在本节课中,我们将探讨Rust运行时在AWS Lambda服务中带来的显著成本优势。我们将分析影响Lambda成本的四个主要驱动因素,并逐一对比Rust与Python在这些方面的表现。

概述

AWS Lambda是一种按使用量付费的无服务器计算服务。其成本主要由四个因素决定:调用次数执行时间内存使用量以及多核利用效率。我们将看到,Rust凭借其高性能和低资源消耗的特性,在每个方面都优于Python,从而能显著降低运营成本。

成本驱动因素分析

以下是影响AWS Lambda成本的四个关键驱动因素。

1. 调用次数

第一个成本驱动因素是调用次数。Lambda服务根据函数被调用的次数进行计费。这意味着每次函数被触发执行,都会产生相应的费用。

因此,对于一个被频繁调用的Lambda函数,其累积成本可能相当可观。如果你构建的应用程序涉及大量的函数调用,就需要仔细监控和优化这些触发机制,以确保成本可控且调用成功。

2. 执行时间

第二个成本驱动因素是执行时间,即处理单个事件所花费的时长。在这个环节,计算速度至关重要。

与Python相比,Rust在执行速度上具有巨大优势。例如,在某些场景下,基于Rust的Linter工具性能可达Python版本的10到100倍。对于其他类型的代码,也可能实现从10倍到1000倍不等的执行时间缩减。更短的执行时间直接意味着更低的计算成本。

3. 内存使用量

第三个因素是内存使用量,这是Python难以绕开的瓶颈。无论你如何用C语言优化Python代码,都无法改变Python消耗更多内存的事实。

近期研究表明,Python的内存使用量可能是Rust的70倍之多。在AWS Lambda中,你需要为函数配置内存额度,而定价模型与此直接相关:更高的内存配置意味着更高的费用。更大的内存额度也会隐式地分配更多的CPU算力和网络带宽。

因此,Python较低的内存效率迫使你不得不配置更大的实例,而使用Rust则可以在更小的实例上运行,避免了资源浪费。

4. 多核利用效率

第四个因素是多核利用效率,这一点常被忽视。Python不支持真正的多线程,这带来了一个问题。

假设你使用一个配置了4核或6核的大内存Lambda实例,这些核心在Python环境下大多会处于空闲状态,除非你采用一些复杂技巧(例如使用多进程),但这又会进一步增加内存消耗。Python在多线程利用上陷入了两难境地。

然而,Rust则能轻松解决这个问题。你可以利用Rust开启多线程,例如使用rayon库,它能自动为每个CPU核心分配线程,而无需开发者进行复杂操作。实际上,你甚至可以通过迭代操作,让每次迭代在一个独立的核心线程上执行。

使用Rust,你可以充分利用Lambda环境的多核计算能力,几乎不需要额外的工作。

总结

本节课我们一起学习了AWS Lambda的四个核心成本驱动因素:调用次数、执行时间、内存使用量和多核利用效率。通过对比分析,我们发现Rust在执行速度内存效率并发处理方面均大幅领先于Python。

当有人说Python适合大规模数据工程和无服务器架构时,这就像说巧克力蛋糕能帮你减肥一样——在极端特定条件下或许成立,但并未反映全貌。事实是,对于无服务器计算,Rust的效率比Python高出数个数量级。关键在于,你需要评估自己的工作流程,找到适合引入Rust的环节。采用Rust运行时,将为你节省可观的成本。

073:使用AWS Lambda控制台

概述

在本节课中,我们将学习如何在AWS Lambda控制台中快速构建和测试两个简单的Lambda函数。我们将创建一个执行加法运算的函数,以及另一个执行除法运算的函数。通过这个过程,你将了解在云端进行快速原型开发的基本流程。


构建第一个Lambda函数:加法器

上一节我们介绍了本教程的目标,本节中我们来看看如何构建第一个执行加法运算的Lambda函数。

首先,我们需要在AWS控制台中创建一个新的Lambda函数。以下是具体步骤:

  1. 在AWS控制台导航到Lambda服务,点击“创建函数”。
  2. 选择“从头开始创作”。
  3. 为函数命名,例如 add
  4. 在“运行时”选项中,选择 Python 3.11。选择Python进行原型设计非常方便,即使后续可能转换为Rust,也能在控制台中直观地查看和测试代码。
  5. 在“架构”下,选择 arm64。这是为了节省运行成本。
  6. 点击“创建函数”。

创建完成后,我们将进入函数代码编辑器。我们将编写一个简单的加法函数。

以下是函数的核心代码逻辑:

import json

def lambda_handler(event, context):
    # 从传入的事件中提取 x 和 y 参数
    x = event['x']
    y = event['y']
    # 执行加法计算
    total = x + y
    # 构造并返回响应
    return {
        'statusCode': 200,
        'body': json.dumps({
            'total': total
        })
    }

代码解析:

  • lambda_handler 是Lambda函数的入口点。
  • event 参数包含了调用函数时传入的数据。
  • 我们从 event 字典中取出键为 ‘x’‘y’ 的值。
  • 计算两者之和,并将结果包装在一个JSON响应体中返回。

编写完代码后,点击“部署”按钮保存更改。


测试第一个Lambda函数

现在我们已经部署了加法函数,接下来需要测试它是否按预期工作。

我们将配置一个测试事件来模拟函数的调用。

  1. 在函数控制台界面,点击“测试”选项卡。
  2. 点击“创建新事件”。
  3. 为测试事件命名,例如 add_test
  4. 在事件JSON中,我们需要提供 xy 的值。一个示例如下:
    {
        "x": 10,
        "y": 20
    }
    
  5. 点击“保存”。
  6. 保存后,点击“测试”按钮来执行函数。

如果代码正确,你将在“执行结果”部分看到返回的响应,其中应包含 “total”: 30。如果在测试过程中遇到错误(例如缩进或语法问题),需要返回代码编辑器进行修正,然后重新部署并再次测试。


构建第二个Lambda函数:除以二

在成功构建并测试了加法函数后,本节我们将构建第二个函数,它接收一个数值并将其除以二。

我们重复创建函数的步骤:

  1. 返回Lambda函数列表页面,点击“创建函数”。
  2. 将新函数命名为 divide_by_two
  3. 运行时同样选择 Python 3.11,架构选择 arm64
  4. 点击“创建函数”。

在新函数的代码编辑器中,我们粘贴以下代码:

import json

def lambda_handler(event, context):
    # 从传入的事件中提取 total 参数
    # 注意:这里期望的输入是上一个加法函数的输出结构
    body = json.loads(event['body'])
    input_number = body['total']
    # 执行除以二的计算
    result = input_number / 2
    # 构造并返回响应
    return {
        'statusCode': 200,
        'body': json.dumps({
            'calculation': result
        })
    }

代码解析:

  • 这个函数预期接收上一个加法函数的输出作为输入。
  • 它首先解析传入事件 eventbody 字段(这是一个JSON字符串),将其转换为Python字典。
  • 然后从字典中取出 ‘total’ 键对应的值。
  • 将该值除以2,并将结果包装返回。

同样,点击“部署”按钮保存代码。


测试第二个Lambda函数

现在我们来测试除法函数。我们需要模拟一个包含加法函数输出的测试事件。

  1. divide_by_two 函数的控制台,点击“测试”选项卡。
  2. 创建一个新的测试事件,命名为 payload_test
  3. 在事件JSON中,我们需要模拟加法函数的输出结构。示例如下:
    {
        "body": "{\"total\": 30}"
    }
    
    注意:这里的 body 是一个字符串,其内容是JSON格式,包含了键 total 和值 30
  4. 点击“保存”,然后点击“测试”。

如果一切正常,执行结果应返回 {“calculation”: 15.0},这表明函数成功接收了输入 30 并计算出了 30 / 2 = 15


总结与展望

本节课中我们一起学习了如何在AWS Lambda控制台中使用Python快速构建和测试两个独立的函数:一个加法函数和一个除法函数。我们涵盖了从创建函数、编写处理逻辑、部署代码到配置测试事件进行验证的完整流程。

通过这个练习,你掌握了在云端进行无服务器函数原型开发的基本方法。使用Python等高级语言可以极大地提升原型开发的速度。未来,你可以将这些函数组合起来,例如使用AWS Step Functions等无服务器编排工具,构建更复杂的工作流,让第一个函数的输出自动成为第二个函数的输入,从而实现连贯的数据处理管道。

074:使用Step Functions控制台 🚀

在本节课中,我们将学习如何使用AWS Step Functions控制台来编排一个由两个AWS Lambda函数组成的分布式应用。我们将通过一个简单的示例,演示如何将“加法”和“除以2”两个函数串联起来,并观察数据如何在它们之间流转。


概述

我们将利用AWS Step Functions这一无服务器编排服务,来协调两个预先构建好的Lambda函数:“加法”函数和“除以2”函数。通过Step Functions的可视化工作流,我们可以轻松地定义执行顺序、传递数据,并获得强大的调试和可观察性能力。


访问Step Functions控制台

首先,我们需要进入AWS Step Functions的控制台界面。在这里,我们可以创建和管理状态机,即我们的工作流定义。

定位已有的Lambda函数

在创建状态机之前,我们需要知道要编排哪些Lambda函数。我通常会转到AWS Lambda控制台,并按“最后修改时间”排序,以快速找到我之前构建的两个函数:divide_by_twoadd

创建新的状态机

接下来,我们点击“创建状态机”。对于原型设计,可视化工作流编辑器非常直观高效,因此我们选择使用它。

设计工作流

我们知道需要编排两个Lambda函数。以下是构建步骤:

  1. 从左侧面板拖拽一个“Lambda调用”任务到画布中。
  2. 将这个任务命名为 Add,以便清晰地标识其作用。
  3. 配置该任务,指定要调用的Lambda函数为 add
  4. 再次拖拽第二个“Lambda调用”任务到画布,放在第一个任务之后。
  5. 将第二个任务命名为 DivideBy2
  6. 配置该任务,指定要调用的Lambda函数为 divide_by_two

关键配置点:每个任务的“函数名称”必须与我们Lambda控制台中的实际函数名完全一致。Step Functions会自动将上一个任务的输出作为下一个任务的输入载荷,这得益于我们函数的设计。

审查与创建

完成可视化设计后,我们点击“下一步”,可以审查整个状态机的JSON定义。再次点击“下一步”,为状态机命名,例如 AddAndDivide,然后点击“创建状态机”。

执行工作流

状态机创建成功后,我们可以立即执行它。点击“开始执行”。要触发这个工作流,我们需要提供初始输入数据,格式通常是一个JSON对象。

我们输入以下测试数据:

{
  "x": 10,
  "y": 20
}

然后点击“开始执行”。

观察执行过程

执行开始后,我们可以实时地、逐步地观察工作流的运行状态:

  1. 第一步 (Add):我们可以看到输入是 {"x":10, "y":20}。执行完成后,输出是这两个数的和,例如 {"total": 30}
  2. 第二步 (DivideBy2):Step Functions会自动将第一步的输出 {"total": 30} 作为第二步的输入。执行完成后,输出是总数除以2的结果,例如 {"result": 15}

通过这个界面,我们可以清晰地看到数据在函数间的传递过程,以及每一步的输入和输出。


总结

本节课中,我们一起学习了如何使用AWS Step Functions控制台来编排Lambda函数。我们完成了从定位函数、可视化设计工作流、配置任务到执行和调试的完整流程。Step Functions的强大之处在于它提供了出色的可观察性、调试能力和可复现性,让你能精确掌控分布式系统的数据流转。结合像Rust这样高效、安全且支持现代编译的工具,Step Functions成为了构建下一代机器学习运维(LLMOps)和数据工程工作流的强大方式。我们轻松地利用了两个现有函数,通过AWS Step Functions构建起了完整的编排逻辑。

075:通过AWS CLI调用Step Functions

在本节课中,我们将学习如何通过AWS命令行界面(CLI)来调用AWS Step Functions,从而以编程方式执行一个编排了多个Lambda函数的分布式应用。这是一种无需进行复杂SDK开发即可测试和验证工作流的方法。

概述

当您拥有一个Step Function状态机,它负责编排一个或多个Lambda函数以构成一个协调的分布式应用时,通过AWS Cloud Shell和CLI来执行它是非常高效的方式。本节将演示具体操作步骤。

准备工作

首先,您需要有一个已部署的Step Functions状态机。在AWS管理控制台的Step Functions服务页面,可以找到状态机的ARN(Amazon资源名称),这是调用它的关键标识。

上一节我们介绍了Step Functions的基本概念,本节中我们来看看如何通过命令行工具与之交互。

使用AWS CLI执行状态机

以下是使用AWS CLI启动状态机执行的核心命令格式:

aws stepfunctions start-execution --state-machine-arn <您的状态机ARN> --input <JSON格式的输入数据>

现在,让我们逐步分解这个过程。

步骤一:打开AWS Cloud Shell

您可以直接在AWS管理控制台启动Cloud Shell,这将提供一个预配置了AWS CLI的终端环境。

步骤二:构造并执行命令

在Cloud Shell中,您需要完成以下操作:

  1. 获取状态机ARN:从Step Functions控制台复制您的状态机ARN。
  2. 准备输入数据:准备一个JSON格式的字符串作为工作流的输入。
  3. 组合命令并执行:将ARN和输入数据填入上述命令格式并运行。

例如,一个完整的命令可能如下所示:

aws stepfunctions start-execution --state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:MyStateMachine --input "{\"number1\": 7, \"number2\": 7}"

执行成功后,CLI将返回本次执行的ARN等信息。

步骤三:验证执行结果

执行命令后,您可以立即返回Step Functions控制台查看执行历史。刷新页面后,您应该能看到新的执行记录。点击进入某次执行,可以查看详细的步骤执行情况和输入/输出载荷,这形成了一个强大的迭代反馈循环。

进行迭代测试

为了更清晰地观察不同输入下的执行情况,您可以方便地修改输入数据并重新运行命令。例如,将输入值从 77 改为 1313,然后再次执行。在控制台中,您可以验证新的执行是否接收了正确的输入并产生了预期的输出(例如,总和为26)。

通过这种方式,您可以快速测试不同的想法,并确保您的工作流按预期运行。

总结

本节课中我们一起学习了如何使用AWS CLI和Cloud Shell来调用Step Functions状态机。这种方法让您能够以编程化、可脚本化的方式触发复杂的工作流,无需编写额外的应用程序代码,非常适合进行快速测试和集成。

076:构建Rust AWS Lambda加法函数

概述

在本节课中,我们将学习如何使用Rust语言构建一个简单的AWS Lambda函数。这个函数将接收两个数字作为输入,计算它们的和并返回结果。我们将从项目创建开始,逐步完成本地开发、测试、构建和部署到AWS Lambda的完整流程。

项目结构与创建

上一节我们介绍了课程目标,本节中我们来看看如何创建和初始化一个Rust Lambda项目。

我们使用cargo-lambda工具来创建项目。这是一个专门为Rust Lambda开发设计的工具,可以快速搭建项目骨架。

以下是创建项目的命令:

cargo lambda new add_function

项目创建后,其目录结构非常简单,主要包含以下文件:

  • src/main.rs:Lambda函数的主要逻辑代码。
  • Cargo.toml:项目的依赖配置文件。

Cargo.toml文件已经预先配置好了Lambda函数所需的核心依赖,例如用于序列化的serde、异步运行时tokio以及AWS Lambda运行时lambda_runtime

Lambda函数逻辑解析

了解了项目结构后,我们来看看函数的核心逻辑是如何实现的。

我们构建的是一个简单的加法函数,其目的是清晰地展示Rust Lambda的工作原理。

首先,我们定义了两个结构体(struct)来处理请求和响应。

请求结构体 Request 定义了函数输入的数据格式:

#[derive(Deserialize)]
struct Request {
    x: i32,
    y: i32,
}

#[derive(Deserialize)] 属性使得该结构体能够自动从传入的JSON事件中解析出 xy 字段。

响应结构体 Response 定义了函数返回的数据格式:

#[derive(Serialize)]
struct Response {
    total: i32,
}

#[derive(Serialize)] 属性使得该结构体能够被自动序列化为JSON返回。

接下来是核心的处理函数 function_handler

async fn function_handler(event: Request, _context: Context) -> Result<Response, Error> {
    let total = event.x + event.y;
    let resp = Response { total };
    Ok(resp)
}

这是一个异步函数,它接收 Request 和Lambda上下文作为参数。函数内部将 xy 相加,将结果封装到 Response 结构体中,然后通过 Ok() 返回。

最后,主函数 main 负责启动Lambda运行时并注册我们的处理函数:

#[tokio::main]
async fn main() -> Result<(), Error> {
    lambda_runtime::run(service_fn(function_handler)).await
}

这部分代码通常由cargo-lambda工具自动生成,开发者一般无需修改。

本地开发与测试

编写完函数逻辑后,我们需要在本地进行测试,以确保其功能正确。

为了简化开发流程,我们可以创建一个Makefile来封装常用的命令。这样我们就不需要记住复杂的命令参数,只需执行简单的make指令即可。

以下是Makefile中可能包含的几个关键命令:

watch:
    cargo lambda watch

invoke:
    cargo lambda invoke --data-file event.json

使用 make watch 命令可以启动本地开发服务器。它会监控代码变化并自动重新编译。

在另一个终端中,使用 make invoke 命令可以调用本地运行的Lambda函数进行测试。我们需要准备一个event.json文件来模拟输入事件,例如:

{"x": 5, "y": 3}

执行后,我们将得到输出结果 {"total": 8}

构建与部署

本地测试通过后,下一步就是将函数构建并部署到AWS云端。

为了获得最佳的成本效益,我们可以选择为ARM64架构构建,这通常能带来更低的执行成本。

以下是构建和部署的命令:

# 为ARM64架构发布构建
cargo lambda build --release --arm64

# 部署到AWS Lambda
cargo lambda deploy

同样,我们可以将这些命令放入Makefile

build:
    cargo lambda build --release --arm64

deploy:
    cargo lambda deploy

执行 make build 进行构建,然后执行 make deploy 进行部署。部署过程非常快速,因为Rust生成的二进制文件体积非常小。

远程调用与监控

函数部署成功后,我们可以在AWS管理控制台中进行测试和监控。

我们不仅可以在本地调用,还可以直接远程调用已部署的函数:

cargo lambda invoke --remote --data-file event.json add_function

在AWS Lambda控制台的“监控”标签页下,我们可以查看函数的调用指标。对于这个Rust加法函数,你会发现其执行时间通常在1毫秒左右,内存使用量也极低(约15MB)。

与Python等解释型语言相比,Rust Lambda在冷启动速度、执行效率和内存开销上具有显著优势。由于AWS Lambda按执行时间和分配的内存计费,因此使用Rust可以显著降低运行成本,尤其适合高性能、高频率调用的场景。

总结

本节课中我们一起学习了使用Rust构建和部署AWS Lambda函数的完整流程。我们从使用cargo-lambda创建项目开始,逐步实现了函数逻辑、进行了本地测试、为生产环境构建,并最终部署到AWS云端。通过这个简单的加法函数示例,我们看到了Rust在Serverless领域带来的卓越性能与成本效益,其极快的执行速度和极低的内存占用是构建高效、经济Lambda函数的强大工具。

077:构建Rust AWS Lambda除二函数

概述

在本节课中,我们将学习如何使用Rust语言从头开始构建一个简单的AWS Lambda函数。这个函数的功能是接收一个数字载荷,将其除以二,并返回计算结果。我们将涵盖从项目初始化、代码编写、本地测试到最终部署的完整流程。


项目初始化与设置

上一节我们介绍了本课程的目标,本节中我们来看看如何初始化一个新的Rust Lambda项目。

首先,我们使用cargo-lambda工具创建一个新的Lambda函数项目。我们将项目命名为divide_by2

cargo lambda new divide_by2

创建项目后,我们进入项目目录。为了简化后续的构建和部署流程,我们通常会准备一个Makefile文件来帮助我们执行常用命令。


编写函数逻辑

在项目初始化完成后,我们需要编写核心的函数逻辑。以下是实现步骤:

  1. 定义函数意图:我们首先明确函数的目标。函数名为divide_by_two,它接收一个名为total的数字,并返回其一半作为calculation结果。
  2. 修改请求与响应结构:我们需要调整自动生成的代码模板中的数据结构,以匹配我们的输入(total)和输出(calculation)。
  3. 实现核心计算:在main函数中,我们处理传入的载荷,执行除法运算,并构造响应。

以下是核心代码修改部分的示例。我们定义了一个包含total字段的请求结构体和一个包含calculation字段的响应结构体。

// 请求载荷结构
struct Request {
    total: f64,
}

// 响应结构
struct Response {
    calculation: f64,
}

// 处理函数
async fn function_handler(event: Request) -> Result<Response, Error> {
    let calculation = event.total / 2.0;
    Ok(Response { calculation })
}

本地测试与调试

代码编写完成后,在部署到云端之前,进行本地测试至关重要。这能帮助我们快速发现并修复问题。

以下是进行本地测试的步骤:

  1. 启动本地Lambda运行时:我们使用make watch命令(其背后是cargo lambda watch)来启动一个本地Lambda模拟环境,并监控代码变化。
  2. 调用测试函数:在另一个终端中,我们使用make invoke命令来模拟一次函数调用。我们需要准备一个包含测试数据(例如{"total": 10})的JSON文件作为输入。
  3. 验证输出:观察函数返回的响应,确认calculation字段的值是否为5,以验证函数逻辑正确。

如果测试成功,我们将看到类似 {"calculation": 5.0} 的输出。


构建与部署到AWS

本地测试通过后,就可以将函数部署到AWS Lambda服务了。

以下是构建和部署的步骤:

  1. 构建发布版本:运行make build命令。这会使用cargo lambda build编译Rust代码,并针对AWS Graviton2处理器(ARM64架构)进行优化,以获得更好的性价比。
  2. 部署函数:运行make deploy命令。这个命令会使用AWS CLI或Serverless Framework将编译好的二进制文件打包并上传到你的AWS账户,创建或更新对应的Lambda函数。
  3. 远程调用验证:部署完成后,你可以再次使用make invoke命令(此时会指向云端函数)或直接通过AWS控制台进行远程调用,以确保函数在云环境中也能正常工作。

整个过程自动化程度高,只需几条命令即可完成从代码到云服务的交付。


总结

本节课中我们一起学习了使用Rust构建AWS Lambda函数的完整流程。我们从使用cargo lambda创建新项目开始,接着编写了接收输入、执行计算并返回结果的函数逻辑。然后,我们在本地环境中对函数进行了测试和调试。最后,我们将函数编译为ARM64架构的二进制文件,并将其成功部署到了AWS Lambda服务。通过这个简单的“除二函数”示例,你掌握了使用Rust开发无服务器函数的基本方法。

078:通过CLI调用AWS Step Function 🚀

在本节课中,我们将学习如何通过命令行界面(CLI)调用一个由Rust编写的AWS Lambda函数组成的AWS Step Functions工作流。我们将从确认已部署的Lambda函数开始,逐步创建并运行一个Step Functions状态机,最后通过CLI命令来触发执行。


确认已部署的Lambda函数

首先,我们需要确认用于构建工作流的Rust Lambda函数已经成功部署在AWS上。

以下是操作步骤:

  1. 在AWS控制台中导航到 Lambda服务
  2. 在函数列表中,确认名为 rustaddivide_by_two 的函数存在。
  3. 检查这两个函数的运行时环境,确保它们都是 Rust运行时

确认函数部署无误后,我们就可以进入下一步,创建Step Functions工作流。


创建Step Functions状态机

上一节我们确认了Lambda函数已就绪,本节中我们来看看如何在AWS Step Functions中创建一个可视化的工作流。

  1. 在AWS控制台中导航到 Step Functions服务
  2. 点击 “创建状态机”
  3. 选择 “使用可视化编辑器设计工作流” 选项。
  4. 为状态机命名,例如 rust_based_workflow
  5. 从左侧面板拖拽两个 “任务” 状态到设计画布上。
  6. 配置第一个任务:
    • 名称add
    • 集成类型:选择 Lambda调用函数
    • 函数:选择之前部署的 rustad 函数
  7. 配置第二个任务:
    • 名称divide_by_2
    • 集成类型:选择 Lambda调用函数
    • 函数:选择之前部署的 divide_by_two 函数
  8. 按照逻辑连接这两个任务状态。
  9. 点击 “下一步”,预览工作流布局。
  10. 点击 “创建状态机” 完成创建。

在控制台执行工作流

状态机创建完成后,我们可以先在AWS控制台中手动执行一次,以验证工作流是否按预期运行。

  1. 在状态机详情页,点击 “开始执行”
  2. 在输入框中,提供一个JSON格式的输入,例如:
    {
      "x": 1,
      "y": 3
    }
    
  3. 点击 “开始执行”
  4. 观察执行详情页面。工作流会先调用 add 函数计算 x + y,然后将结果传递给 divide_by_2 函数进行除以2的操作。
  5. 最终输出应类似于:
    {
      "output_calculation": 2,
      "input_total": 4
    }
    
    这表示 (1 + 3) / 2 = 2 的计算成功。


通过AWS CLI调用工作流

在控制台验证工作流运行成功后,我们将学习如何通过AWS命令行工具(CLI)来触发执行,这是实现自动化的重要一步。

首先,确保你的终端环境已配置好AWS CLI,并且输出格式设置为JSON,以便于解析结果。可以使用以下命令设置:

aws configure set output json

接下来,使用 start-execution 命令来触发状态机的执行。你需要提供状态机的Amazon资源名称(ARN)和输入参数。

以下是调用命令的示例:

aws stepfunctions start-execution \
  --state-machine-arn <你的状态机ARN> \
  --input '{"x": 5, "y": 3}'

请将 <你的状态机ARN> 替换为你实际创建的状态机ARN。

命令执行后,CLI会返回本次执行的ARN。你可以使用这个ARN在Step Functions控制台中查看详细的执行历史和结果。

为了验证CLI调用是否生效,你可以返回AWS控制台,刷新执行列表。你应该能看到一条新的执行记录,其输入参数正是我们通过CLI传递的 {"x": 5, "y": 3}


总结

本节课中我们一起学习了如何构建和调用一个基于Rust的AWS Step Functions工作流。我们首先确认了Rust Lambda函数的部署状态,然后使用可视化编辑器创建了一个包含两个任务的状态机。我们在控制台手动执行并验证了工作流的逻辑。最后,我们掌握了通过AWS CLI命令 aws stepfunctions start-execution 来远程触发工作流执行的方法,从而实现了将高性能、低成本的Rust计算工作流集成到自动化流程中的目标。

079:构建可链式调用的Rust AWS Step Functions 🧩

在本节课中,我们将学习如何使用Rust语言构建可与AWS Step Functions链式调用的Lambda函数。我们将创建两个简单的Lambda函数,并通过AWS Step Functions将它们连接成一个工作流,实现类似“乐高积木”式的可视化编排。

概述

AWS Step Functions是一种强大的无服务器工作流服务,允许你将多个操作(如Lambda函数)链接在一起。一个函数的输出可以作为下一个函数的输入。其优势在于提供了可视化的拖放界面来创建工作流,并具备出色的执行过程内省和调试能力。我们将使用Rust和cargo-lambda工具来实现这一功能。

创建第一个Lambda函数(Rust Marco)

首先,我们使用cargo-lambda库来创建第一个Lambda函数。

以下是创建和配置rust-marco函数的步骤:

  1. 创建项目:使用cargo lambda new rust-marco命令创建一个新的Lambda项目。
  2. 编写函数逻辑:在src/main.rs中,我们定义处理程序。函数接收一个包含name字段的JSON输入。
  3. 核心逻辑:如果输入的name是“Marco”,则返回body为“polo”的响应;否则返回“nobody”。我们使用tracing库添加日志以便调试。
  4. 构建与部署:使用make releasemake deploy命令(或对应的cargo lambda命令)来构建和部署函数。

以下是rust-marco函数的核心代码逻辑:

async fn function_handler(event: LambdaEvent<Value>) -> Result<Value, Error> {
    let (event, _context) = event.into_parts();
    let name = event["name"].as_str().unwrap_or("");

    let body = if name == "Marco" { "polo" } else { "nobody" };
    tracing::info!("Response body: {}", body);

    let resp = Response { body: body.to_string() };
    Ok(serde_json::to_value(&resp)?)
}

创建第二个Lambda函数(Rust Polo)

接下来,我们创建第二个函数,它将处理第一个函数的输出。

以下是创建和配置rust-polo函数的步骤:

  1. 创建项目:同样使用cargo lambda new rust-polo创建项目。
  2. 编写函数逻辑:这个函数期望接收一个包含body字段的JSON输入(即上一个函数的输出)。
  3. 核心逻辑:检查body字段是否包含“polo”字符串。如果包含,则返回“you win”;否则返回“you lose”。
  4. 构建与部署:同样使用make命令或cargo lambda进行构建和部署。

以下是rust-polo函数的核心代码逻辑:

async fn function_handler(event: LambdaEvent<Value>) -> Result<Value, Error> {
    let (event, _context) = event.into_parts();
    let body = event["body"].as_str().unwrap_or("");

    let result = if body.contains("polo") { "you win" } else { "you lose" };
    tracing::info!("Game result: {}", result);

    let resp = Response { body: result.to_string() };
    Ok(serde_json::to_value(&resp)?)
}

在AWS控制台创建Step Functions工作流

现在我们已经有了两个可以独立运行的Lambda函数,下一步是在AWS控制台中将它们链接起来。

以下是创建Step Functions状态机的步骤:

  1. 在AWS Step Functions控制台点击“创建状态机”。
  2. 在可视化编辑器中,从左侧拖放一个“Lambda调用”任务到画布中。
  3. 配置该任务,选择我们部署的rust-marco函数。
  4. 再拖放第二个“Lambda调用”任务。
  5. 配置第二个任务,选择rust-polo函数。Step Functions会自动将第一个任务的输出作为第二个任务的输入传递。
  6. 连接两个任务,为状态机命名(例如rust-marco-polo-chain)并创建。

工作流的定义类似于以下JSON结构(由控制台可视化生成):

{
  "Comment": "A simple Marco Polo chain",
  "StartAt": "RustMarco",
  "States": {
    "RustMarco": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:rust-marco",
      "Next": "RustPolo"
    },
    "RustPolo": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:rust-polo",
      "End": true
    }
  }
}

执行与测试工作流

创建好状态机后,我们可以执行它并观察结果。

以下是执行和验证工作流的步骤:

  1. 在状态机详情页点击“开始执行”。
  2. 提供输入数据,例如:{"name": "Marco"}
  3. 观察执行过程。你可以看到每个步骤的输入和输出,这为调试提供了极大的便利。
  4. 成功的执行流将是:rust-marco接收{"name":"Marco"},返回{"body":"polo"}rust-polo接收此结果,返回{"body":"you win"}
  5. 你可以尝试输入{"name": "其他名字"}来触发失败或不同的执行路径,并观察系统如何反应。

总结

本节课中,我们一起学习了如何使用Rust构建可链式调用的AWS Step Functions。我们首先使用cargo-lambda创建并部署了两个简单的Lambda函数。然后,我们在AWS控制台中通过拖放方式,将这两个函数连接成一个可视化的工作流。最后,我们执行了该工作流,并利用Step Functions的内省功能观察了数据的传递过程。这种方法使得构建、编排和调试复杂的无服务器工作流变得直观且高效。

080:基于EFS挂载的AWS Lambda无服务器MLOps 🚀

在本教程中,我们将学习如何构建一个基于AWS Lambda的无服务器机器学习推理服务。该服务使用ONNX模型格式,模型文件通过Amazon EFS(弹性文件系统)挂载。我们将涵盖从环境设置、安全配置到代码部署的完整流程。

概述 📋

上一节我们介绍了无服务器MLOps的概念,本节中我们来看看如何具体实现一个基于EFS和AWS Lambda的ONNX模型推理服务。核心目标是将大型模型文件存储在EFS中,并通过高性能的Rust语言编写的Lambda函数进行调用,从而无需管理服务器即可提供推理服务。

架构与核心组件 🏗️

整个架构的核心是Amazon EFS文件系统,所有服务都必须能通过端口5049与其通信。以下是实现此架构所需的关键步骤。

1. 创建并配置Amazon EFS

首先,你需要在AWS控制台创建一个EFS文件系统。

  • 在AWS控制台导航至“Amazon EFS”服务。
  • 点击“创建文件系统”,通常选择默认配置即可。

创建完成后,需要将其挂载到你的开发环境(例如AWS Cloud9)以便上传模型文件。

以下是挂载EFS的关键步骤:

  • 在EFS控制台,找到你的文件系统,点击“附加”按钮。
  • 选择使用“EFS挂载助手”的选项,它会生成一个挂载命令。
  • 在你的Cloud9实例终端中运行该命令,例如:sudo mount -t efs -o tls fs-12345678:/ /mnt/efs
  • 确保与该EFS关联的安全组允许来自你开发环境的5049端口的入站流量。

2. 配置网络与安全组

这是确保服务间能通信的关键一步。你需要配置安全组规则,允许Lambda函数和你的开发机器访问EFS。

  • 在EC2控制台的“安全组”部分,找到与你的EFS文件系统关联的安全组。
  • 添加入站规则,允许来自你的Lambda函数VPC的IP范围或特定安全组的TCP 5049端口流量。
  • 同样,确保你的Cloud9实例的安全组也允许与EFS通信。

3. 设置AWS Lambda函数

现在,我们来配置将调用EFS中模型的Lambda函数。

首先,在Lambda控制台创建一个新的函数。然后需要进行两项关键配置:

配置VPC:

  • 在Lambda函数的“配置”标签下,找到“VPC”设置。
  • 将你的Lambda函数放入与EFS文件系统相同的VPC中,并选择已配置好5049端口规则的安全组。

配置文件系统:

  • 在“配置”标签下,找到“文件系统”设置。
  • 点击“添加文件系统”。
  • 你需要提供EFS文件系统ID和一个访问点
  • 访问点需要在EFS控制台中单独创建。在EFS控制台,进入“访问点”标签,点击“创建访问点”,并为其指定一个路径(如/lambda)。
  • 在Lambda配置中,将本地挂载路径设置为例如/mnt/efs

4. 开发与部署Rust Lambda函数

环境配置完成后,我们开始编写和部署推理代码。我们推荐使用GitHub Codespaces作为开发环境。

以下是核心的Rust代码结构,用于从EFS加载ONNX模型并进行推理:

// 示例:从EFS路径加载ONNX模型
use ort::Session;

fn load_model_from_efs() -> Result<Session, ort::Error> {
    // 模型文件位于EFS挂载点
    let model_path = "/mnt/efs/models/squeezenet.onnx";
    Session::builder()?
        .with_model_from_file(model_path)?
        .commit()
}

// 执行推理的函数
fn run_inference(input_data: &[f32]) -> Result<Vec<f32>, ort::Error> {
    let session = load_model_from_efs()?;
    // ... 准备输入张量 ...
    // session.run(...)
    // ... 处理并返回输出 ...
}

为了便于测试和部署,可以创建一个Makefile

deploy:
	cargo lambda deploy --profile your-aws-profile

invoke:
	cargo lambda invoke --remote \
  		--data-ascii '{"image_url": "https://example.com/cat.jpg"}' \
  		--profile your-aws-profile

使用cargo lambda工具可以轻松地构建、部署和远程调用你的Rust Lambda函数。

5. 调试与验证

在部署后,验证一切是否正常工作至关重要。

建议在代码中添加一个辅助函数,用于列出EFS挂载点中的文件,以确认模型已正确加载:

fn list_efs_files(path: &str) -> std::io::Result<()> {
    for entry in std::fs::read_dir(path)? {
        let entry = entry?;
        println!("Found in EFS: {:?}", entry.path());
    }
    Ok(())
}

调用此函数可以在Lambda日志中看到模型文件列表,这是有效的调试手段。

总结 🎯

本节课中我们一起学习了构建基于EFS和AWS Lambda的无服务器MLOps推理管道的完整流程。我们回顾一下关键步骤:首先创建并挂载EFS文件系统,并确保安全组开放5049端口;接着配置Lambda函数的VPC和文件系统挂载,关联EFS访问点;然后使用Rust和cargo-lambda工具开发并部署推理代码;最后通过添加调试信息来验证部署。

这种架构结合了Rust的高性能、ONNX的模型通用性、EFS的共享存储以及Lambda的无服务器优势,为生产环境部署机器学习模型提供了一种高效、可扩展且无需管理基础设施的解决方案。

081:使用Rust构建AWS Lambda函数URL

概述

在本节课中,我们将学习如何使用Rust语言构建一个高性能的AWS Lambda函数URL。我们将通过一个简单的加法服务示例,演示从本地开发到云端部署的完整流程。你将了解到如何利用Rust和cargo-lambda工具快速创建、测试并部署一个无服务器的RESTful服务。


1. 项目初始化与工具介绍

上一节我们介绍了课程目标,本节中我们来看看如何初始化一个Rust Lambda项目。

我们将使用cargo-lambda工具来创建和管理项目。这是一个专门为Rust语言优化AWS Lambda开发而设计的工具链。

以下是使用cargo-lambda创建新项目的命令:

cargo lambda new test

执行此命令后,工具会询问是否创建一个HTTP函数。选择“是”后,它会生成一个包含基础模板代码的项目。这个模板为我们处理了Lambda运行时与Rust之间的集成,提供了一个良好的起点。


2. 核心功能实现

现在我们已经有了项目基础,本节中我们来看看如何实现一个具体的业务逻辑:解析URL路径并进行加法运算。

我们的目标是创建一个服务,当访问/add/{a}/{b}这样的URL时,能够返回两个数字的和。为了实现这个功能,我们需要添加必要的依赖并编写处理逻辑。

首先,在Cargo.toml文件中添加serde_json依赖,用于处理JSON序列化:

serde_json = "1.0"

接下来,我们修改主处理函数。以下是实现的核心步骤:

  1. 从传入的HTTP请求中提取URL路径。
  2. 手动解析路径,将其按/分割成多个部分。
  3. 提取路径中的两个数字参数(例如,add/2/3中的23)。
  4. 将两个数字相加。
  5. 构建一个包含结果的JSON响应并返回。

以下是实现此逻辑的代码框架:

use lambda_http::{run, service_fn, Body, Error, Request, RequestExt, Response};
use serde_json::{json, Value};

async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
    // 1. 提取路径
    let path = event.uri().path();
    // 2. 分割路径
    let parts: Vec<&str> = path.split('/').collect();
    // 3. 提取参数 (假设路径格式为 /add/2/3)
    let a: i32 = parts.get(2).unwrap_or(&"0").parse().unwrap_or(0);
    let b: i32 = parts.get(3).unwrap_or(&"0").parse().unwrap_or(0);
    // 4. 计算和
    let sum = a + b;
    // 5. 构建JSON响应
    let payload = json!({ "total": sum });

    let resp = Response::builder()
        .status(200)
        .header("content-type", "application/json")
        .body(Body::from(payload.to_string()))
        .map_err(Box::new)?;
    Ok(resp)
}

3. 本地测试与运行

功能实现完成后,在部署到云端之前,我们需要在本地进行测试以确保其正常工作。

我们可以使用cargo lambda watch命令在本地启动一个模拟Lambda环境的服务器。这个命令会监听代码变化并自动重新编译。

cargo lambda watch

服务器启动后(默认在localhost:9000),我们可以使用curl命令来测试我们的加法服务:

curl http://localhost:9000/add/2/2

如果一切正常,你应该会收到一个JSON响应:{"total":4}。本地测试成功意味着我们的逻辑是正确的,可以准备进行云端部署。


4. 构建与云端部署

经过本地验证,本节中我们来看看如何将应用部署到AWS Lambda。

部署过程主要分为两步:构建和部署。我们可以使用项目中的Makefile(如果存在)或直接使用cargo-lambda命令来完成。

以下是典型的部署命令,它们会为ARM架构(Lambda上成本更低的运行时)构建项目,并处理上传等事宜:

make build
make deploy

部署脚本会自动处理诸如创建执行角色、配置权限等基础设施任务。部署完成后,你的函数就会出现在AWS Lambda控制台中。


5. 配置函数URL

函数部署成功后,默认还不能通过公共URL访问。我们需要为其配置一个“函数URL”。

以下是配置步骤:

  1. 登录AWS管理控制台,进入Lambda服务。
  2. 找到刚刚部署的函数(例如名为add_url的函数)。
  3. 进入函数的“配置”选项卡。
  4. 在左侧菜单中选择“函数URL”。
  5. 点击“创建函数URL”。
  6. 在认证类型中选择“NONE”(仅用于演示,生产环境应使用更安全的认证方式)。
  7. 点击“保存”。

配置完成后,AWS会生成一个唯一的HTTPS端点。复制这个URL,我们就可以像访问普通Web服务一样访问我们的Lambda函数了。


6. 验证云端服务

最后,让我们验证部署在云端的服务是否工作正常。

使用curl命令,访问你刚刚获得的函数URL,并附加上路径参数:

curl https://your-unique-id.lambda-url.region.on.aws/add/2/3

如果返回{"total":5},那么恭喜你,一个使用Rust编写的高性能、无服务器URL服务已经成功在AWS Lambda上运行起来了。


总结

本节课中我们一起学习了使用Rust构建AWS Lambda函数URL的完整流程。我们从项目初始化开始,使用cargo-lambda工具生成了基础模板。然后,我们实现了一个解析URL路径并执行加法运算的核心处理函数,并在本地进行了测试。接着,我们完成了针对ARM架构的构建,并将应用部署到了AWS Lambda。最后,我们为函数配置了可公开访问的URL,并成功验证了云端服务的运行。整个过程表明,Rust凭借其高性能和安全性,是构建现代无服务器REST应用的绝佳选择。

082:通过ARM64架构降低Rust AWS Lambda成本 💰

在本节课中,我们将学习如何通过结合Rust编程语言和ARM64架构来显著降低AWS Lambda的运行成本。我们将分析成本的驱动因素,并解释Rust与ARM架构如何协同作用以实现最佳性价比。


概述

AWS Lambda的成本主要由四个因素驱动。使用Rust运行时可以有效优化这些因素,从而降低成本。此外,基于ARM架构的实例能提供额外的价格优势。本节将逐一剖析这些驱动因素,并说明Rust与ARM64结合为何是服务器无计算(Serverless)的理想选择。


AWS Lambda成本的四大驱动因素 🚀

以下是影响AWS Lambda成本的四个主要方面,使用Rust运行时可以对这些方面进行优化。

1. 调用次数与执行时间

显然,Lambda的使用量越少,产生的费用就越低。这包括调用次数和执行时间。Rust卓越的计算性能在此处发挥巨大作用,因为它能带来10倍到1000倍的性能提升。性能公式可以简化为:
总成本 ∝ 调用次数 × 平均执行时间
更快的执行速度直接减少了计费时间。

2. 内存分配

Lambda根据分配的内存收费。Rust凭借其高效的内存管理,在平均场景下能减少高达7倍的内存使用量。这意味着你可以配置更低的内存规格,从而节省费用。其核心优势在于无垃圾回收和精准的内存控制。

3. 并发处理

Rust支持安全的多线程(safe true threads),能够更好地利用并发。在高并发场景下,这有助于更快地处理请求,间接减少了所需的计算资源总量和费用。

4. 架构选择:ARM64实例

除了语言层面的优化,基础设施的选择也至关重要。基于ARM架构的AWS实例(如使用Graviton2处理器的实例)平均比同规格的x86实例便宜34%。对于计算密集型应用,这能带来显著的成本节约。


超越成本:额外优势 ✨

上一节我们介绍了核心的成本驱动因素,本节中我们来看看选择Rust与ARM64架构还能带来哪些额外好处。

能效与环保目标

如果您的组织有关注能效或环保目标,ARM处理器是更优的选择。与x86架构相比,这类处理器功耗更低。这有助于企业达成其绿色计算(Green Computing)目标。

极致性能优化

Rust提供底层系统控制能力和高效的内存管理。开发者可以利用这些特性,专门为ARM64架构构建高度优化的应用程序。这可以进一步减少执行时间。虽然Lambda本身单价不高,但执行时间仍是计费依据,因此任何性能提升都能直接转化为成本节约。代码层面的优化示例如下:

// 利用Rust和ARM架构特性进行高效计算
fn optimized_computation(data: &[i32]) -> i32 {
    data.iter().sum() // 简单的例子,实际中可使用SIMD等ARM指令集优化
}

卓越的可扩展性

当我们将Rust的性能、低内存占用与ARM实例的成本优势结合起来时,其效益对于可变的工作负载尤其明显。这种组合提供了极高的性价比,使应用能够经济高效地应对流量高峰。

硬件能力支持

AWS提供了为ARM64优化的特定实例(如搭载Graviton2处理器的实例)。针对这些实例并使用Rust进行开发,可以充分发挥硬件的全部能力,从而进一步提升成本效益。


总结

本节课中我们一起学习了如何通过Rust和ARM64架构降低AWS Lambda成本。我们分析了成本的四大驱动因素——调用次数、执行时间、内存分配和并发,并揭示了Rust如何在每个环节提供优化。此外,ARM64实例的价格优势、能效和硬件支持,与Rust的低层控制能力相结合,使其成为服务器无计算场景下近乎默认的优选方案。虽然其他任务(如数据可视化或快速API原型开发)可能适合脚本语言,但对于追求高性能、低成本和可扩展性的Serverless应用,Rust与ARM64的组合无疑是“甜蜜点”。

083:83_04_01_Polars Rust AWS Lambda应用 🚀

在本节课中,我们将学习如何结合Rust生态中的Polars数据框库与AWS Lambda服务,构建高性能、低成本的数据工程应用。我们将探讨其核心优势、架构原理以及实践方式。


概述 📋

数据工程领域拥有一项“超能力”,即 Rust Polars(Rust数据框库)与 Rust AWS Lambda 的结合。这种组合能带来低内存消耗、毫秒级响应、卓越的性能以及低廉的成本。与Python、Java等解释型语言相比,Rust凭借其内存安全性和无垃圾回收机制,提供了更快的执行速度和更可预测的性能。

上一节我们介绍了Rust在系统编程中的优势,本节中我们来看看如何将其应用于数据工程和云服务。


Polars:高性能数据框库 ⚡

Polars是这项“秘密武器”的核心部分。它是一个用Rust实现的高速数据框库,其设计目标是在速度上超越许多其他语言的同类库。

核心公式/代码示例:

// 使用Polars进行数据操作的基本示例
use polars::prelude::*;

fn main() -> Result<()> {
    let df = DataFrame::new(vec![
        Series::new("name", &["Alice", "Bob", "Charlie"]),
        Series::new("value", &[10, 20, 30]),
    ])?;
    println!("{:?}", df);
    Ok(())
}

其卓越的性能使其成为全球最快的数据框库之一。


AWS Lambda:无服务器架构 ☁️

在AWS Lambda上运行Rust代码,可以充分利用无服务器架构的可扩展性和效率。

以下是几种与AWS Lambda交互和调用的方式:

  • cargo lambda invoke:通过命令行直接调用。
  • URL端点:通过HTTP URL触发函数。
  • 事件驱动:响应特定事件(如S3文件上传)。

你可以将核心业务逻辑代码与库集成,然后在main函数中处理Lambda调用,并利用优秀的cargo-lambda库将其部署到AWS运行时环境。


并行处理能力 🔄

Rust的所有权系统和内置并发支持,使得高效的并行执行成为可能。这在Python(受GIL限制)和Java(存在其他并发问题)等语言中要复杂得多。

因此,你能够充分利用多核处理器的能力,这在处理数据时能带来显著的性能提升。


成本效益 💰

基于ARM架构的Lambda函数与优化的Rust二进制文件结合,可以带来更短的执行时间和更低的内存使用率。

与基于Python和Java的解决方案相比,这直接转化为更低的运行成本。


安全性与可靠性 🛡️

Rust以其强大的安全性保证而闻名,尤其是在内存安全和线程安全方面。这类错误在Python和Java中要常见得多。


集成与现代工具链 🛠️

Rust拥有全球最优秀的生态系统之一,工具链活跃且不断增长。

以下是其生态中的关键工具和库:

  • Cargo生态系统:提供了强大的包管理和构建工具。
  • cargo-lambda:专门用于简化Rust Lambda函数的开发和部署。
  • Polars:正成为数据框处理的新兴标准。

此外,AWS Lambda服务本身已经非常成熟和成功。有趣的是,其底层采用的Firecracker开源虚拟化框架也是用Rust构建的。


总结 🎯

本节课中我们一起学习了Rust Polars与AWS Lambda如何共同构成数据工程的“新兴超能力”。我们探讨了其在性能、成本、安全性和现代工具链集成方面的显著优势。Rust为构建高效、可靠且经济的数据处理应用提供了强大的基础,值得每一位数据工程师深入了解和实践。

084:构建并部署Polars Rust AWS Lambda 🚀

在本节课中,我们将学习如何将高性能的Polars数据框库与AWS Lambda结合,并使用Cargo Lambda工具来构建和部署一个Rust函数。整个过程将展示如何创建一个能够快速处理数据的无服务器应用。


项目结构概览

首先,我们来看一下项目的基本结构。通过运行 tree 命令并排除 target 目录,可以清晰地看到项目的核心文件。

.
├── Cargo.toml
├── Makefile
└── src
    └── lib.rs

项目包含一个Cargo配置文件、一个Makefile以及位于src目录下的Rust源代码文件。代码量不大,接下来我们将逐一查看这些文件。


解析 Cargo.toml 文件

Cargo.toml 文件定义了项目的依赖项。我们使用了以下关键库:

  • 序列化/反序列化库:用于处理Lambda函数的输入和输出。
  • Polars:高性能数据框库,用于数据处理。
  • Tokio:异步运行时,允许我们编写异步代码。

大部分与Lambda运行时相关的配置都由 cargo-lambda 工具自动处理。这个工具极大地简化了开发流程。


使用 Cargo Lambda 工具

cargo-lambda 工具提供了便捷的命令来开发、测试和部署Lambda函数。

以下是开发流程中常用的命令:

  • 本地开发与测试:运行 cargo lambda watch 可以启动一个本地开发服务器,方便交互式测试。
  • 本地调用函数:运行 make invoke(其内部调用 cargo lambda invoke)可以模拟AWS环境调用函数,并传递键值对参数。
  • 构建与部署:运行 make buildmake deploy 可以构建项目并将其部署到AWS。为了节省成本,我们可以选择部署到ARM64架构。
  • 远程调用:部署后,运行 make aws-invoke 可以远程调用已部署在AWS上的Lambda函数。

整个反馈循环都由 cargo-lambda 高效管理。


核心代码解析

上一节我们介绍了工具链,本节中我们来看看具体的实现代码。主要逻辑位于 src/lib.rs 文件中。

数据处理逻辑

代码的核心是使用Polars库对内置的鸢尾花数据集进行一系列数据处理操作,包括过滤、分组和聚合。

// 示例性代码逻辑:过滤、分组、聚合
let df = df.filter(&filter_condition)?;
let grouped = df.groupby(["column_name"])?;
let result = grouped.agg(&[/* 聚合操作 */])?;

为了演示方便,数据集被直接硬编码在Rust文件中。对于大型项目,更常见的做法是将数据文件存储在Amazon S3等对象存储服务中。

Lambda函数处理器

Lambda函数的主入口是一个异步处理函数。它接收一个包含过滤条件的请求,将其应用于数据框计算,然后返回结果。

use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request {
    filter: String, // 接收过滤条件
}

#[derive(Serialize)]
struct Response {
    payload: String, // 响应负载,此处简化地将整个数据框转为字符串
}

async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    let filter_condition = event.payload.filter;
    // 调用计算函数,传入过滤条件
    let result_df = calculate(&filter_condition)?;
    // 将结果数据框转换为字符串(仅用于演示)
    let payload = result_df.to_string();
    Ok(Response { payload })
}

处理函数从事件中提取过滤条件,传递给计算逻辑,并将结果封装成响应。响应会被自动序列化为JSON格式。

主函数

主函数使用Tokio运行时启动Lambda运行时,并设置必要的日志追踪。

#[tokio::main]
async fn main() -> Result<(), Error> {
    // 初始化追踪
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .init();
    // 启动Lambda运行时
    lambda_runtime::run(service_fn(function_handler)).await?;
    Ok(())
}

测试与部署流程

本地测试

在部署之前,强烈建议在本地进行测试。我们可以分屏操作:一个终端运行 make watch 启动本地模拟环境;另一个终端运行 make invoke 来调用函数并查看输出结果。这能确保代码逻辑正确无误。

构建与部署

本地测试通过后,就可以进行部署了。整个过程非常简单:

  1. 运行 make build 编译项目。
  2. 运行 make deploy 将编译好的二进制文件部署到AWS Lambda。

cargo-lambda 工具会处理所有与AWS交互的复杂细节。

远程验证

部署完成后,运行 make aws-invoke 可以远程调用部署在AWS上的Lambda函数。你会看到函数在毫秒级别内返回结果,这充分展示了Rust和Polars在无服务器环境下的高性能优势。


总结

本节课中我们一起学习了如何构建和部署一个集成Polars库的Rust AWS Lambda函数。我们了解了项目的结构,解析了核心的Cargo配置和数据处理代码,并实践了使用 cargo-lambda 工具进行本地测试、构建和部署的完整流程。这种组合为需要快速、高效处理数据的无服务器应用提供了一个强大的解决方案。

085:在AWS控制台操作已部署的Polars Rust Lambda 🚀

在本节课中,我们将学习如何在AWS Lambda控制台中操作一个已部署的、使用Rust和Polars库编写的Lambda函数。我们将探索如何查看其指标、进行测试、分析性能,并了解Rust在无服务器计算环境中的独特优势。


AWS Lambda控制台概览

这是AWS Lambda控制台,它允许我查看可用的不同指标,并深入了解已部署的Lambda函数的运行情况。

在这个案例中,我使用Rust语言,并借助cargo-lambda工具来部署这个函数。

接下来,我将查看已修改的部分。看,这里有一个最近部署的、使用Polars库的Rust Lambda函数。如果我们点击查看,可以看到更多关于其运行状态的信息。


触发器与集成

这个函数会指向某个目的地吗?例如,它是否配置了触发器?很多时候,当你使用Rust构建高性能代码时,配置触发器是构建大规模数据工程管道的绝佳方式。这是因为Rust性能卓越,并且能与其他服务很好地协同工作。

以下是你可以为Rust Lambda函数配置的一些触发器类型:

  • 负载均衡器或CloudFront
  • 用于批量处理数据的服务
  • 从S3或SQS等服务中拉取数据

Rust Lambda函数拥有大量优秀的触发器选项。


测试Lambda函数

由于这个函数是作为二进制文件部署的,我们无法直接查看其代码。但我们可以对其进行测试。

那么,我们如何测试它呢?在这个案例中,我知道将要发送的载荷(payload)会包含一个关键词过滤器和一个数字5

如果我格式化这个载荷并点击“测试”,我们实际上可以看到执行结果。完美。


性能分析

现在,关于这个结果,一个有趣的点是:你可以看到这里初始化耗时34毫秒,而首次构建的持续时间为1387毫秒

如果我再次运行这个测试,回到测试界面并再次点击“测试”,下一次的执行速度会快得多。你可以看到这里的持续时间仅为6毫秒

因此,我们从这个Lambda函数中获得了惊人的响应时间和卓越的性能。

事实上,我们可以深入查看诸如持续时间构建持续时间内存大小最大内存使用量等细节。


Rust在AWS Lambda中的成本优势

这里需要指出关于Rust的一个非常重要且独特之处:AWS Lambda的计费是基于分配给函数实例的内存量。

如果你使用像Python或Ruby这样的脚本语言,可能需要分配更多内存,因此会被收取更多费用。但在Rust的情况下,请看这里,它的内存使用量远低于分配值,即使我正在用Rust执行一些相对繁重的操作,包括处理数据帧等。

所以,当你的Lambda部署后,这是一个需要关注的关键洞察点。


监控与日志

我们还可以进入“监控”选项卡,查看诸如日志等信息,了解最近发生了多少次调用、哪些调用最昂贵。

我们也可以查看指标,例如Lambda函数的执行时长(查看这个通常是个好主意)、被调用了多少次,以及是否存在任何错误或其他问题。

你可以在这里深入挖掘海量的细节信息。


手动调用与反馈循环

那么,我们如何实际调用这个函数并进行操作呢?既然它已经在运行,我只需要回到控制台,输入命令再次调用AWS Lambda。

这将再次调用它一次,我们甚至可以多次调用它来进行测试。

我们可以看到它在这里获得了非常好的性能。

然后,如果我们回到这里,实际上可以查看最近的调用记录,刷新后能看到许多不同的运行实例。

因此,在初始开发阶段,一个很好的实践是建立反馈循环,通过手动调用和查看日志流来深入了解细节。当然,你也可以在代码中添加更多日志,以获取更深入的运行洞察。


配置选项

现在,我想提一下另一个配置项。如果我们进入“配置”页面,查看“常规配置”,你还可以配置快照启动(这可以帮助你),以及配置临时存储

在我们的具体案例中,还有一点需要注意:你也可以选择ARM运行时。ARM的好处在于可以节省34%或更多的成本。所以,使用Rust本身已经节省了大量费用,而当你转向ARM架构时,还能节省更多。


总结

本节课中,我们一起学习了如何在AWS Lambda控制台中操作一个已部署的Polars Rust函数。我们涵盖了从查看指标、配置触发器、进行测试,到深入分析性能表现和成本优势的全过程。关键点在于,Rust凭借其卓越的性能和极低的内存占用,使其成为构建高性能数据工程、MLOps或云计算应用的理想选择,尤其是在AWS Lambda这种按资源消耗计费的无服务器环境中。通过利用ARM架构,还能进一步优化成本。

086:使用CodeWhisperer和AWS工具包分析Rust Lambda代码 🛠️

在本节课中,我们将学习如何利用AWS Toolkit和CodeWhisperer工具来分析和改进一个已能正常工作的Rust Polars Lambda函数。我们将重点关注如何通过安全扫描、代码建议和注释来提升代码质量。

初始Lambda函数与工具准备

这里我们有一个功能正常的Rust Polars Lambda函数。我们的目标是探索如何让它变得更好。

首先,我们可以通过Github Copilot扩展来操作。让我们暂时全局禁用它。可以看到,这里高亮显示它已被禁用。

现在,如果我们转到AWS Toolkit,可以看到这里有许多可以尝试的功能。其中之一就是Lambda服务。我们知道这个Lambda函数名为“Polars Lambda”。实际上,我们可以直接进入并调用它,这似乎是开始熟悉工具的好方法。

调用Lambda函数进行测试

如果我们在这里选择“在AWS上调用”,我只需要在括号内输入一些内容,例如输入“filter”。我们可以填入数字5,然后继续调用它。它能正常工作吗?完美。这样我就能够实际与它进行交互了。

引入CodeWhisperer进行代码分析

接下来我们还能做什么?另一件很酷的事情是,如果我们关闭当前窗口,可以转向CodeWhisperer。CodeWhisperer能够提供自动代码建议。

查看CodeWhisperer的文档,有几个要点需要指出:它可以扫描你的代码,以高亮并识别安全问题。随着支持的语言越来越多,会有一个矩阵显示何时能进行完整扫描,以及建议功能何时完全可用。我们可以在这里查看。

我们可以看到,随着这个工具的不断发展,目前对Java、Python、JavaScript、TypeScript和C#的支持质量最高。但它也支持为Rust等语言生成代码。每个月都会有更多的开发和训练进行,从而获得更广泛的支持。

CodeWhisperer的安全扫描与集成优势

对于Rust这类语言,CodeWhisperer也能进行安全扫描。我们可以看到,安全扫描可以在活动文件上执行。

这是一个非常棒的集成功能,当你构建AWS特定代码时,可以免费获得。因此,处理AWS Lambda和Rust的一个最佳实践就是使用像CodeWhisperer这样的工具来进行安全扫描、代码建议,甚至为你的代码添加注释,使其更加清晰明了。

总结

本节课中,我们一起学习了如何使用AWS Toolkit调用和测试Rust Lambda函数,并重点介绍了如何利用CodeWhisperer工具来提升代码质量。我们了解到CodeWhisperer不仅能提供代码补全建议,还能执行安全扫描,帮助开发者编写更清晰、更安全的AWS Lambda代码。随着工具的持续更新,对Rust等语言的支持也将越来越完善。

Rust编程4-5:04:AWS Lambda函数URL详解 🚀

在本节课中,我们将学习AWS Lambda函数URL这一强大功能,它允许我们轻松地将部署在Lambda上的高性能Rust应用(例如使用Polars数据框库)暴露为可通过HTTP直接访问的Web端点。

上一节我们介绍了如何在AWS Lambda中集成Rust库,本节中我们来看看如何通过函数URL功能,让外部服务或浏览器能够直接调用我们的Lambda函数。


函数URL为每个Lambda函数提供了一个唯一的、固定的HTTP(S)端点。其URL结构遵循特定的格式。

URL的基本模式如下:

https://<url-id>.lambda-url.<region>.on.aws/

其中,<url-id>是系统生成的唯一标识符,<region>是您的Lambda函数所部署的AWS区域。


关于身份验证,函数URL主要支持两种配置方式。

以下是两种主要的身份验证类型:

  • AWS_IAM:调用者需要使用AWS凭证对请求进行签名(例如使用SigV4签名)。这提供了基于IAM策略的精细访问控制。
  • NONE:无需任何身份验证。函数URL将公开可访问,适用于公开API或需要简化调用的场景。

配置好函数URL后,您可以通过多种方式轻松地测试和调用它。

您可以使用以下工具进行测试:

  • 网页浏览器:直接访问URL(对于GET请求或无身份验证的URL)。
  • cURL命令:在命令行中发送HTTP请求。
  • Postman:图形化界面工具,方便构建和发送复杂请求。
  • AWS专用cURL (aws curl):一个已集成SigV4签名能力的cURL变体,便于测试需要IAM认证的URL。

调用函数URL时,您可以向它发送包含数据的请求载荷。

请求支持标准的HTTP载荷格式。您可以将数据以JSON对象的形式放在请求体中发送,并可以设置不同的属性。Lambda函数会接收到这个完整的HTTP事件。


Lambda函数返回的响应遵循一个特定的结构,便于客户端处理。

响应格式包含以下几个关键部分:

  • 状态码:标准的HTTP状态码(如200、404、500)。
  • 响应头:包含Content-Type等元数据的HTTP头。
  • 响应体:函数返回的实际内容。
  • Cookies:服务器可以设置的Cookie信息。
  • Base64编码指示器:一个布尔标志,指示响应体是否经过了Base64编码。

Lambda运行时会自动将函数的不同输出类型(如纯文本字符串或JSON对象)映射到合适的HTTP响应。


在安全方面,根据您选择的身份验证类型,需要采取相应的措施。

如果您的函数URL配置为AWS_IAM认证,则必须使用SigV4协议对发出的HTTP请求进行签名。这确保了请求的完整性和来源可信。您可以使用前面提到的aws curl或配置了相应插件的Postman来简化签名过程。


此外,函数URL的功能还受到一些基础设施层面的支持。

关于区域和网络协议,需要注意:

  • 区域支持:函数URL在所有提供Lambda服务的AWS区域均可用。
  • IP协议:同时支持IPv4IPv6地址,您可以根据需求进行配置。

本节课中我们一起学习了AWS Lambda函数URL的核心概念与使用方法。总而言之,函数URL是一种极其便捷的方式,它能让我们将基于Rust高性能库(如Polars)构建的Lambda函数,快速暴露为可通过HTTP访问的API,极大地简化了无服务器应用的集成与调用流程。

088:构建并部署支持函数URL的Polars Rust AWS Lambda 🚀

在本节课中,我们将学习如何构建一个基于Polars和Rust的AWS Lambda函数,并为其启用函数URL,从而创建一个可以通过HTTP直接调用的数据查询服务。


项目结构概览

首先,我们来看一下项目的代码结构。通过运行 tree -I target 命令,可以清晰地看到项目的目录布局。

.
├── Cargo.toml
├── src
│   ├── lib.rs
│   └── main.rs
└── ...

核心文件是 src/lib.rssrc/main.rslib.rs 包含了所有与Polars数据处理相关的核心逻辑,而 main.rs 则负责处理HTTP路由和Lambda函数入口。


核心代码解析

上一节我们了解了项目结构,本节中我们来看看具体的代码实现。main.rs 文件使用了一个Web框架来处理路由。

// 示例路由结构
#[tokio::main]
async fn main() -> Result<(), Error> {
    // 初始化路由
    let app = Router::new()
        .route("/", get(hello_world))
        .route("/filter/{value}", get(filter_data));
    // ... Lambda运行器配置
}

以下是代码中的两个主要路由处理函数:

  1. 根路径路由 (/): 返回一个简单的“Hello Polars”消息。
  2. 数据过滤路由 (/filter/{value}): 这是核心功能。它接收一个路径参数 {value},调用 lib.rs 中的函数对内置的Iris数据集进行过滤和计算,最后将结果转换为JSON格式返回。

lib.rs 中的函数封装了Polars数据框的操作,例如 df.filter(...)df.select(...)


本地测试与运行

理解了代码结构后,我们需要在部署前进行本地测试。以下是本地测试的步骤。

首先,使用 make watch 命令(其内部调用 cargo lambda watch)在本地启动Lambda模拟环境。

make watch

服务启动后,我们可以使用 curl 命令来测试两个端点。

  1. 测试根路径:

    curl http://localhost:9000
    

    预期返回:"Hello Polars"

  2. 测试数据过滤功能,例如查询 sepal_length 大于5的数据:

    curl http://localhost:9000/filter/5
    

    预期返回一个经过过滤和计算的JSON格式数据数组。


构建与部署到AWS Lambda

本地测试通过后,就可以将应用部署到生产环境了。我们将构建一个高效的ARM64版本以节省成本。

  1. 运行构建命令:

    make build
    
  2. 运行部署命令:

    make deploy
    

    此命令会将编译好的二进制文件打包并部署到您AWS账户中配置的Lambda函数。


配置函数URL并远程调用

部署成功后,我们需要在AWS控制台中为Lambda函数启用函数URL功能,这样才能通过HTTP直接访问。

  1. 登录AWS控制台,导航到Lambda服务。
  2. 找到刚刚部署的函数,进入其“配置”选项卡。
  3. 选择“函数URL”,点击“创建函数URL”。
  4. 在认证类型中选择“NONE”(仅用于演示,生产环境请使用更安全的认证方式)。
  5. 创建完成后,复制生成的URL。

现在,我们可以像测试本地服务一样,使用 curl 和这个URL来远程调用我们的Lambda函数了。

curl https://{your-function-url}.lambda-url.{region}.on.aws/filter/5

如果一切顺利,您将收到与本地测试相同的JSON响应。这标志着一个完整的、基于Polars和Rust的、并通过AWS Lambda函数URL提供服务的Web API已成功部署。


总结

本节课中我们一起学习了如何将一个集成了Polars库的Rust应用部署为AWS Lambda函数。我们首先分析了项目代码结构,然后进行了本地测试,接着将其构建并部署到云端,最后通过启用和调用函数URL,实现了无需API网关即可直接通过HTTP访问的数据查询服务。这套流程展示了利用Rust和Serverless架构构建高性能数据后端的高效路径。

089:讲师与课程介绍 🎓

在本节课中,我们将了解本课程的讲师以及课程将要涵盖的核心内容。课程将聚焦于大语言模型领域的前沿技术,特别是LLM Ops(大语言模型运维)与生成式AI的实践应用。

大家好,我是Noah Gift,是本课程的讲师。

我是杜克大学的驻校高管。这门课程令人兴奋,因为它涵盖了大语言模型领域正在发生的一些最前沿的技术。

首先,我们将深入探讨一些DevOps的概念,这些概念是理解LLM Ops的基础。上一节我们介绍了讲师,本节中我们来看看课程的具体安排。

其次,我们将学习Rust、Hugging Face Candle等技术。这是一个极佳的方式,能够将你在模型方面的工作集成起来,以便将其部署到云端并运行推理。

我们还将深入探讨关键的LLM Ops技术,包括Rust、PyTorch以及ONNX。以下是本课程将涉及的核心技术栈:

  • Rust:一种高性能的系统编程语言。
  • PyTorch:一个广泛使用的深度学习框架。
  • ONNX:一种开放的模型表示格式,用于促进框架间的互操作性。

这些是构建大语言模型应用的新兴标准。最后,在课程尾声,我们将进入生成式AI领域。

这包括使用诸如AWS Code Whisperer、Google Bard等工具。我们将真正深入探讨你可以用生成式AI做哪些不同的事情,以及它们如何最终反馈并影响LLM Ops的实践。

好的,我们有很多内容要学习。期待在课程结束时与大家相见。

本节课中我们一起学习了课程讲师Noah Gift的背景,以及本课程的核心路线图。课程将从DevOps基础过渡到LLM Ops,并涵盖Rust、Candle、PyTorch等关键技术,最终探索生成式AI工具及其与LLM Ops的闭环。我们即将开启一段深入前沿技术领域的旅程。

090:编程的本质

概述

在本节课中,我们将探讨编程的核心本质,并帮助你区分哪些是必须掌握的编程核心,哪些是可以通过工具自动化或暂时忽略的“无用痛苦”,以及哪些是可选技能。理解这一点,可以帮助初学者避免常见误区,更快地提升编程效率。

编程的核心:输入、处理与输出

当你刚开始学习编程时,一个非常令人困惑的问题是:哪些是必须经历的“痛苦”,哪些是无用的“痛苦”?掌握这个区分的过程可能需要数年时间。

我将以一种我认为能帮助人们缩短这个过程的方式来进行分解,或许能节省五年时间。首先,让我们看看什么是真正的编程,以及什么是必不可少的。

如果你思考一个函数,它有一个输入 x 和一个 y,它执行一个工作单元,并返回 xy 的总和。

fn add(x: i32, y: i32) -> i32 {
    x + y // 工作单元:求和
}

这就是所有编程语言的本质。它可以是 Rust、Python、JavaScript 或 Ruby。但编程的本质是:你有一个工作单元,它接收输入,执行工作,然后返回输出。

除此之外的任何事情,要么是无用的痛苦(你应该将其自动化),要么是可选的。它可能有用,也可能没用。

无用的痛苦:应该自动化的事情

以下是初学者常陷入的“无用痛苦”,你应该利用工具将它们自动化,而不是手动处理。

1. 调试运行时错误

这确实不应该是你手动完成的事情。刚开始编程时,你可能会感到不知所措,因为你觉得自己犯了所有这些错误。在编程中,一切总是出问题,这是事实。但程序员的角色是通过自动化来修复这些问题。

你应该使用持续集成、Makefile、优秀的代码检查工具,并可能使用 Copilot 或其他编码助手来帮助你处理运行时错误。

2. 包管理系统

这是另一个让初学者非常困惑的领域。他们认为使用 Conda、PIP 或其他工具就是编程。事实上,这与编程无关,甚至可能被称为糟糕的环境管理。许多包管理解决方案设计得并不好,而且彼此竞争,这与编程本身无关。

如果你感觉自己没有进步,可能只是因为你把时间浪费在了一些对经验丰富的开发者来说也没有意义的事情上。这同样应该被自动化,作为新手,你不应该为此花费大量时间担心。

3. 语法错误

如果你使用能自动检查这些问题的代码格式化和检查工具,你真的不需要担心这个。编辑器可以帮助你处理语法问题。这不是你应该花费大量时间的事情。

4. 样板代码

假设你正在定义一个类,或者在使用某种框架,其中的样板代码如果你不理解,就毫无意义。这只是无用的痛苦,可以通过生成式 AI 或优秀的编辑器等工具来解决。

5. 虚拟环境

我认为这在 Python 这类语言中是一种“生产力税”。因为它是一种脚本语言,你必须不断记住自己是否在正确的虚拟环境中。这与编程绝对无关,是你希望可以自动化的痛苦。

6. 逻辑错误

一旦你开始构建一些代码,为什么不设置一些自动化,以避免犯逻辑错误呢?这就是单元测试。如果单元测试在你提交代码时自动运行,你就不必担心这个问题。

7. 开发环境设置

现在,借助 GitHub Codespaces 等工具,这个问题已经基本解决了。你不应该花两周时间来设置环境。在我职业生涯早期,这很常见,一个新来的硕士或博士毕业生会花两周时间设置环境。他们显然非常有才华,但开发环境的设置过程非常可怕。

如今,有了 GitHub Codespaces、Docker 或基于云的环境,这应该是即时的。你不应该在这方面花费大量时间。

可选技能:可能有用也可能没用

上一节我们介绍了应该自动化的“无用痛苦”,本节中我们来看看那些可能有用也可能没用的可选技能。这也是编程新手容易混淆的地方。

1. 面向对象编程

一种可能有点精英主义的程序员会对别人说:如果你不做面向对象编程,你就不是在编程。这是错误的。

原因在于,这就是编程,但它不是面向对象编程。你可以进行不需要对象的编程。面向对象编程可能是一个有用的抽象,但它与学习成为一名程序员或进行编程无关。你只需要输入、工作单元和输出就能解决编程问题。

2. 并发

刚开始编程时,很容易陷入一种思维定式,认为你应该总是创建线程或进程,或者进行异步网络 I/O。但事实上,对于你试图解决的问题来说,这可能是一个真正的负担。所以,它可能有用,也可能没用。

3. 复杂算法

很容易对你所能想到的最复杂的算法感到兴奋。但最初当你刚起步时,使用最好的算法可能有用,也可能没用。

4. 测试驱动开发

很多时候,你会听到那些在测试方面可以说是“狂热分子”的人告诉你,你必须在写代码之前先写测试。这根本不是真的。测试驱动开发对你正在进行的项目来说,可能有用,也可能没用。

5. 框架

很多时候,人们将编程与框架混淆。框架是由第三方开发者编写的。一个 Web 框架或命令行工具框架可能有用,也可能没用。事实上,它可能非常复杂,以至于你无法理解。这并不意味着反映了你的能力,可能只是这个框架本身设计得不好。

6. 项目管理

许多非技术人员尤其倾向于 Scrum 和敏捷,因为容易获得认证。然后他们将这些“议程”强加给开发人员。开发人员可能会感到困惑,因为他们没有取得进展。但现实是,方法论本身可能就是有缺陷的。

总结

本节课中,我们一起学习了编程的核心本质。重要的是识别出核心(输入、工作单元、输出),以便你能专注于重要的事情。

如果你理解这一点,并在此基础上逐步积累成果,将可以自动化的东西自动化,并避免在真正需要之前去做那些可选的事情,你很可能能在职业生涯的生产力上走一条五年的捷径。

关键在于:专注于核心,自动化琐事,明智地选择工具和方法

091:微服务运维化 🚀

在本节课中,我们将学习如何将一个微服务进行“运维化”。这意味着我们将探讨如何将一段代码逻辑,通过一系列自动化流程,转变为可部署、可监控、可扩展的线上服务。核心在于理解从代码提交到生产环境部署的完整生命周期。


上一节我们介绍了微服务的基本概念,本节中我们来看看如何将其付诸实践。下图展示了一个微服务运维化的典型流程。

图中描绘了将一个微服务进行运维化的过程。请注意,在Git版本控制系统中,微服务的某些优势开始显现。

我喜欢将微服务视为一段可以上线运行的逻辑概念。它具备以下一些特征:代码规模小,并且可复用。

因此,你可以将这段逻辑转化为多种形式。以下是几种可能的转化方向:

  • 可以将其制作成一个命令行工具
  • 可以将其封装成一个代码库
  • 当然,可以将其构建成一个微服务
  • 也可以将其转换为一个Docker容器

这些特性共同构成了微服务的本质。它就像是一个被“运维化”并赋予了新形态的函数。


上一部分我们了解了微服务的多种形态,接下来我们看看它如何进入自动化流程。当代码进入持续交付系统或构建服务器时,会经历一系列标准步骤。

这个过程通常包括代码检查、测试、编译,可能还会将构建产物推送到容器仓库,最后通过基础设施即代码的方式进行部署。

将基础设施即代码与容器技术结合使用,会带来一个关键优势:你可以轻松创建任意数量的环境

例如,你可以创建:

  • 一个预发布环境
  • 一个生产环境
  • 一个负载测试环境
  • 一个开发环境

并且,你可以根据服务被部署到的不同环境,执行相应的自动化操作。


在了解了多环境部署的优势后,我们以预发布环境为例,看看具体的验证流程。在预发布环境中(这也是我在微服务实践中常做的),你的微服务会暴露健康指标。

这些指标包括CPU使用率、内存占用、请求延迟等。它们会被收集到某种监控系统中,例如Prometheus或AWS的CloudWatch

随后,系统会进行性能验证。这一步的核心目的是确认实际情况是否符合你的预期。

一旦验证通过并获得批准,你就可以轻松地将代码合并到一个新的分支,并直接推送到生产环境。


本节课中我们一起学习了微服务运维化的完整流程。总而言之,一个微服务最简洁的解释就是:一段被你推送上线的逻辑

通过版本控制、持续集成/持续部署、容器化、基础设施即代码以及监控告警这一系列环节,这段逻辑得以可靠、高效地运行在复杂的生产环境中。

092:微服务持续集成 🚀

在本节课中,我们将学习如何为微服务项目建立一个持续集成生态系统。我们将从最重要的第一步——项目结构开始,并了解如何通过自动化流程来测试、检查代码规范性和格式化代码,以确保每次代码提交的质量。

概述

持续集成的核心在于,每当开发者(也就是你)将更改推送到基于Git的代码仓库时,系统能够自动测试、检查代码规范并格式化代码。我们将使用一个包含Makefile的项目结构,该文件定义了所有本地可执行的操作。同时,我们还将设置一个构建服务器(机器人),它会在云端持续工作,确保代码质量,为后续的部署做好准备。建立本地的持续集成环境是一个关键概念,它能让你的项目在准备投入生产时处于就绪状态。

接下来,让我们看看如何在Github项目的实践中实现这一点。

项目结构解析

现在,让我们来看一个专为持续集成和后续持续交付设置的项目结构。以下是其关键组成部分:

首先,在这个名为noahgift-continuous-integration的代码仓库中,我们有一个Github workflows目录。这里是构建系统的配置所在。在本例中,我们使用Github Actions,但它也可以是Jenkins、AWS Code Build等其他构建系统。该系统会按照我们的指令测试代码。

我们可以查看main.yml文件来了解具体的命令。该文件规定,在每次推送或开发者做出更改时,系统会启动一个Ubuntu虚拟机,使用Python 3.8,并执行以下步骤:make installmake lintmake test以及后续的make format。这种结构非常出色,我建议所有持续集成项目都采用类似设置。

接下来,我们看看代码本身的环境,也就是项目的各个子组件。这里有一个README.md文件(这总是一个好主意),一个显示构建系统状态的徽章,一个执行两数相加的基础功能文件,一个用于测试代码的测试文件,以及一个包含版本化包依赖的requirements.txt文件。这个依赖文件非常重要,它能确保环境的可复现性,让我知道构建系统使用的Python包版本与我测试时完全一致。

最后,如果我们查看Makefile文件本身,可以看到它实际上是一系列“配方”的集合,定义了make installmake testmake formatmake lint等命令。这就是整个项目的结构。

实践演练:在真实环境中使用

那么,在现实世界中该如何使用这个结构呢?我们可以使用基于云的开发环境,例如Github Codespaces,来启动并查看它。

现在我们进入了这个Github Codespaces环境。请注意,我有一个终端,左侧显示了所有代码文件,并且我已经创建了一个虚拟环境。你可以使用virtualenv命令创建自己的虚拟环境。在我的情况下,我编辑了.bashrc文件,并直接将激活环境的source命令添加了进去(关于如何编辑.bashrc文件,我们在Bash部分的另一节课中有详细讲解)。

现在,我将再次浏览项目结构,并运行make all命令。让我们看看它会做什么:它会安装依赖、检查代码规范、测试我们的代码。这一切的妙处在于,我能得到一个清晰的反馈循环,告诉我一切正常。

现在,如果代码中存在问题会怎样?这里有一个很好的小例子:如果我让一个变量赋值给它自己,这不一定会导致代码崩溃,但可能会在未来引发潜在问题。在这种情况下,我们可以看到两个不同的问题:我们让变量赋值给它自己,而且我实际上从未让这个变量做任何事情,从未给它赋值。现在,如果我修改它,仍然能看到这个变量存在警告问题。第一个问题是异常或错误,会导致代码无法运行;另一个则只是警告。无论如何,代码规范检查工具都能捕获到这个Bug。

接下来,我将推送这个更改。现在执行git push来推送这些更改。完成后,我可以回到仓库页面,观察构建系统的运行。这就是持续集成系统的优势:它是一个监视我们代码的机器人,可以把它想象成一个不断清理问题、查找安装问题、代码规范问题和测试问题的守护者。由于我们有一条错误信息,代码规范检查在这里会失败,表明代码确实存在规范性问题。

问题修复与自动化流程

我们该怎么办?幸运的是,修复过程非常简单。我甚至不需要进入我的开发环境,可以直接在这个文件中进行编辑。我可以看到这里有问题,实际上并不需要这两行代码。让我们修复它,然后让我们的机器人(构建服务器)检查一下,看它是否能通过。

现在,我们等待构建服务器运行这个任务。它开始设置代码、安装依赖。最终,我们看到代码规范检查通过了,代码格式化也通过了,所有步骤都顺利完成了。

总结

本节课中,我们一起学习了为微服务建立持续集成生态系统的核心步骤。我们首先了解了项目结构的重要性,它包含一个定义所有自动化操作的Makefile。接着,我们探讨了如何利用Github Actions等构建服务器,在代码提交后自动执行测试、规范检查和格式化。通过实践演示,我们看到了如何在Github Codespaces环境中运行这些检查,以及当代码出现问题时,系统如何快速给出反馈并指导我们轻松修复。这个过程是每个进行微服务开发、命令行工具开发,乃至任何Python软件开发的项目都应该具备的。它极大地提升了代码质量和开发效率。

093:Makefile是什么及如何使用 🛠️

在本节课中,我们将学习什么是Makefile,它的用途是什么,以及如何创建和运行Makefile中的命令。Makefile是一个用于自动化构建步骤的工具,在Unix类系统(如macOS和Linux)中广泛使用。

什么是Makefile?

Makefile是一个包含一系列规则的文件,用于定义如何编译和构建项目。它通常用于自动化软件构建过程,但也可以用于运行任何你希望自动化的命令序列。

如何创建和运行Makefile

上一节我们介绍了Makefile的基本概念,本节中我们来看看如何实际创建一个Makefile并运行其中的命令。

首先,你可以在终端中键入 which make 命令来检查你的系统是否安装了 make 工具。在大多数Unix类机器上,make 命令都是预装的,或者很容易安装。

以下是检查 make 是否安装的步骤:

  1. 打开终端。
  2. 输入命令 which make
  3. 如果已安装,终端会显示 make 命令的安装路径(例如 /usr/bin/make)。

接下来,我们将使用 touch 命令创建一个名为 Makefile 的空文件。请注意,文件名必须精确地是 Makefile(首字母大写M很重要)。

创建文件后,你可以用任何文本编辑器打开它,并开始定义命令。例如,你可以定义一个名为 hello 的规则。

以下是创建一个简单 Makefile 的步骤:

  1. 在项目目录中,运行 touch Makefile
  2. 使用编辑器打开 Makefile
  3. 输入你的第一条规则,例如:
    hello:
        echo "This is my first make command."
    
  4. 保存文件。
  5. 在终端中运行 make hello 来执行该命令。

Makefile 的优点在于你可以将任何命令封装在其中,从而隐藏一些复杂性。例如,你可以定义一个 install 规则来运行安装步骤。

以下是定义 install 规则的示例:

install:
    # 这里以后可以替换为实际的安装命令,例如 pip install
    echo "This will later be a pip install command."

保存后,运行 make install 即可执行该规则下的命令。

版本控制与后续开发

创建并测试了基本的Makefile后,通常的下一步是将其添加到版本控制系统中。

以下是管理Makefile的常见步骤:

  1. 使用 git add Makefile 将文件添加到暂存区。
  2. 使用 git commit -m "adding a Makefile" 提交更改。
  3. 使用 git push 将更改推送到远程仓库。

完成这些步骤后,你就设置好了一个Makefile,并可以在后续的开发步骤中在此基础上进行构建,添加更复杂的构建、测试或部署规则。

总结

本节课中我们一起学习了Makefile的基础知识。我们了解了Makefile是一个用于自动化项目构建和任务执行的工具,学会了如何检查 make 命令、创建 Makefile 文件、在其中定义简单的规则(如 helloinstall),以及如何运行这些规则。最后,我们还介绍了如何将Makefile纳入版本控制,为项目的自动化流程奠定基础。

094:什么是DevOps 🚀

在本节课中,我们将学习DevOps的核心概念。我们将探讨DevOps的三个主要组成部分,并理解它们如何共同构成一个高效的软件开发和运维反馈循环。


当有人提到DevOps时,其核心组成部分是什么?为了进行总结,我们可以将其归纳为几个关键方面。

首先,我们有软件工程最佳实践。这可以被视为一个基础模块。

其次,存在一种文化层面的要求。这意味着公司的文化需要拥抱持续改进的理念。

最后,是最终的结果。这主要体现在自动化上。

如果我们深入探讨,DevOps的本质可以看作是软件工程最佳实践、组织文化和自动化的结合体。它形成了一个反馈循环。缺少其中任何一个环节,都无法实现真正的DevOps。这个反馈循环是理解DevOps的关键。

那么,什么是软件工程最佳实践?我们稍后会详细讨论。粗略地说,它指的是在构建项目时遵循的一系列标准化操作清单。

关于自动化,我们刚刚已经提及。这主要指的是持续集成/持续部署的概念,即能够持续地交付代码。与此紧密相关的还有基础设施即代码的概念,它也扮演着重要角色。

在文化方面,核心是“改善”这一理念。这是一个源自日本汽车工业的术语,意为持续改进。


核心组成部分详解

以下是构成DevOps的三个核心支柱:

  1. 软件工程最佳实践

    • 在项目开发过程中,需要遵循一套明确的、可重复的检查清单或流程。
  2. 自动化

    • 核心是 CI/CD,实现代码的持续集成与部署。
    • 延伸概念包括 基础设施即代码
  3. 组织文化

    • 核心是 改善,即一种致力于持续改进的文化。

总结

本节课中,我们一起学习了DevOps的基本构成。我们了解到,DevOps并非单一的工具或技术,而是软件工程最佳实践自动化(尤其是CI/CD)与倡导持续改进组织文化三者的有机结合。这三者共同构成了一个强大的反馈循环,是成功实施DevOps的关键。

095:改善方法论 🛠️

在本节课中,我们将学习两种核心的改善方法论:PDCA循环五问法。这些方法不仅适用于制造业,也广泛应用于软件工程和问题解决中,旨在通过系统性的步骤持续改进流程和根除问题。

PDCA循环:持续改进的科学方法 🔄

上一节我们介绍了改善方法论的重要性,本节中我们来看看第一个核心方法——PDCA循环。PDCA是“计划、执行、检查、处理”的缩写,它是科学方法在制造或软件工程领域的具体应用。

以下是PDCA循环的四个阶段:

  1. 计划:在此阶段,你需要识别问题。例如,我们需要构建一个查看库存的自动化工具。
  2. 执行:接下来,尝试实施解决方案。一旦完成初始原型,你需要进行检查。
  3. 检查:检查以确保方案确实实现了业务目标。例如,如果围绕库存进行了自动化,它应该能提供准确的库存计数。你需要分析结果。
  4. 处理:如果方案有效,就将其作为生产设施的一个组成部分付诸行动。如果无效,则需要回到计划阶段,识别问题所在,然后重复此过程。

因此,PDCA循环是一个不断改进事物的迭代过程。这是改善方法论的一个关键组成部分。

五问法:探寻问题的根本原因 ❓

了解了PDCA循环后,我们来看看另一个强大的工具——五问法。它通过连续追问“为什么”来深入挖掘问题的根本原因。

以下是一个来自我职业生涯的真实且非常严重的案例:

我们遇到服务器随机崩溃的问题。通过连续追问,我们最终找到了根本原因。

  1. 为什么服务器会崩溃? 答案是不确定,因为没有日志文件,我们无法调查崩溃原因。
  2. 为什么没有日志文件? 提出这个问题后,我们发现系统使用的是临时存储,这种存储会在重启后消失,并且没有交换分区。
  3. 为什么系统使用没有交换分区的临时存储? 答案是,曾在一家顶级科技公司工作的DevOps专家说他们那里不使用交换分区,并且临时存储更便宜。
  4. 如果我们启用交换分区并使用EBS存储会怎样? EBS存储是跨重启持久化的块存储。通过这一步,我们能够在崩溃日志中显示存在内核恐慌和内存不足错误,从而调试问题。
  5. 是什么导致了那个错误? 现在我们看到根本原因是:Java应用服务器配置文件分配的内存超过了机器可用内存。这导致应用软件本身消耗了所有内存,并且因为没有交换分区可写入,Linux内核不得不介入并杀死了Java进程。最后,一个用于管理并自动重启进程的监管程序制造了一个竞态条件,导致机器崩溃。

通过这五个问题,我们解决了一个非常棘手的问题,并找到了根本原因。并非所有问题都能通过五问法解决,但许多问题可以。这体现了改善方法论的核心原则:追根溯源,持续找出原因并修复它。

总结 📝

本节课中我们一起学习了两种关键的改善方法论。PDCA循环提供了一个迭代框架,用于计划、测试、检查和实施改进。五问法则是一种强大的诊断工具,通过层层深入追问“为什么”,帮助我们穿透表面现象,找到问题的根本原因。掌握这些方法,将使你在面对复杂工程和业务挑战时,能够更系统、更有效地推动持续改进。

096:用于持续交付的基础设施即代码 🚀

在本节课中,我们将学习如何使用最新的云原生生态系统工具来构建一个持续交付工作流。我们将重点探讨“基础设施即代码”的概念及其在自动化部署流程中的核心作用。


概述

持续交付的目标是能够快速、可靠地将软件变更交付到生产环境。为了实现这一点,我们不仅需要自动化软件的构建和测试,还需要自动化其运行环境的创建和管理。这就是“基础设施即代码”发挥作用的地方。

开发环境

首先,开发工作通常在特定的环境中进行。很多时候,这会是一个基于云的环境,例如 AWS 上的 Cloud9 或 Azure 平台上的 GitHub Codespaces。

使用这些云原生环境的原因是,它们让你能够使用所有将在生产环境中使用的开发者工具。因此,你拥有一个与生产环境非常相似的一对一开发环境。

推送变更

当你构建项目时,你不仅会推送软件代码,还会推送“基础设施即代码”。

基础设施即代码允许你定义在基于云的环境或基于 Kubernetes 的环境中发生的每一个细节。请注意,当你推送变更时,构建系统将测试你的代码、合并代码,然后这个构建版本会自动进入“资源调配”阶段。

基础设施即代码的核心特性

基础设施即代码的一个强大特性是它的幂等性

这意味着,它只在必要时才进行更改,并且总是使系统状态保持一致。

例如,如果你最初创建了基础设施,当你进行第二次构建时,它不会再次创建基础设施,因为它会注意到(比方说)这里的 S3 存储桶已经存在,因此无需进行更改。

同样,如果你修改了基础设施即代码的定义,做了一个小的更改,它就会执行并为你创建一个新的存储桶。

最后,如果你需要清理资源,使用基础设施即代码可以轻松完成。

灵活的环境管理

另一个强大的功能是,你可以执行任何你希望的操作。例如,你可以选择不执行删除操作,而是说:“我想在这里创建新的基础设施,我想在这个特定环境中构建一个预发布环境。”

这个预发布环境是进行负载测试的地方。然后,你可以同样地分叉代码和基础设施即代码,再创建一个生产环境

总结

本节课中,我们一起学习了持续交付工作流的核心。我们了解到,使用基础设施本身来定义系统,并进行幂等性的更改,是持续交付的重要组成部分。这种方法允许你持续地将变更推送到新环境。这并不意味着在没有人工干预的情况下,变更总会直接进入生产环境,但它确实允许你将变更推送到多个环境。这正是持续交付中的关键概念。

097:应对受损资源与工作负载 🛡️

在本节课中,我们将学习如何在AWS环境中识别、隔离和分析被恶意篡改的资源与工作负载,并探讨自动化响应、取证保护以及事后的准备与恢复流程。掌握这些技能对于维护云环境的安全、合规性和业务连续性至关重要。

什么是受损资源?🔍

上一节我们介绍了课程概述,本节中我们来看看核心概念。受损资源是指任何被恶意篡改的网络或设备。例如,一个被入侵的EC2实例,或一个遭到未授权访问的S3存储桶。其影响范围广泛,可能从数据丢失延伸到法律责任的承担。因此,迅速响应至关重要。

AWS安全事件响应指南 📘

了解基本概念后,我们来看看AWS提供的官方指导。AWS提供了一份专门针对其环境的安全事件响应指南。这份指南是任何AWS安全专业人员的必备资源。

以下是该指南涵盖的关键步骤:

  • 识别事件:确认安全事件的发生。
  • 隔离受影响系统:防止损害扩大。
  • 与法律实体协调:确保符合法律和合规要求。

隔离受损资源 🚧

上一节我们介绍了响应指南,本节中我们来看看具体操作。隔离受损资源意味着阻止其造成进一步危害,就像隔离病毒一样。在AWS中,这可能意味着断开EC2实例的网络连接,或限制S3存储桶的访问权限。这种快速隔离能最大限度地降低风险,是大多数响应流程的第一步。

根本原因分析技术 🔬

成功隔离资源后,下一步是找出问题根源。根本原因分析旨在理解资源为何会受损,这是防止未来事件发生的关键。

以下是常用的分析技术:

  • 查看CloudTrail日志:审计API调用活动。
  • 分析GuardDuty发现结果:利用威胁检测服务的洞察。
  • 使用其他AWS工具:如VPC流日志、Config规则等。

核心目标是理解出错原因,并制定未来的改进措施。

数据与日志分析 📊

除了分析原因,收集和分析现场数据同样重要。其核心思想是捕获关键数据,例如创建EBS卷快照或进行内存转储。这有助于理解事件发生时系统的实际状态。

以下是相关的日志分析概念:

  • 查询S3中的日志:从存储的日志中提取信息。
  • 提供上下文信息:将不同来源的数据关联起来。

这些技术支持对事件进行详细理解和生成准确的报告。

自动化修复 🤖

在分析了原因和数据后,我们可以利用自动化来加速响应。自动化修复意味着利用AWS服务实现自动响应。例如,可以构建无服务器自动化响应流程,用于发送电子邮件或通知。这能加快恢复速度,减少人为错误,并且可以根据特定事件进行定制。自动化是现代事件响应与编排中的一个强大工具。

取证数据保护 ⚖️

为了满足法律和合规性要求,保护取证数据至关重要。取证数据保护对于调查的完整性和任何潜在的法律程序都至关重要。

以下是可用于保护证据的工具示例:

  • S3对象锁定:使用S3 Object Lock功能防止证据被篡改或删除。
  • 隔离的取证账户:在独立的AWS账户中保存和分析证据。

准备与恢复 📈

最后,我们必须认识到,安全事件管理是一个持续的过程。这意味着在事件发生前就做好准备,制定计划、准备工具并进行培训。恢复工作则包括恢复服务、从事件中学习经验教训,并为未来进行改进。这些是有效安全管理组织中持续进行的流程,意味着你需要不断更新这些程序。

总结 📝

本节课中,我们一起学习了应对AWS中受损资源与工作负载的全过程。要记住,响应工作需要综合运用技术技能、周密计划和对AWS的理解。这包括隔离恢复以及事后维护安全与合规的步骤。同样重要的是,持续学习和实践对于维护一个安全的组织环境至关重要。

098:设计与实施监控告警 📊

在本节课中,我们将要学习监控与日志记录在软件系统中的核心作用,它们本质上是一种针对软件系统的数据科学实践。我们将探讨如何在部署服务器时,通过源代码中的日志记录和插桩代码来收集关键数据,并利用这些数据进行集中式分析和告警。

监控与日志:软件系统的数据科学 🔍

监控与日志记录是软件系统的数据科学。这一实践在“数据科学”这个术语成为流行词汇之前,就已经广泛存在了。

上一节我们提到了监控的重要性,本节中我们来看看其具体实现方式。

服务器部署中的日志与插桩 📝

当你部署一个服务器时,其源代码内部本身就包含了日志记录代码。它同时也包含了插桩代码。

以下是其重要性的体现:

  • 上下文信息:日志代码必须包含其所在服务器的上下文信息。例如,如果服务器位于特定区域(如美国东海岸或西海岸),日志中应体现这一点。
  • 唯一标识:服务器还应具有某种唯一标识,以便能够被集中追踪。
  • 关键指标:像 CPU内存磁盘 I/O 这类指标,在发现分布式系统中发生的问题时至关重要。

集中式监控的必要性 🧩

很多时候,如果系统中有多台服务器(例如10台),当某台服务器出现非常细微或非确定性的问题时,几乎不可能被察觉。例如,服务器可能周期性崩溃,或者存在某种网络问题。

解决这类问题的唯一方法就是运用数据科学。具体做法是:将日志和插桩数据收集起来,输入到一个集中式的仪表板中。

以下是集中式监控带来的好处:

  • 搜索与分析:你可以在其中搜索日志,进行深入分析。
  • 设置告警:你可以基于特定条件设置告警规则。
  • 自动化洞察:甚至可以通过机器学习获得自动化的洞察。

现代软件工程的核心 🧠

现代软件工程的核心与灵魂在于,要非常谨慎地认识到:你的系统本质上是一个数据科学可以大展身手的领域。你必须确保拥有足够的上下文信息,以便在生产环境中调试你的系统。

本节课中我们一起学习了监控与日志记录作为软件系统数据科学的基础理念。我们了解了在服务器代码中嵌入上下文丰富的日志和指标的重要性,以及如何通过集中式仪表板进行搜索、告警和自动化分析,从而有效诊断和解决分布式系统中的复杂问题。这是构建可靠、可观测的现代软件系统的关键。

099:审计网络安全 🔍

在本节课中,我们将学习如何审计组织内的网络流量,以识别恶意攻击或配置不当的内部行为。我们将重点介绍使用VPC流日志这一工具来实现网络流量的记录、查看与分析。


概述

审计网络流量对于维护组织安全至关重要。它帮助我们识别潜在的恶意攻击,并检查内部网络行为是否存在错误配置。一种有效的方法是使用VPC流日志。通过记录和查看网络流量,我们可以收集、存储和分析网络流日志,进而排查包括安全问题在内的各种故障,确保网络按预期运行。


使用VPC流日志审计网络

上一节我们介绍了审计网络流量的重要性。本节中,我们来看看如何使用VPC流日志这一具体工具来实现审计。

在AWS环境中,过去可能需要借助代理等工具来完成网络流量审计。现在,我们可以直接使用VPC流日志功能。

以下是创建和查看VPC流日志的基本步骤:

  1. 首先,进入AWS管理控制台的VPC服务。
  2. 选择需要监控的VPC,然后创建VPC流日志。
  3. 创建后,系统将开始记录并展示该VPC内的所有网络连接。

VPC流日志界面会实时显示网络连接流,我们可以清晰地看到哪些连接被接受,哪些被拒绝。这为后续分析提供了直观的数据基础。


集成告警系统

在成功收集到网络流日志数据后,下一步是建立监控和告警机制,以便在出现异常时及时响应。

VPC流日志提供了一个良好的数据接口,我们可以利用它来创建告警。例如,可以将流日志数据发送到Amazon CloudWatch,利用其监控和告警功能。此外,也可以集成到其他第三方安全信息和事件管理(SIEM)系统中。

通过设置合理的告警规则(如检测到大量来自异常IP的拒绝连接),我们可以在潜在安全事件发生时立即获得通知。


总结

本节课中,我们一起学习了网络流量审计的重要性以及如何使用VPC流日志这一工具。我们了解到,通过启用VPC流日志,可以实时监控网络连接状态(接受或拒绝),并利用其数据接口与CloudWatch等系统集成,构建自动化的安全告警机制,从而有效提升网络的安全性和可观测性。

100:Rust安全设计原则 🛡️

在本节课中,我们将学习Rust语言的核心安全设计原则。我们将通过一个名为“Rustland”的神奇王国童话故事来理解这些抽象概念,使其变得简单易懂。

Rust是一种现代编译型语言,其设计核心就是安全。它通过一系列独特的机制,在编译时而非运行时保障程序的安全性,从而预防了广泛存在的常见错误和漏洞。

核心特性

上一节我们概述了Rust的安全设计理念,本节中我们来详细看看它的几个核心特性。

以下是Rust语言的核心安全特性:

  1. 无垃圾回收的内存安全

    • Rust无需垃圾回收器即可保证内存安全。这意味着不存在空指针或悬垂指针,也没有数据竞争。安全性在编译时就被强制执行。
  2. 所有权与借用

    • 所有权和借用是Rust的独特功能,用于管理内存安全。在Rust中,每个值都有一个被称为其“所有者”的变量。同一时间只能有一个所有者。这可以防止多个线程同时修改数据,而这是其他语言中常见的错误和崩溃来源。
  3. 默认不可变

    • 在Rust中,所有变量默认都是不可变的。这意味着一旦变量被赋予一个值,除非明确标记为可变,否则它不能被更改。这个设计选择可以防止对变量的意外修改和数据竞争。
  4. 零成本抽象

    • 零成本抽象意味着Rust提供了高级语法,但不会牺牲性能。这些高级抽象使代码更容易理解和编写,从而降低了出现错误和安全漏洞的可能性。
  5. 内置测试与文档

    • 这意味着文档贯穿于代码之中。你可以拥有更清晰的代码和易于理解的文档,并且这些都内置在Rust的安装中。
  6. 无数据竞争的并发

    • Rust的设计可以在编译时防止数据竞争。其所有权系统允许对状态变化进行细粒度控制,类型系统确保任何给定的数据要么只能被单个线程修改,要么可以被多个线程同时读取,但不能同时进行。这个设计选择有助于防止复杂且难以检测的并发错误,这些错误在典型的多线程应用程序中可能导致安全漏洞。

Rustland童话故事

我们已经了解了Rust的技术特性,现在让我们通过一个童话故事来形象化地理解这些原则。

想象一个名为Rustland的神奇王国。在这个王国里,有一些特殊的规则来管理玩具(在我们的故事中,玩具代表内存)。

  • 所有权规则:每个玩具(或每块内存)在同一时间只能有一个主人。这可以防止关于谁可以玩哪个玩具的混乱和争执。想象一下,如果两个孩子想同时玩同一个玩具,可能会导致冲突或玩具损坏。这很像Rust中的内存安全和所有权,它防止了代码的不同部分试图使用同一块内存而产生的冲突。

  • 借用规则:有时,主人可以在严格的规则下将玩具借给另一个人:在玩具被归还之前,主人不能玩它。这很像Rust的借用特性,即使在内存被共享时也能确保安全。

  • 不可变性:Rustland的玩具也是神奇的。一旦它们被制造出来,除非贴上一个特殊的“可变”贴纸,否则就不能被改变。这可以防止任何人意外损坏或改变玩具。这也类似于Rust中变量默认不可变的特性,防止了意外的更改。

  • 零成本抽象:这个王国还以神奇的建造者而闻名,他们可以用简单的积木制造复杂的玩具,而且完全不会减慢玩具的生产速度。这类似于Rust的零成本抽象,允许开发者编写复杂的代码而不牺牲性能。

  • 内置文档:Rust有一个很棒的图书馆,里面装满了关于如何正确使用每个玩具的说明书。这类似于Rust的内置文档,确保开发者知道如何正确使用代码的每个部分,从而减少了出错或出现安全问题的机会。

  • 并发模型:最后,在Rustland,有一种独特的方法来组织游戏约会。孩子们不会以混乱的方式一起玩,而是在受控的环境中,每个孩子都有一些时间玩每个玩具,从而防止玩具丢失或损坏的机会。这很像Rust的并发模型,它通过确保数据只能被一个线程修改或被多个线程读取(但不能同时进行)来防止数据竞争。

这个童话故事的结局是,Rustland成为了一个对所有人来说都快乐且安全的王国,就像Rust对所有用户来说都是一种可靠且高效的语言。

总结

本节课中,我们一起学习了Rust语言的核心安全设计原则。我们了解到,Rust通过所有权系统借用检查器默认不可变零成本抽象等机制,在编译阶段就主动预防了内存错误、数据竞争等常见问题。通过“Rustland”的童话比喻,我们希望这些概念对初学者而言变得更加直观和易于理解。这些设计使得Rust在提供高性能的同时,也能确保高可靠性和安全性。

101:通过Rust编译器预防数据竞争 🛡️

在本节课中,我们将要学习Rust语言的一个核心特性:它如何保护开发者,避免在多线程应用程序中犯下灾难性的错误。我们将通过一个具体的代码示例,理解Rust编译器如何阻止潜在的数据竞争,并介绍如何使用互斥锁(Mutex)来安全地共享数据。


理解数据竞争的危险性

上一节我们介绍了Rust的多线程基础。本节中我们来看看一个典型的多线程编程陷阱:数据竞争。

数据竞争发生在多个线程同时访问同一块内存,且至少有一个线程进行写入操作时。这可能导致程序崩溃、数据损坏或产生不可预测的结果。在Rust中,编译器会主动识别并阻止此类不安全操作。

以下是导致数据竞争的示例代码:

use std::thread;

fn main() {
    let mut data = vec![1, 2, 3];

    for i in 0..3 {
        thread::spawn(move || {
            data[i] += 1; // 尝试在多个线程中修改同一个向量
        });
    }
}

在这段代码中,我们创建了一个可变向量 data,并尝试在三个不同的线程中捕获其可变引用并进行修改。这是一个危险的操作,因为所有线程都可能同时读写 data,从而引发数据竞争和内存损坏。在任何场景下,我们都不希望这种情况发生。


编译器的保护机制

当我们尝试编译上述代码时,Rust编译器会立即阻止我们。

以下是编译器的错误信息核心内容:

error[E0382]: use of moved value: `data`
  --> src/main.rs:8:27
   |
5  |     let mut data = vec![1, 2, 3];
   |         -------- move occurs because `data` has type `Vec<i32>`, which does not implement the `Copy` trait
...
8  |         thread::spawn(move || {
   |                           ^^^^^^^ value moved into closure here, in previous iteration of loop

编译器明确指出问题所在:data 的所有权在循环的第一次迭代中就被移动(move)到了第一个线程的闭包中。在后续的迭代中,我们试图再次使用一个已经被移动的值,这是不允许的。本质上,这是一个危险的操作,而编译器保护了我们。

如果我们无视错误强制编译,编译器会说“不,你不能这样做”。这个保护机制是Rust的核心优势之一。


解决方案:使用互斥锁(Mutex)

既然直接共享可变数据行不通,那么解决方案是什么呢?我们需要一种机制来协调多线程对数据的访问。

Rust标准库中的 Mutex(互斥锁)正是为此而生。互斥锁确保在任何时刻,只有一个线程可以访问被保护的数据。

以下是使用 Mutex 安全共享数据的代码:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // 使用 Arc 实现多所有权,使用 Mutex 实现内部可变性
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));
    let mut handles = vec![];

    for i in 0..3 {
        // 克隆 Arc 的指针,增加引用计数
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            // 获取锁,这会阻塞直到锁可用
            let mut data_guard = data_clone.lock().unwrap();
            // 现在可以安全地修改数据
            data_guard[i] += 1;
            // 当 data_guard 离开作用域时,锁会自动释放
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }

    // 打印最终结果
    println!("Result: {:?}", *data.lock().unwrap());
}

这段代码的工作流程如下:

  1. 包装数据:我们将向量包装在 Mutex 中,然后再包装在 Arc(原子引用计数)中。Arc 允许多个线程拥有数据的所有权,Mutex 确保互斥访问。
  2. 获取锁:在线程中,我们调用 lock() 方法来获取互斥锁。这会返回一个守卫(guard)。
  3. 操作数据:通过这个守卫,我们可以安全地读取或修改被保护的数据。
  4. 释放锁:当守卫离开作用域被销毁时,锁会自动释放,其他线程便可以获取锁。

这样,每次只有一个线程能操作向量元素,修改完成后释放锁,让其他线程继续。这完美解决了数据竞争问题。


总结与Rust的优势

本节课中我们一起学习了Rust如何通过其所有权系统和编译器检查来预防数据竞争。

我们首先看到了一个试图在多个线程中直接修改共享向量而导致编译失败的例子。接着,我们学习了使用 Arc<Mutex<T>> 这一经典组合来安全地实现线程间的数据共享与修改。

这是Rust语言一个强大的特性,它能保护你免于执行那些本不该进行的危险操作。编译器是你的朋友。这也是选择Rust进行高度并行、高并发工作的一个重要原因,因为这些安全特性已内置于语言本身。

此外,作为一个现代编译型语言,Rust从过去几十年其他语言的经验教训中汲取了智慧。一些在二三十年前设计的语言中不那么明显的问题,在Rust这样的新语言中得到了根本性的解决。这使得用Rust编写安全、高效的多线程代码变得更加容易和可靠。

102:使用AWS Config实现安全 🔒

在本节课中,我们将学习如何使用AWS Config在账户级别分析AWS资源。我们将了解其核心功能、配置方法以及如何通过命令行和图形界面来审计资源合规性。


概述

AWS Config是一项服务,用于监控和评估AWS资源的配置。它记录资源配置的变更,并根据您指定的最佳实践规则评估这些变更,从而帮助您实现安全审计和合规性管理。


AWS Config 的核心功能

上一节我们介绍了AWS Config的用途,本节中我们来看看它的核心工作机制。

AWS Config会监控AWS资源中发生的变更。具体来说,Config会以一致的格式记录并规范化这些变更,从而形成一个可以追踪所有活动的审计系统。它会根据您指定的配置来评估这些变更。

其工作流程的核心概念可以概括为以下步骤:

  1. 监控与记录:监控资源变更。
  2. 规范化:将变更转换为一致格式。
  3. 评估:根据规则评估变更的合规性。
  4. 报告:将结果交付到存储桶或通过API、仪表板展示。

使用AWS Config的两种方式

了解了核心功能后,我们可以通过两种主要方式来操作AWS Config:AWS命令行界面(CLI)和管理控制台。

通过AWS CLI使用Config

对于高级用户而言,熟悉命令行操作非常有益。AWS Cloud Shell环境是一个很好的起点。

在CLI中,您可以使用 aws configservice 命令。输入 help 可以查看所有可用选项,用于描述您组织中的所有资源。

aws configservice help

对于初步设置好的AWS Config系统,运行CLI查询可能是最佳的使用方式之一。

通过AWS管理控制台使用Config

另一种方式是使用AWS管理控制台,它提供了非常友好的图形界面。

以下是访问控制台仪表板的步骤:

  1. 登录AWS管理控制台。
  2. 导航到“AWS Config”服务。
  3. 您将看到已设置规则的仪表板,可以查看安全组、S3存储桶、EC2实例等资源的合规状态。

配置与部署合规性包

现在我们已经知道如何访问Config,本节我们将学习如何配置具体的审计规则。

在控制台中,您可以配置“合规性包”。一个合规性包是多个规则的集合。例如,您可能有一些不合规的规则需要处理。

部署一个新合规性包的流程如下:

  1. 在AWS Config控制台选择“合规性包”。
  2. 点击“部署合规性包”。
  3. 您可以选择使用示例模板。系统提供了许多最佳实践模板,例如:
    • API网关的操作最佳实践
    • AWS备份的操作最佳实践
    • S3的操作最佳实践
  4. 选择所需的模板(例如“S3操作最佳实践”),点击“下一步”。
  5. 为合规性包命名(例如 S3-Best-practices)。
  6. 再次点击“下一步”并部署。

部署完成后,这些规则将应用于与此根用户关联的所有账户,而不仅仅是单个账户。您可以根据AWS推荐的最佳实践来审计资源。


查看审计结果与问题排查

部署合规性包后,我们可以查看审计结果。例如,系统可能提示EFS(弹性文件系统)未受备份计划保护。

审计报告会明确指出:

  • 哪个规则发现了问题(例如:“此规则由AWS Config创建,用于检查EFS系统是否受备份计划保护”)。
  • 哪些资源不合规(例如:“这些资源未受备份计划保护”)。

这种方式的好处在于,它基于明确的规则集提供客观评估,无需依赖个人意见或手动询问团队是否执行了备份等操作。


总结

本节课中我们一起学习了AWS Config,这是一个用于在账户级别审计AWS资源的强大工具。我们了解了其记录和评估资源配置变更的核心功能,并掌握了通过AWS CLI和管理控制台两种方式来使用它。最后,我们学习了如何部署合规性包来实施AWS推荐的最佳实践规则,从而实现自动化的安全与合规审计。对于使用AWS的大型组织,尤其是需要严格合规管理的企业,AWS Config是一项强烈推荐的服务。

103:AWS Security Hub 演示 🛡️

在本节课中,我们将学习 AWS Security Hub 的核心功能。这是一个帮助您在云上管理和提升安全态势的服务。我们将了解它如何整合安全视图、自动化安全检查,并提供合规性支持。

概述

AWS Security Hub 帮助您在云上管理和提升安全态势。该平台的一个突出特点是它能将所有安全信息整合到一个统一的视图中。

安全概览与自动化检查

上一节我们介绍了 Security Hub 的整合视图功能。本节中,我们来看看它的核心自动化能力。

Security Hub 会自动化执行安全检查。这些安全检查不仅会在您当前所在的区域运行,还会覆盖您账户下的任何区域以及未来生成的任何新区域。这对于满足合规性要求有巨大帮助。

集成的安全服务与警报处理

Security Hub 集成了多项 AWS 安全服务。以下是它所整合的部分关键服务:

  • Amazon GuardDuty
  • Amazon Inspector
  • Amazon Macie

当这些服务生成警报后,您可以对警报采取行动。例如,您可以建立工单系统、聊天通知或邮件提醒。这些集成允许您为特定的安全事件设置自动化的补救措施。

功能界面详解

现在,让我们具体看看 Security Hub 是如何工作的。

在 Security Hub 的主界面,首先您会看到一个摘要。这里会根据您设置的安全标准,显示您通过了多少项检查。旁边会列出“安全检查失败最多的资源”,您可以在此查看具体的资源信息。

如果我们进入“基础安全最佳实践”部分,这是其中一项安全标准。我们还有“CIS AWS Foundations Benchmark 1.4”等其他标准。您甚至可以启用更多标准。这项功能的优点是,您可以根据业务需求(例如处理信用卡交易或医疗健康数据)来启用相应的合规选项。

在“控制”页面,您可以查看不同的安全控制项。再次回到“安全标准”页面,您可以深入查看特定标准包含的详细内容。这是一种非常有用的方式,可以将标准统一应用到您账户下的每一个区域。

深入分析与发现

“洞察”也是一个很好的功能。您可以深入查看并详细分析一些被自动发现的问题。

以下是“洞察”功能可能揭示的一些关键发现:

  • 发现最多的资源
  • 具有公开读写权限的存储桶(S3 Buckets)
  • 存在某些问题的 EC2 实例

这些都是需要重点关注和检查的内容。同样,在“发现”页面,我们可以深入查看具体的发现项。您可以通过账户 ID 来精确追踪发生了什么。

集成与自动化

最后,在集成方面,您可以将 Security Hub 与其他服务集成,例如聊天机器人、审计管理器、Config 或 Detective,以实现与您系统的进一步整合。

在“自动化”部分,您可以创建规则。例如,您可以创建一条规则,提升与重要资源相关的发现项的严重等级。

总结

本节课中,我们一起学习了 AWS Security Hub。总而言之,Security Hub 是您进行安全优先级排序、执行首要安全检查的地方。您可以为其添加自动化流程。由于该服务提供了统一的安全视图和评估标准,任何实施安全管理的组织都应该使用它。

104:讲解如何通过双因素认证保护账户 🔐

在本节课中,我们将学习如何为GitHub账户启用双因素认证(2FA)。这是一种至关重要的企业安全实践,能确保账户遵循最佳安全标准。

概述

双因素认证是目前最安全的身份验证方式之一。通过结合时间性的一次性密码(TOTP),它能提供极强的账户保护。接下来,我们将分步介绍如何在GitHub上配置和使用双因素认证。

在GitHub界面中启用双因素认证

首先,我们需要在GitHub的用户设置中启用双因素认证功能。

以下是具体的操作步骤:

  1. 进入GitHub的“Settings”(设置)。
  2. 在左侧菜单中找到并点击“Password and authentication”(密码和身份验证)。
  3. 在该页面中,找到“Enable two-factor authentication”(启用双因素认证)选项并点击进入。

设置身份验证器应用

上一节我们介绍了如何找到启用2FA的入口,本节中我们来看看如何配置具体的验证工具。

启用2FA后,系统会引导你设置身份验证器应用(如Google Authenticator或Authy)。你需要使用手机应用扫描页面提供的QR码。成功绑定后,你的验证器应用将开始生成基于时间的一次性密码(TOTP),这是双因素认证中最安全的形式之一。

其核心原理可以用一个简化的公式表示:
一次性密码 = 函数(共享密钥, 当前时间)

配置备份安全密钥

在设置好基础的身份验证器应用后,为了进一步提升账户安全性,我们强烈建议配置备份安全密钥。

备份安全密钥(如YubiKey等物理安全密钥)比短信验证(SMS)更加安全,是推荐的最佳实践。完成此步骤后,你的账户将拥有多一重的恢复和保护机制。

双因素认证登录流程解析

现在我们已经完成了双因素认证的设置,让我们来了解一下完整的登录流程是如何工作的。

以下是用户登录时,双因素认证触发的完整步骤:

  1. 用户发起登录请求:用户在GitHub界面输入用户名和密码。这是双因素认证中的“第一因素”。
  2. 服务器验证密码:服务器收到请求后,会核对提交的密码与存储的凭证是否匹配。只有密码正确,流程才会进入下一步。
  3. 生成双因素令牌:密码验证通过后,服务器会启动2FA流程。这包括通过短信或用户已绑定的OTP应用,向用户发送一个唯一的、有时效性的令牌
  4. 用户提交令牌:用户在GitHub登录界面输入收到的或OTP应用生成的令牌。这是“第二因素”,也是最后的安全层。
  5. 服务器验证令牌:服务器将用户提交的令牌与自己生成并发送的令牌进行比对验证。在这个短暂的验证窗口期内,令牌必须匹配,登录才能成功。

这个过程确保了即使密码泄露,没有第二因素(动态令牌)也无法登录账户。

总结

本节课中我们一起学习了如何为GitHub账户配置双因素认证。我们了解了在设置界面启用2FA、绑定身份验证器应用、设置备份安全密钥的具体步骤,并详细分析了2FA登录的整个安全验证流程。采用基于时间的一次性密码(TOTP)是目前最安全的方法之一,未来它很可能成为所有账户的必备要求。通过本教程,你应该能够为自己的账户成功启用这一强大的安全保护措施。

105:理解访问权限与角色 🔐

在本节课中,我们将要学习GitHub中的访问权限和角色。理解这些概念对于安全、有效地管理代码仓库和团队协作至关重要。


概述

GitHub的权限系统围绕用户类型、访问级别和组织结构来设计。我们将从个人账户开始,逐步探讨组织账户和企业账户的权限模型,并了解如何为不同成员分配合适的访问级别。


用户类型

首先,我们可以从用户类型的角度来考虑访问权限和规则。

以下是几种主要的用户类型:

  • 仓库所有者:对仓库拥有完全控制权。
  • 组织所有者:对整个组织拥有最高管理权限。
  • 团队成员:属于组织内特定团队的成员,权限由团队设定。
  • 外部协作者:被邀请参与特定仓库的个人,不属于该组织。

访问级别

上一节我们介绍了用户类型,本节中我们来看看不同层级的访问控制。权限不仅取决于“你是谁”,还取决于“你能做什么”。

我们可以从三个层面来思考访问级别:

  1. 仓库访问级别:这决定了你对单个仓库的操作能力。例如,你拥有读取权限还是写入权限?
  2. 组织级别:这涉及对整个组织的管理,例如组织所有者、账单管理等角色。
  3. 企业级别:这是针对大型企业账户的更高层级的控制和管理。

个人账户权限

让我们从个人账户开始,这是一个很好的起点。默认情况下,个人账户的所有者拥有在GitHub上执行所有操作的权限,这很合理。你可以创建新仓库、拉取代码、写入仓库等。

然而,你经常需要与他人协作,但又不想赋予他们任何管理权限。通常,你会添加协作者级别的访问权限。这意味着协作者可以对你的仓库进行读取、写入或两者皆可的操作。


组织账户权限

接下来是组织账户。例如,一家公司可能拥有多个不同的仓库。在组织级别,角色通常分为所有者账单管理员成员

如果你是组织的成员,你将无法访问账单系统,也不能销毁仓库或整个组织,除非你拥有所有者级别的权限。

在权限方面,我们需要考虑人员、团队以及读、写、管理等操作。

  • 人员:这指的是分配给个人的典型操作角色。
  • 团队:这是为了更有效地组织权限。你可以按团队划分访问权限。例如,某些私有或绝密代码仓库,可能只允许特定团队访问,而其他团队则不行。
  • 读/写/管理:这些是在组织级别进行控制的权限。例如,谁可以创建新仓库,谁可以将仓库设为公开或私有。

企业账户权限

最后,我们来谈谈企业账户。这是GitHub生态系统中一个非常有趣的方面,它允许你以更企业化的方式进行组织。

这意味着你可以在整个组织范围内进行协作。管理员可以拥有可见性和管理权。企业所有者可以邀请现有组织加入账户或转移组织。你还可以在企业级别强制执行策略,这为大型组织提供了必要的控制级别。

此外,企业账户还涉及账单、使用情况、用户许可证等管理。例如,获取生成式AI编码助手、用于构建服务的GitHub Actions计算时长或GitHub代码空间等功能的访问权限。


总结

本节课中我们一起学习了GitHub的权限管理体系。重要的是思考不同的访问级别、与之关联的权限,以及根据特定的账户类型(个人、组织或企业)制定合理的规则。理解这些概念将帮助你更安全、高效地管理项目和团队协作。

106:仓库权限级别详解

在本节课中,我们将学习GitHub仓库的三种主要权限级别。理解这些级别对于管理团队协作、保障代码安全至关重要。

权限级别概述

GitHub仓库的权限级别可以从三个主要角度来理解。

上一节我们介绍了权限级别的整体概念,本节中我们来看看具体的三种级别。

无访问权限

无访问权限意味着用户或协作者无法查看或与仓库进行任何交互。

其核心含义在于,这对于安全性至关重要,因为它能确保未经授权的用户甚至无法看到仓库的存在。

以下是适用此权限的场景:

  • 过去的协作者不再需要访问权限。
  • 不应拥有访问权限的人员,例如外部供应商。

只读访问权限

只读访问权限允许用户读取代码,但不能进行任何更改。

这个想法类似于GitHub中的“读取”级别权限。

其含义在于,这非常适用于审计、自动部署,甚至是非技术利益相关者,但不适用于需要做出更改的人员。

读写/管理访问权限

读写访问权限意味着你拥有写入甚至管理员权限,可以修改仓库本身的管理设置。

因此,这适用于核心开发人员、项目经理甚至DevOps人员。

权限策略详解

上一节我们介绍了三种基础权限级别,本节中我们来看看它们在GitHub企业版中对应的具体策略。

这正好对应我们下面看到的这些策略:仓库创建、仓库复刻、仓库管理以及外部协作者管理。

以下是GitHub企业版中的仓库策略,你可以看到它们如何清晰地对应之前的图表:

  • 仓库创建:策略可以是“禁用”(无策略),或者明确指定成员可以创建公开、私有或内部仓库。
  • 仓库复刻:你可以设置策略为“启用”或“禁用”,这分别对应可变或不可变的访问。
  • 外部协作者:同样可以设置是否允许。
  • 仓库管理:在仓库本身的管理方面,人们是否可以更改仓库、删除或转移仓库。如果涉及删除操作,有哪些不同的方面需要我们关注。

因此,这些都是你可以使用GitHub企业版在细粒度级别上进行控制的内容。

总结

本节课中我们一起学习了GitHub仓库的三种核心权限级别:无访问权限、只读访问权限和读写/管理访问权限。我们了解了每种级别的含义、适用场景,并看到了它们在GitHub企业版中对应的具体管理策略。掌握这些知识有助于你更安全、高效地管理项目协作。

107:仓库隐私设置与选项 🔐

在本节课中,我们将学习GitHub仓库的隐私设置与选项。我们将从企业级别的策略开始,逐步深入到单个仓库的精细控制,了解如何管理仓库的可见性、所有权和状态。


企业级别的仓库可见性策略

首先,让我们从企业层面来审视仓库的可见性策略。企业级别的设置决定了整个组织内仓库创建和可见性的基础规则。

在企业的“策略”设置中,我们可以配置仓库可见性的全局策略。例如,我们可以完全禁用创建仓库的权限,或者对创建权限进行限制。

以下是企业级别可配置的主要选项:

  • 限制仓库创建类型:可以设置成员只能创建特定类型的仓库,例如仅限“内部”或“私有”仓库。
  • 无策略:也可以选择不设置任何策略,允许自由创建。
  • 限制外部协作者:我们可以限制仓库对外部协作者的可见性,确保只有内部成员才能访问。

了解这些企业级别的可见性控制非常重要,因为它们决定了后续进行更精细控制的可能性。


组织与仓库级别的设置

上一节我们介绍了企业级的策略,本节中我们来看看在具体的组织内部,如何对单个仓库进行管理。

返回到具体的组织页面,选择目标组织后,我们可以进入“仓库”部分,对每个仓库进行单独控制。

进入任意仓库的“设置”页面,在页面最底部,有一个名为“危险区域”的部分。这里提供了仓库级别的精细控制选项。需要注意的是,这些功能的可用性同样受企业级别策略的控制。

以下是“危险区域”提供的主要操作:

  • 更改可见性:可以将仓库在“公开”和“私有”之间切换。设为“公开”意味着外界可以访问;设为“私有”则只有您自己(或您指定的成员)可以访问。
  • 转移所有权:可以将仓库的所有权转移给另一个用户或其他组织。
  • 归档仓库:将仓库设置为只读状态。这意味着仓库仍然可见,但内容不可更改,适用于存档历史项目。
  • 删除仓库:永久删除此仓库。这是一个不可逆的操作,执行后仓库将无法恢复。

例如,如果我们决定删除一个名为“Duke”的仓库,只需在确认框中输入仓库名称并确认,该仓库就会被永久删除。


总结

本节课中,我们一起学习了GitHub仓库的隐私设置。我们从企业级别的可见性策略入手,了解了如何全局限制仓库的创建和访问。接着,我们深入到组织和仓库级别,探索了如何更改单个仓库的可见性、转移所有权、归档或删除仓库。通过结合企业级策略和仓库级的精细控制,您可以确保仓库的可见性完全符合您组织的安全与协作要求。

108: 揭示GitHub生态核心概念 🚀

在本节课中,我们将要学习如何利用GitHub生态系统来构建和部署机器学习运维(MLOps)项目。我们将探讨GitHub提供的几个核心工具,它们如何帮助团队和个人实现代码的可复现性、高效开发以及自动化部署。


概述:GitHub在MLOps中的价值

GitHub不仅是一个代码托管平台,更是一个强大的开发与协作生态系统。对于教授和学习MLOps,以及专业实践而言,它提供了关键优势。

以下是GitHub生态中四个核心概念,它们共同构成了现代MLOps工作流的基础。


1. 可复现性:Codespaces环境

上一节我们介绍了GitHub的整体价值,本节中我们来看看实现可复现性的关键工具——GitHub Codespaces。

Codespaces环境能提供极高的可复现性,确保团队其他成员或课堂上的学生都能访问与你完全相同的开发环境。

可复现性主要围绕以下三点实现:

  • 云端开发环境:它是一个基于云的工作空间,内置了容器镜像。这个镜像可以是Ubuntu、微软官方镜像或任何与Docker兼容的镜像。
  • 自定义配置:你可以通过 .devcontainer 配置文件来定制这个环境,以满足你的特定需求。
  • 计算与存储:你可以自定义Codespaces,使其使用GPU、多核CPU或高内存机器。这非常宝贵,因为你可以在与生产环境相似的环境中进行测试和构建。


2. 计算能力:GPU访问

在拥有了可复现的环境之后,强大的计算能力是运行机器学习模型的基石。本节我们将探讨如何在GitHub生态中访问GPU资源。

访问GPU允许你执行多种任务,例如深入使用Hugging Face(预训练模型的领先提供商)。你可以下载预训练模型,并在你的设备上直接进行微调,而无需购买昂贵的独立GPU。

以下是利用GPU进行开发的主要方式:

  • 主流框架:Hugging Face经常与PyTorch配合使用,你可以在环境中访问PyTorch。
  • 灵活选择:你也可以使用TensorFlow,从头开始构建自己的深度学习模型。
  • 底层控制:甚至可以深入更底层,直接使用NVIDIA CUDA SDK来构建直接应用于GPU的函数。


3. 开发效率:Copilot AI编程助手

当我们准备好环境和计算资源后,下一步就是高效地编写代码。本节我们将介绍能极大提升编码效率的工具——GitHub Copilot。

Copilot AI编程助手是一种获取代码建议、直接为项目添加细节的方式。由于Copilot能感知你项目中的所有内容,你可以形成一个持续改进的良性循环。

这个循环通常包含以下步骤:

  1. 获取建议:向Copilot系统提出提示和问题,获取关于如何编写代码、构建代码或构建命令行工具的建议,它甚至能为你生成样板代码。
  2. 代码质量:随后,你可以要求进行代码规范检查(如Pylint)、运行测试(如pytest)或在IPython中执行代码。
  3. 效率提升:这种循环可以带来开发速度上数倍的提升。

4. 自动化部署:GitHub Actions

最后,当我们完成了代码开发与测试,如何将其安全、自动地部署到生产环境呢?本节我们将学习实现这一目标的自动化工具——GitHub Actions。

通过GitHub Actions实现持续集成和持续交付,允许你在构建代码的同一位置部署你的应用程序。

以下是利用GitHub Actions实现自动化部署的典型工作流:

  1. 本地构建:在Codespaces中,执行 make installmake testmake deploy 等步骤(定义在你的Makefile中)。
  2. 触发CI/CD:将代码推送到仓库,这会触发GitHub Actions中定义的持续集成步骤。
  3. 镜像构建与推送:Actions可以构建一个Docker镜像,并将其推送到容器注册表(如Amazon ECR)。
  4. 自动部署:推送镜像的动作可以进一步触发平台即服务(如AWS App Runner)自动部署你的应用程序。

这样,你就拥有了在完全相同的环境中开发和最终部署所需的所有工具,为实现持续集成和持续交付的最佳实践提供了平滑的过渡。


总结

本节课中我们一起学习了GitHub生态系统中四个核心概念,它们构成了现代MLOps工作流的关键支柱:

  1. Codespaces 提供了可复现的云端开发环境。
  2. GPU访问 赋予了处理复杂机器学习模型的强大计算能力
  3. Copilot 通过AI辅助显著提升了开发效率
  4. GitHub Actions 实现了从代码到生产的自动化部署流水线。

掌握这些工具,你将能够构建一个高效、协作且专业的机器学习项目开发与运维流程。

109:GitHub Actions实施教程 🚀

在本节课中,我们将学习如何为一个项目启用并实施GitHub Actions。GitHub Actions是一个强大的自动化工具,可以帮助我们自动执行构建、测试和部署等任务。我们将通过一个具体的例子,了解其基本结构和工作流程。

概述

GitHub Actions允许我们通过YAML文件定义自动化工作流。这些工作流可以在代码推送、拉取请求等事件触发时自动运行。我们将查看一个已启用Actions的项目,理解其目录结构、YAML文件的编写方式,以及如何将其与本地开发流程(如Makefile)相结合。

项目结构与YAML文件

任何启用了Actions的项目,都会在.github/workflows/目录下包含一个或多个YAML文件。

以下是一个示例工作流文件的内容,它定义了一个在ubuntu-latest容器上运行的作业:

name: CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Make Install & Lint
        run: make install-lint
      - name: Make Lint & Test
        run: make lint-test
      - name: Make Test
        run: make test
      - name: Make Format
        run: make format

在这个YAML文件中,我们定义了几个步骤,每个步骤都对应一个在Makefile中定义的命令。

与Makefile的关联

将GitHub Actions工作流与Makefile直接关联的好处在于,你无需在YAML文件中反复思考要运行什么命令。你在本地开发时使用的命令(如make formatmake lint)与自动化流程中运行的命令完全一致。

让我们看看对应的Makefile片段:

install-lint:
    cargo install --path .
    cargo clippy -- -D warnings

lint-test:
    cargo clippy -- -D warnings
    cargo test

test:
    cargo test

format:
    cargo fmt --all -- --check

这种对应关系确保了开发环境与CI/CD环境的一致性。当代码推送到仓库时,GitHub Actions会自动执行这些在Makefile中定义的步骤。

在GitHub上创建工作流

在GitHub仓库页面上,你可以通过点击“Actions”标签页来创建新的工作流。

系统会提供几种选项:

  1. 自行设置:这会提供一个基础模板,你可以直接在模板中填入想要运行的命令。这对于初学者是一个不错的起点。
  2. 使用预设示例:GitHub提供了许多针对不同场景的预设工作流,例如“发布Python包”。这些示例是学习最佳实践的好材料。

虽然直接在YAML中编写命令是一种方法,但更推荐使用Makefile来建立本地与云端命令的对应关系。

实战演示:触发一次构建

现在,让我们看看GitHub Actions是如何被触发并工作的。

  1. 首先,我们使用git stash命令将本地的临时更改暂存起来。
  2. 接着,我们对项目中的某个文件(例如一个脚本)做一个小小的修改。
  3. 使用git status查看更改,然后通过git addgit commit提交这些更改。在提交信息中,我们可以写上“Kicking off build”。
  4. 一旦代码被推送到GitHub,它会立即触发一个新的GitHub Actions构建任务。

构建开始后,我们可以进入“Actions”标签页,逐步查看YAML文件中定义的每一个步骤的执行情况。这对于调试和监控自动化流程非常有用。

对于一个标准的部署流程,我建议按顺序执行以下步骤:make install(安装依赖)、make lint(代码检查)、make test(运行测试),最后是make deploy(部署到生产环境)。GitHub Actions正是构建这种专业化DevOps工作流的核心工具。

总结

本节课我们一起学习了GitHub Actions的基本实施方法。我们了解了工作流YAML文件的结构,认识了将其与Makefile结合以保持环境一致性的优势,并演示了如何通过代码提交来触发自动化构建。GitHub Actions功能强大,是现代化、自动化开发流程中不可或缺的一环。

110:GitHub Codespaces 🚀

在本节课中,我们将学习如何使用GitHub Codespaces来自定义一个公开的代码仓库模板。我们将配置开发环境、安装扩展、修改配置文件,并最终创建一个可供他人使用的个性化模板。

概述

GitHub Codespaces提供了一个基于云的完整开发环境。本节演示将展示如何从一个公开模板开始,通过选择机器配置、启用预构建容器、安装必要的扩展以及修改配置文件,来定制一个满足特定需求的开发环境。

环境配置与启动

首先,我们有一个公开的模板仓库。该仓库内包含一些配置文件,例如用于配置Codespaces的开发容器配置文件,以及一些GitHub Actions工作流。

为了满足个人需求并修改此模板,我们可以使用Codespaces。进入Codespaces界面后,默认会进入云端编辑模式。在这里,我们可以选择默认配置,或者从多种机器配置中进行选择。

选择机器配置取决于具体任务。例如,进行需要多核处理的教学时,可以选择16核的机器。如果需要进行GPU相关的工作,例如微调Hugging Face模型,则应选择配备GPU的机器。

以下是选择GPU机器并创建Codespace的步骤:

  1. 在配置菜单中选择GPU机器。
  2. 点击“创建Codespace”。

创建过程需要一些时间。为了加速未来的启动,我们可以启用预构建容器功能。这是一个非常实用的功能。

加速启动:预构建容器

Codespace启动后,我们可以进入设置来配置预构建容器。

以下是配置预构建容器的步骤:

  1. 在Codespaces设置中,选择“使用预构建配置”选项。
  2. 选择主分支(main branch)。
  3. 设置为每次推送代码后自动重建此Codespace。

启用此选项将显著提升未来环境启动的速度。环境启动时,系统会配置开发环境的各种选项。环境就绪后,就可以开始自定义模板了。

自定义开发环境

关于此环境,需要注意的一点是,我们可以通过修改开发容器配置文件来自定义它。这正是接下来要做的事情。

环境连接成功后,首先进行一些基础配置。我喜欢调整编辑器的颜色主题,选择高对比度且易于访问的“色盲测试版”主题,并适当调整字体大小。

接下来,开始为环境添加更多内容并进行个性化设置。其中一个重要步骤是安装扩展。

以下是安装扩展的步骤:

  1. 转到环境中的“扩展”面板。
  2. 搜索并安装“Coil it”扩展(选择夜间构建版以获取最新功能)。
  3. 搜索并安装“GitHub Copilot Labs”扩展,它可用于代码翻译等功能。
  4. 对每个已安装的扩展,右键点击并选择“添加到开发容器”。

这样做的好处是,将来将此环境提供给他人时,他们也能直接获得这些功能。

修改配置文件与模板

另一个常见的定制步骤是修改Makefile并为其安装语法支持扩展。

以下是相关操作:

  1. 搜索并安装“Makefile”扩展。
  2. 右键点击该扩展,选择“添加到开发容器”。
  3. 打开仓库中的Makefile文件。
  4. 向其中粘贴一些预设的配置内容,例如部署、安装、测试、代码格式化和项目检查等指令的模板。
  5. 可以添加注释,为其他使用者提供操作建议。

至此,一个包含实用指令的模板环境就基本配置完成了。

环境验证与最终提交

我们可以验证环境的功能。例如,在终端中输入htop命令,可以查看这个高内存环境的状态。输入nvidia-smi命令,可以检查GPU是否正常运行。

最后,将修改提交到模板仓库。

以下是提交更改的步骤:

  1. 使用git status查看更改。
  2. 使用git add .devcontainergit add Makefile命令添加已修改的配置文件。
  3. 提交更改。

现在,其他人就可以使用这个经过我们定制和增强的模板了。

总结

本节课我们一起学习了如何利用GitHub Codespaces定制开发环境。我们从选择机器配置开始,通过启用预构建容器加速启动,然后安装了必要的编辑器扩展并修改了Makefile配置文件,最终创建并提交了一个功能更丰富、更适合特定工作流的个性化模板。这个过程展示了如何快速构建和分享可复现的开发环境。

111:演示GitHub Copilot 🚀

在本节课中,我们将学习如何使用GitHub Copilot来辅助编程。我们将探索其代码翻译功能,并演示如何通过Copilot进行结对编程,快速构建一个命令行计算器工具。

概述

GitHub Copilot是一个AI驱动的编程助手,能够帮助开发者编写、理解和翻译代码。本节我们将通过两个主要部分来体验其功能:首先,使用Copilot Labs的代码翻译功能将Bash脚本转换为Python和Ruby;其次,与Copilot进行结对编程,共同构建一个功能完整的命令行计算器应用。

启用GitHub Copilot

首先,我们需要在一个已启用Copilot的代码仓库中工作。在VS Code中,我们可以通过扩展面板找到GitHub Copilot Labs和GitHub Copilot。Copilot Labs提供了一些实验性功能,例如代码翻译。

代码翻译功能

上一节我们介绍了如何启用Copilot。本节中,我们来看看如何使用其代码翻译功能来学习新语言。

以下是使用代码翻译功能的步骤:

  1. 在Copilot Labs面板中,找到并启用“语言翻译”功能。
  2. 准备一段你想要翻译的源代码。例如,我们可以从一个现有仓库复制一段Bash脚本。
  3. 将这段代码粘贴到一个新文件中。
  4. 在Copilot Labs的翻译界面,选择你想要转换成的目标语言(例如Python)。
  5. Copilot会自动生成目标语言的等效代码。

例如,我们有一段简单的Bash循环脚本 loops.sh

#!/bin/bash
for i in {1..3}; do
  echo "I love eating healthy snacks like this apple"
done

使用Copilot将其翻译成Python后,会生成类似 py_loops.py 的代码:

for i in range(1, 4):
    print("I love eating healthy snacks like this apple")

运行这两个脚本,会得到相同的输出。这个过程对于已经掌握一门语言并想快速学习另一门语言的开发者来说非常高效。我们还可以轻松地将代码翻译成其他语言,比如Ruby。

与Copilot结对编程

了解了基础的翻译功能后,现在让我们看看如何与Copilot进行更深入的协作,共同开发一个项目。

我们将创建一个名为 ai_pair 的目录,并在其中构建一个命令行计算器。首先,创建一个名为 calc 的文件,并在文件顶部添加描述性注释来引导Copilot。

#!/usr/bin/env python3
"""
This module is used to create calculation functions such as addition and subtraction.
This module will also be invoked as a command-line script using the Click library.
"""
import click

添加 #!/usr/bin/env python3 这一行(shebang)并赋予文件可执行权限,可以让我们直接运行这个脚本。Copilot非常智能,它能根据注释理解我们的意图。当我们开始编写一个函数框架时,比如 def add,Copilot会自动补全整个函数体。

为了让这个工具更实用,我们告诉Copilot我们希望使用Click库来构建命令行界面。我们只需提示“构建一个Click group”,Copilot就会为我们生成所有样板代码,创建一个名为 calculator 的应用组。

接着,我们可以继续提示Copilot为这个group添加具体的命令。例如,在定义了 add 函数后,我们可以要求“为add函数创建一个click命令”。Copilot会理解上下文,自动生成对应的命令装饰器和函数封装。对于 subtract 命令也是如此。

完成基本功能后,我们还可以要求Copilot进行增强。例如,我们可以提示“使用Click的colored output为add函数添加彩色输出”。Copilot会相应地修改代码,为输出添加颜色。

最终,我们就能通过命令行使用这个工具:

./calc --help
./calc add 2 6
./calc subtract 10 4

总结

本节课中我们一起学习了GitHub Copilot的两个强大应用。首先,我们利用Copilot Labs的代码翻译功能,快速将Bash脚本转换成了Python和Ruby代码,这为学习新语言提供了极大便利。其次,我们体验了与Copilot进行结对编程,通过简单的提示和引导,高效地构建了一个带有彩色输出的命令行计算器应用。这个过程展示了Copilot如何理解开发者意图、生成样板代码并加速开发流程。

112:Candle - Rust极简机器学习框架 🕯️

概述

在本节课中,我们将学习Hugging Face Candle框架。这是一个功能强大但设计极简的Rust机器学习框架,专注于GPU支持和简易演示。我们将了解其核心特性、应用场景以及如何利用它执行机器学习任务。


框架介绍

Candle是由Hugging Face赞助开发的Rust机器学习框架。它集成了Rust语言的全部优势,专注于高性能和易用性。

例如,框架提供了在线演示,如使用Whisper模型进行音频转录。用户甚至可以用一行代码调用大多数大型语言模型。


核心特性

上一节我们介绍了Candle框架的基本概念,本节中我们来看看它的具体特性。

极简设计与高性能

Candle是一个为Rust设计的机器学习框架。其极简设计实现了GPU支持、高性能和易用性。

以下是其主要特性:

  • 机器学习任务支持:支持矩阵乘法等基础机器学习操作。
  • 丰富的模型库:提供了大量预训练模型和示例代码。
  • 大型语言模型:支持调用常见的大型语言模型。
  • 多模态模型:支持如Stable Diffusion这样的文生图模型。

跨平台与浏览器兼容性

Candle具备出色的跨平台能力。

以下是其支持的部署选项:

  • WebAssembly:可以构建基于浏览器的Web版本应用。
  • CPU执行:支持在没有GPU的环境下运行。
  • CUDA支持:充分利用NVIDIA GPU进行加速计算。

这意味着开发者可以构建一次模型,然后灵活地部署到多种平台。

面向无服务器推理

框架移除了PyTorch等重型依赖,这带来了显著优势。

以下是其主要优点:

  • 更小的二进制文件:生成的应用程序体积更小。
  • 无服务器部署:可以部署到AWS Lambda等支持无服务器计算的平台,这对于部署大型语言模型尤其有潜力。

类似PyTorch的API

对于熟悉PyTorch的开发者,Candle的学习曲线非常平缓。其API设计与PyTorch相似,便于开发者快速上手。

这是一个非常令人兴奋的框架,潜力巨大。无论是启用GPU还是仅使用CPU,执行大型语言模型都变得非常简单。


总结

本节课中,我们一起学习了Hugging Face Candle框架。我们了解了它是一个专注于高性能和易用性的极简Rust机器学习框架,支持GPU加速、丰富的模型库、跨平台部署以及类似PyTorch的友好API。这些特性使其成为在Rust生态中进行机器学习开发的强大工具。

113:使用GitHub Codespaces进行Rust Candle GPU推理 🚀

在本节课中,我们将学习如何利用GitHub Codespaces的强大功能,为Rust Candle框架配置一个支持GPU的云端开发环境,从而高效地运行大型语言模型推理任务。

概述

Rust Candle框架允许你通过简单的命令(如 cargo run --example)运行大型语言模型(例如Falcon或Llama)的推理。一个关键技巧是传递 --features cuda 参数来启用GPU加速,这能显著提升推理性能。然而,获取GPU资源通常是个挑战。幸运的是,GitHub Codespaces提供了便捷的GPU访问途径。接下来,我们将一步步了解如何实现这一配置。

配置开发容器

首先,我们需要一个包含 devcontainer 配置的代码仓库。这个开发容器包含三个核心文件,它们共同定义了Codespaces的环境。

以下是配置开发容器的关键步骤:

  1. 环境变量文件 (.env)
    此文件用于存储本地环境变量,例如API密钥或密码。

  2. Dockerfile
    此文件负责构建Codespaces的基础镜像,完成所有繁重的环境配置工作。让我们详细看看它做了什么:

    • 它基于 rust 开发容器镜像,这免去了我们手动安装Rust的麻烦。
    • 它安装了一系列必要的系统包,包括 clanglld 等编译工具。
    • 根据项目需求,你可能还需要安装其他包。例如,示例中安装了 ffmpeg(用于音频处理)、gcc,以及至关重要的 CUDA工具包cuda-toolkitcuda-nvcc)。
    • 此外,它还可以自动创建Python虚拟环境,解决Python依赖问题。
  3. devcontainer.json 配置文件
    这个文件对于让CUDA环境正常工作至关重要。其中,"features" 部分需要设置 "ghcr.io/devcontainers/features/cuda:1": {"install": true}。这确保了Codespaces会安装CUDA和cuDNN库。
    你还可以在此文件中定制环境,例如设置安装后命令postCreateCommand)或添加VSCode扩展(如Copilot、Makefile工具等)。

上一节我们介绍了开发容器的核心配置文件,本节中我们来看看如何利用 postCreateCommand 来完善环境设置。

devcontainer.json 中,postCreateCommand 指定的命令会在Codespaces创建完成后自动运行。示例中的命令执行了以下操作:

  • 设置CUDA的路径环境变量,确保系统能正确找到CUDA。
  • 将这个路径设置写入 ~/.bashrc 文件,使其在每次启动终端时都生效。

这样配置的好处是,任何复刻(fork)此环境的人,在启动Codespaces后,GPU环境都应该能“开箱即用”,直接开始运行任务。

启动并验证GPU Codespaces

配置完成后,就可以启动Codespaces了。以下是操作流程:

  1. 在GitHub仓库页面,点击 “Code” 按钮,然后选择 “Codespaces” 选项卡。
  2. 点击 “New with options”
  3. “Machine type” 中选择带GPU的机型(例如,“6-core, 1 GPU”)。这种机型非常适合深度学习工作,尤其配合Rust这种推理性能极高的语言。它通常提供112GB内存、128GB存储和一个GPU。
  4. 创建完成后,环境会自动根据你的配置进行设置。

环境启动后,我们需要验证关键组件是否正常工作。

以下是需要验证的项目:

  • 验证Rust:运行 rustc --version 命令,确认Rust已正确安装。
  • 验证CUDA环境:运行 nvidia-smi 命令。这将显示NVIDIA驱动版本和GPU状态,确认CUDA驱动正常工作。
  • 监控GPU使用率:运行 nvidia-smi -l 1 命令。这会以1秒为间隔循环刷新GPU状态,类似于针对GPU的 top 命令。这对于确认你的代码是否成功调用了GPU至关重要。

运行GPU推理示例

现在,让我们实际运行一个推理任务来测试GPU加速效果。

首先,在一个终端中启动GPU监控命令:

nvidia-smi -l 1

然后,在另一个终端中,使用Rust Candle框架运行一个示例。例如,运行一个使用StarCoder模型生成代码的示例命令(具体命令参考项目README):

cargo run --example starcoder --features cuda -- --prompt "Python function to add two numbers"

观察运行推理的终端输出,同时关注GPU监控终端。你会看到GPU使用率在推理过程中显著上升(例如,从0%峰值达到80%以上),这证明GPU正在被高效利用,从而带来了快速的推理速度。

总结

本节课中我们一起学习了如何配置和使用GitHub Codespaces来搭建一个支持GPU的Rust Candle开发环境。我们了解了开发容器(devcontainer)的配置方法,学会了如何验证GPU环境,并最终通过实际运行大型语言模型推理任务,亲眼见证了GPU加速带来的性能提升。这是一种将大型语言模型推理提升到新水平的绝佳方式:你可以自己运行模型,使用Rust Candle框架调用最新模型,并实时监控GPU的工作负载。

114:远程开发与AWS加速计算

概述

在本节课中,我们将学习如何利用Rust语言进行高性能的现代LLMOps开发。由于Rust是全球最快的编程语言之一,且没有脚本语言的遗留问题,使用cargo一条命令即可完成推理。然而,运行这类应用通常需要强大的GPU支持,并非人人具备这样的硬件条件。因此,我们将介绍如何通过远程开发和VS Code工具,在AWS的EC2实例上搭建开发环境,实现高效的GPU加速计算。


选择AWS加速计算实例 🚀

上一节我们介绍了Rust在LLMOps中的优势,本节中我们来看看如何选择合适的AWS加速计算实例。在AWS上,有多种加速计算实例类型可供选择。

以下是选择实例时需要考虑的因素:

  • 处理器类型:例如AMD处理器通常成本更低。
  • GPU数量:根据测试或生产需求选择,无需为测试支付过多GPU费用。
  • 实例规格:例如G5实例系列适合高性能计算。

在本例中,我们选择了G5.16xlarge实例,这是一台配备现代GPU的合理机器,适合进行测试和开发。


配置EC2实例与远程连接 🔧

上一节我们选择了合适的AWS实例,本节中我们来看看如何配置EC2实例并通过SSH进行远程连接。首先,需要在AWS控制台启动EC2实例,并获取其IP地址。

以下是配置和连接EC2实例的步骤:

  1. 启动EC2实例:在AWS控制台选择G5.16xlarge实例并启动。
  2. 获取IP地址:实例启动后,记录其公有IP地址。
  3. 安装VS Code远程扩展:在本地VS Code中安装Remote-SSH扩展。
  4. 添加SSH主机:通过VS Code添加新的SSH主机,使用实例的IP地址。
  5. 连接远程机器:选择主机并连接,VS Code将打开远程开发环境。

连接成功后,可以在VS Code中打开终端,验证是否连接到正确的机器。例如,运行uname -anvidia-smi命令,确认系统信息和GPU状态。


在远程环境中开发与运行推理 🖥️

上一节我们成功连接到远程EC2实例,本节中我们来看看如何在远程环境中进行开发和运行推理。首先,需要在远程机器上安装Rust和必要的代码库。

以下是远程开发的步骤:

  1. 安装Rust:通过rustup命令一键安装Rust。
  2. 克隆代码库:使用git clone命令克隆Candle代码库。
  3. 进入项目目录:切换到包含推理代码的目录。
  4. 运行推理示例:使用cargo run命令执行推理任务。

在远程环境中,可以像在本地机器一样浏览代码、构建项目并运行示例。例如,运行Llama模型的推理示例,可以观察到GPU加速带来的显著性能提升。此外,还可以尝试其他模型如BLOOM或StarCoder的推理示例。


总结

本节课中,我们一起学习了如何利用Rust和AWS加速计算实例进行高性能LLMOps开发。首先,我们选择了合适的AWS G5实例作为开发环境。接着,配置了EC2实例并通过VS Code的Remote-SSH扩展实现了远程连接。最后,在远程环境中安装了Rust、克隆了代码库,并成功运行了GPU加速的推理示例。通过这种方式,即使没有本地高性能GPU,也能充分利用云端资源进行高效的Rust开发与推理任务。

115:构建Candle Hello World 🚀

在本节课中,我们将学习如何设置一个基于GPU的开发环境,并使用Rust的Candle库创建一个简单的“Hello World”项目。我们将涵盖从环境验证到项目构建的完整流程,包括如何添加CUDA支持以利用GPU加速。


环境准备与验证 ✅

首先,我们需要确保开发环境已正确设置,特别是GPU开发所需的工具。以下步骤用于验证环境。

打开终端,输入以下命令来检查Rust和CUDA编译器驱动是否已安装:

R C digest version
make verify

make verify 命令会显示计算能力并确认CUDA编译器驱动存在。这是进行后续GPU开发的基础。


创建基础Rust项目 🛠️

上一节我们验证了环境,本节中我们来看看如何创建一个新的Rust项目。

我们将遵循Hugging Face Rust Candle文档的指导。使用 cargo new 命令创建一个名为 my_app 的新项目:

cargo new my_app

命令执行后,进入项目目录:

cd my_app

为了便于使用各种命令和快捷方式,我喜欢将通用的 Makefile 复制到项目中。这样可以利用预设的构建和运行命令。

cp ../Makefile .

此时,我们可以先尝试在不启用GPU支持的情况下构建项目,以确保基础配置正确。

cargo build


添加Candle依赖并构建 🔧

现在项目骨架已经搭建完成,接下来我们需要添加Candle库作为依赖。

使用 cargo add 命令可以方便地添加依赖。它会自动更新 Cargo.toml 文件。

cargo add candle-core

添加完成后,查看项目源代码。默认的 src/main.rs 文件内容如下,它只是一个简单的“Hello, world!”程序:

fn main() {
    println!("Hello, world!");
}

这个阶段的目标是验证安装和构建流程是否成功。

由于我们已经复制了 Makefile,现在可以使用 make build 命令来构建项目,这等同于执行 cargo build

make build

构建成功后,运行程序以验证一切工作正常:

make run
# 或 cargo run

如果终端成功打印出 “Hello, world!”,则说明基础安装和构建流程成功。


创建支持CUDA的项目 ⚡

上一节我们完成了基础项目的构建,本节我们将创建一个支持CUDA GPU加速的新项目。

再次使用 cargo new 命令创建一个新项目,这次我们将其命名为 my_cuda_app

cargo new my_cuda_app

进入新项目目录,并同样复制 Makefile 以便操作:

cd my_cuda_app
cp ../Makefile .

现在,关键的区别在于我们需要为 candle-core 库启用CUDA特性。这可以通过在添加依赖时指定特性来完成。

以下是添加支持CUDA的Candle依赖的命令:

cargo add candle-core --features cuda

执行后,查看 Cargo.toml 文件,你会看到依赖项已更新为 candle-core = { version = “…”, features = [“cuda”] }

此时,我们可以直接构建并运行项目,来验证CUDA支持是否已正确启用。

cargo build
cargo run

如果程序能够成功构建并运行,则证明CUDA环境配置和项目设置都已就绪。


总结 📝

本节课中我们一起学习了如何从零开始设置一个支持GPU的Rust开发环境,并使用Candle库创建项目。

我们首先验证了基础环境,然后创建了一个标准的Rust项目并添加了Candle依赖。接着,我们构建并运行了项目以确保流程正确。

最后,我们创建了第二个项目,并通过添加 cuda 特性启用了GPU加速支持,完成了整个CUDA开发环境的搭建验证。

通过以上步骤,你现在已经拥有了一个可以开始进行基于Candle和CUDA的Rust机器学习或LLM应用开发的基础环境。

116:探索先进大语言模型StarCoder 🚀

在本节课中,我们将要学习如何探索和使用一个名为StarCoder的先进开源大语言模型。StarCoder专门用于代码生成,支持超过80种编程语言。我们将了解它的特点,并动手实践如何在Rust环境中运行它,体验其作为编程助手的能力。

什么是StarCoder?🤖

上一节我们介绍了课程主题,本节中我们来看看StarCoder模型本身。

StarCoder是Hugging Face推出的一个开源大语言模型,专为代码生成而设计。它拥有155亿个参数,并在来自“The Stack”数据集的80多种编程语言上进行了训练。与GitHub Copilot等工具类似,它是一个生成式AI模型,但其核心优势在于完全开源。这意味着研究人员、开发者乃至大学机构都可以自由地使用、研究和改进它,旨在“民主化生成式AI革命”,而不仅仅是将其掌握在风险投资家手中。

环境准备与身份验证 🔑

了解了StarCoder的基本信息后,我们需要准备运行它的环境。这包括安装必要的工具和进行身份验证。

首先,需要安装Hugging Face的命令行工具huggingface-hub,并使用它登录你的账户。

以下是安装和登录步骤:

  1. 使用pip安装工具:pip install huggingface-hub
  2. 在终端运行登录命令:huggingface-cli login
  3. 根据提示输入你的Hugging Face访问令牌。

登录成功后,你可以通过运行huggingface-cli whoami命令来验证身份,终端会显示你的用户名,确认你已成功认证。

在GPU环境中运行StarCoder ⚡

身份验证完成后,我们就可以尝试运行模型了。由于模型推理计算量较大,为了获得更好的性能,我们将在配备强大GPU(例如AWS的G5实例)的远程环境中进行操作。

在准备好GPU环境并确认huggingface-cli登录状态后,我们可以运行一个Rust项目来调用StarCoder。该项目利用了Hugging Face的candle框架,这是一个用Rust编写的极简机器学习推理库。

运行模型的核心命令通常如下所示:

cargo run --features cuda --example bigcode --prompt “编写一个Rust函数,实现两个数字相加”

对这个命令的解析如下:

  • cargo run:执行Rust项目。
  • --features cuda:启用CUDA特性,以利用NVIDIA GPU进行加速计算。
  • --example bigcode:指定运行项目中的bigcode示例,该示例专为运行StarCoder这类大代码模型设计。
  • --prompt:后面跟随的是给模型的提示词,即你希望它完成的代码任务。

执行命令后,你会观察到GPU使用率上升。模型首先会加载(这可能需要几秒钟),然后开始推理并生成代码建议。例如,对于“编写一个Python函数实现两个数字相加”的提示,模型可能会生成类似以下的代码:

def add_two_numbers(a, b):
    return a + b

你可以轻松地更换提示词中的编程语言,例如换成“Ruby function that adds two numbers”,模型会相应地生成Ruby代码。模型的加载时间是主要的耗时环节,但实际的推理生成速度非常快。

总结 📝

本节课中我们一起学习了开源代码大语言模型StarCoder。我们首先了解了它的背景和开源使命,然后逐步完成了从环境准备、身份验证到在GPU加速环境下实际运行模型的完整流程。通过实践,我们看到StarCoder能够根据简单的自然语言提示,快速生成多种编程语言的代码片段,展示了其作为开源编程助手的巨大潜力。借助Rust生态的高效和Hugging Face平台的便利,开发者可以相对容易地将这样的先进AI能力集成到自己的工具链中。

117:使用Candle Whisper进行语音转录 🎤

在本节课中,我们将学习如何使用Hugging Face的Candle框架运行Whisper模型,以实现自动语音识别功能。我们将从基础概念开始,逐步介绍如何配置环境、编译项目以及利用GPU加速推理过程。


概述

自动语音识别是当前新兴的技术方向之一。OpenAI的Whisper模型是一个无需微调即可使用的优秀示例。本节课将指导你如何利用Candle框架运行Whisper模型,并探索其不同参数配置。

环境准备与工具介绍

上一节我们介绍了Whisper模型的基本概念。本节中,我们来看看运行它所需的工具和环境。

Candle是一个极简主义的高性能框架,支持GPU加速。其代码库中已包含使用Whisper的示例。为了运行它,我们首先需要查看源代码结构。

以下是main.rs文件的关键观察点:

  • 它使用了clap库来解析命令行参数。
  • 我们可以查看其中定义的常量或可传入的参数。

配置GPU实例

为了获得最佳性能,建议使用配备GPU的计算实例。例如,AWS EC2的G5实例类型搭载了新一代NVIDIA GPU。接下来,我们将通过Visual Studio Code的远程开发功能连接到这样的实例。

在终端中,我们可以使用nvidia-smi命令来确认GPU是否就绪。

编译与运行示例

现在,让我们开始实际操作,看看Candle与Whisper的接口如何工作。

首先,我们可以运行一个基础示例。在项目目录下执行:

cargo run --example whisper

程序会在CPU上运行,并下载一个示例音频文件进行转录,速度仍然很快。

启用GPU加速

虽然CPU运行尚可,但我们可以通过启用特定功能来利用GPU加速。

以下是启用不同级别加速的步骤:

  1. 首先,我们可以添加cuda功能来启用基础GPU支持。编译时会占用部分GPU资源。
  2. 为了进一步优化,可以启用cudnn功能。这是针对CUDA深度神经网络的优化库,能显著提升推理效率。

启用这些功能后重新运行程序,可以观察到GPU利用率显著提升,推理速度更快。

处理自定义音频文件

基础的示例运行顺利。接下来,我们看看如何处理自定义格式和不同的音频文件。

假设在上一级目录中存有自定义的WAV音频文件(例如four_score.wav)。我们可以通过传递特定参数来转录它。

以下是运行自定义转录的命令示例:

cargo run --example whisper --release --features cuda,cudnn -- \
    --input ../four_score.wav \
    --model medium \
    --language en \
    --task transcribe

这个命令会:

  • 以发布模式编译,并启用CUDA和CUDNN功能。
  • 指定输入音频文件的路径。
  • 使用medium规模的Whisper模型进行英语转录。

首次在CPU上运行自定义文件可能会有些缓慢。但当我们切换到强大的GPU实例并启用优化功能后,可以看到GPU利用率最高可达70%,实现了非常快速的推理。这种性能使得该工具适合用于需要处理大量音频转录任务的场景,例如结合AWS Batch服务构建数据处理系统。

总结

本节课中我们一起学习了如何使用Candle框架运行Whisper语音识别模型。我们从环境准备开始,逐步完成了示例运行、GPU加速功能启用以及自定义音频文件处理。关键点在于利用--features cuda,cudnn参数来充分发挥硬件性能。这套工作流程为构建高效的批量语音转录服务提供了基础。

118:探索AWS远程开发架构 🚀

概述

在本节课中,我们将学习如何构建一个基于Rust和Hugging Face Candle的异步开发工作流。我们将重点介绍如何利用AWS云服务,以高效、低成本的方式搭建一个适用于大型语言模型(LLM)开发和部署的远程开发架构。


开发环境搭建 💻

上一节我们概述了课程目标,本节中我们来看看如何搭建开发环境。

首先,你需要一个EC2 G5实例。该实例的优势在于它具备执行现代GPU推理任务的能力,并且你可以使用Spot实例来节省成本。

在开发过程中,你可以随时停止和启动这个实例。你可以通过Visual Studio Code Remote连接到该实例,这是一种非常高效的开发方式。

以下是搭建步骤:

  1. 在VS Code中安装远程开发插件。
  2. 配置插件以连接到你的EC2 G5实例。
  3. 连接成功后,你的本地笔记本电脑即可访问这个强大的云端开发环境。

这意味着你可以在一个功能强大的云端环境中开始开发代码,并且按使用量付费,而无需购买昂贵的硬件,例如价值4万美元的GPU。


生产环境部署与协同工作流 🔄

上一节我们介绍了开发环境的搭建,本节中我们来看看如何将其扩展到生产环境,并实现协同工作。

这里的情况变得更有趣。你可以结合EFS(弹性文件系统)进行协同开发。

以下是该工作流的核心环节:

  • 共享存储:EFS的挂载点可以同时连接到你的开发机和生产环境。
  • 模型管理:你可以将庞大的模型和权重文件(数据量可能达到数十GB)下载到EFS中。
  • 数据中转:EFS可以作为这些数据从开发到生产环境的中转站。

当你向AWS Batch部署任务时,Batch服务可以自动配置EC2 G5实例。这样,你就能以一种非常高效、优雅且低成本的方式,在这些强大的GPU上执行基于Rust的现代推理任务。

你不需要使用Notebook,也不需要Python,整个过程采用纯Rust的方式实现。


架构扩展与成本优化 💡

上一节我们探讨了生产部署流程,本节中我们来看看如何进一步扩展架构并优化成本。

另一个需要考虑的方向是,如果你将代码编译为同时兼容CPU的版本。

你的AWS Lambda函数可以直接从EFS读取该二进制文件,并同样将其作为生产实例提供服务。

因此,我认为这是一个非常令人兴奋的、新兴的Rust在LLMOps和MLOps领域的故事。这真正意味着你可以探索新事物、新想法,并追求效率。

对于许多希望部署LLM服务的公司而言,我认为这个特定的架构是一个很好的起点。


总结

本节课中,我们一起学习了如何利用AWS云服务构建一个基于Rust的远程开发与生产架构。我们从搭建EC2 G5开发实例开始,探讨了通过VS Code Remote进行远程开发的方法。接着,我们引入了EFS来实现开发与生产环境间的数据共享与协同工作,并介绍了如何使用AWS Batch进行高效、低成本的GPU推理任务部署。最后,我们还看到了该架构通过AWS Lambda服务CPU版本二进制文件的扩展可能性。这套架构为希望在LLM领域采用高效、现代化Rust技术栈的团队提供了一个强大的起点。

Rust编程4-5:31:Rust在大语言模型中的优势 🚀

在本节课中,我们将探讨Rust编程语言在构建和部署大语言模型(LLM)应用时的独特优势。我们将逐一分析其核心组件如何为LLM项目带来安全性、性能与可维护性。


概述

Rust的组件特性使其非常适合用于大语言模型应用。接下来,我们将逐步分析这些具体组件。


编译器与内存安全

上一节我们介绍了Rust在LLM中的整体优势,本节中我们来看看其编译器如何保障安全。

在Rust编译器中,借用检查器(Borrow Checker)能够防止数据竞争和不安全的内存访问错误。其所有权系统会在对象离开作用域时自动释放内存,因此无需手动管理内存。编译时检查能在代码部署前发现错误,确保大语言模型部署的健壮性。

核心概念示例

// 借用检查器确保内存安全
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // 所有权转移,s1不再有效
    // println!("{}", s1); // 此处编译会报错
    println!("{}", s2); // 正确
}

模块化组件设计

接下来,我们看看Rust如何通过模块化设计来构建LLM的核心处理单元。

分词器(Tokenizer)将文本转换为用于词嵌入(Word Embeddings)的数值ID。嵌入层(Embedding Layer)将词语映射到高维稠密向量。变换层(Transform Layer)则使用神经网络模型处理文本。这种模块化设计允许通过Rust的Trait来灵活混合与匹配特定组件。

核心概念示例

// 使用Trait定义组件接口
trait Tokenizer {
    fn tokenize(&self, text: &str) -> Vec<u32>;
}
trait EmbeddingLayer {
    fn embed(&self, tokens: &[u32]) -> Vec<f32>;
}

并发与并行执行

了解了数据处理组件后,我们来看看Rust如何高效利用计算资源。

轻量级的Rust线程允许并发与并行执行。通道(Channels)可以安全地在线程间传递数据,没有数据竞争的风险。这使得应用能够充分利用多核CPU,以实现高性能和高可扩展性。

核心概念示例

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        tx.send("Hello from thread").unwrap();
    });
    println!("{}", rx.recv().unwrap());
}

Cargo包管理器与生态系统

最后,我们来探讨Rust强大的构建与依赖管理工具如何简化LLM项目的开发。

Cargo是Rust内置的包管理器,它简化了应用依赖管理。庞大的生态系统提供了丰富的包,可以轻松扩展功能,例如构建命令行工具或Web框架。它可以方便地在项目和团队间共享代码,并通过语义化版本控制确保稳定性。这使得调用大语言模型变得非常简单,例如在Rust Candle项目中,只需运行 cargo run --example 命令即可。

核心概念示例

# 使用Cargo运行示例
cargo run --example my_llm_example

总结

本节课中,我们一起学习了Rust在大语言模型应用中的关键优势。我们从编译器与内存安全模块化组件设计并发与并行执行,以及Cargo包管理器与生态系统四个方面进行了探讨。这些特性共同使Rust成为一个构建高效、安全、可维护的LLM应用的强大工具。

120:无服务器推理 🚀

在本节课中,我们将探讨一个令人兴奋的新领域:为大型语言模型(LLM)构建基于Rust的无服务器推理架构。我们将了解其核心优势、工作原理以及如何实现高效且经济的部署。

概述

无服务器推理为机器学习模型的部署带来了革命性的变化。它结合了可扩展性、成本效益和易于管理的特性,特别适合处理大语言模型这类计算密集型任务。接下来,我们将深入剖析其架构和关键组件。

架构与核心优势

上一节我们介绍了无服务器推理的概念,本节中我们来看看其具体的架构设计和带来的核心优势。

通过观察架构图,我们可以发现,部署机器学习模型可以实现可扩展性成本效益,并且推理过程可以构建在无服务器架构之上。

用户输入可以包含文本、图像或其他数据,这些数据将被传递到例如AWS Lambda这样的无服务器函数中。该函数将包含模型本身以及用于运行量化模型的Rust Candle运行时。

模型量化技术

了解了整体架构后,我们来看看其中的一项关键技术:模型量化。这是实现高效推理的关键。

模型本身将被量化。这意味着它将使用降低精度的整数数据类型,而不是32位浮点数。这可以使模型大小缩小多达四倍。

量化过程可以用以下伪代码概念表示:

// 将高精度浮点权重转换为低精度整数
let quantized_weights = float_weights.quantize(to=Int8);

同时,Candle运行时提供了类似Llama.cpp的优化操作,能够高效地运行量化模型。

无服务器函数特性

模型准备好之后,需要由无服务器函数来承载和运行。以下是其主要特性:

无服务器函数能够自动扩展以处理任意数量的流量,确保快速的响应时间。函数规模甚至可以缩减至零,因此您只需为实际使用的资源付费。

小型化的模型和无需闲置资源的特点,使得这种方案极具成本效益。

成本与性能分析

那么,这种架构具体如何节省成本并提升性能呢?我们来具体分析一下。

无服务器计费基于调用次数,而非持续运行的高昂GPU费用。量化后的整数模型运行速度更快,即使在低端或移动CPU上也能胜任,这真正开启了新的应用场景。

对于许多模型而言,不再需要那些昂贵的GPU。输出结果也能非常快速地返回给用户,自动扩展机制将延迟降至最低。

总结

本节课中我们一起学习了基于Rust和Candle运行时的LLM无服务器推理架构。该架构使您能够高效、经济且大规模地将先进的机器学习模型部署到生产环境中,它通过模型量化减小尺寸、利用无服务器实现自动扩展和按需付费,从而在成本、性能和可扩展性之间取得了出色的平衡。

121:使用Rust构建命令行推理工具 🚀

在本节课中,我们将学习如何使用Rust构建一个命令行界面工具,该工具能够利用Hugging Face的预训练Transformer模型进行文本推理。我们将从下载模型开始,逐步完成模型加载、CLI构建和推理逻辑的实现。

概述

下图展示了构建和运行一个Rust命令行推理工具的整体流程。

该流程始于从Hugging Face Hub下载预训练的Transformer模型。接着,在Rust中加载模型并构建命令行界面。最后,实现推理逻辑,使用户能够通过CLI输入文本并获得模型的推理结果。

从Hugging Face下载模型

首先,我们需要从Hugging Face Hub获取一个预训练的Transformer模型。你可以选择像BERT或GPT-2这样的模型。

上一节我们介绍了整体流程,本节中我们来看看第一步:获取模型。这是所有后续步骤的基础。

在Rust中加载模型

下载模型后,下一步是在Rust代码中加载它。这可以通过rust-berttransformers等crate实现,它们为Hugging Face模型提供了Rust绑定。

另一个选择是使用rust-candle接口。这允许我们在Rust代码中使用该模型进行推理。

以下是使用rust-bert加载模型的示例代码框架:

use rust_bert::pipelines::question_answering::{QuestionAnsweringModel, QaInput};

let model = QuestionAnsweringModel::new(Default::default())?;

使用Clap构建命令行界面

模型准备就绪后,我们需要构建一个用户友好的命令行界面。clap crate可以轻松实现参数解析,允许灵活的用户输入。

你甚至可以定制提示词。例如,如果你想为每个提示词添加一个固定的前言以获得更好的结果,可以将此逻辑集成到CLI中。

以下是构建CLI的基本步骤:

  • 定义参数:使用clap的宏或构建器模式定义程序接受的参数,如输入文本。
  • 解析输入clap将自动处理命令行参数的解析和验证。
  • 组织逻辑:将用户输入传递给后续的推理逻辑。

实现推理逻辑

CLI构建完成后,接下来是实现核心的推理逻辑。这部分代码负责接收用户的输入,将其传递给之前加载的Hugging Face模型,并返回推理结果。

rust-bert等crate会简化这个过程,自动处理数据格式转换和推理执行。

推理逻辑的核心公式可以概括为:
输出 = 模型推理(用户输入)

运行CLI并执行推理

最后,用户可以通过运行CLI并提供文本输入来使用这个工具。输入文本通过CLI传递给Hugging Face模型,推理过程在Rust中执行,最终结果将打印返回给用户。

简而言之,整个过程如下:

  1. 从Hugging Face下载模型。
  2. 使用模型crate在Rust中加载模型。
  3. 使用clap构建CLI。
  4. 实现推理逻辑并将其与模型集成。
  5. 运行CLI,对用户输入执行推理。

总结

本节课中我们一起学习了使用Rust构建命令行推理工具的完整流程。通过利用Rust及其丰富的crate生态系统,特别是rust-bertclap,我们能够高效地集成先进的AI模型并创建出强大的应用。这确实是执行大语言模型推理的一种高效方法。

122:使用Rust构建聊天推理机器人 🤖

在本节课中,我们将学习如何利用Rust生态系统中的一些即插即用组件,构建一个用于大型语言模型推理的Rust聊天机器人。

概述

我们将从选择模型开始,逐步完成加载模型、构建用户界面和实现核心聊天逻辑的过程。整个过程将展示如何将强大的自然语言处理能力集成到Rust应用程序中。

选择与加载模型 🧠

首先,你可以考虑使用Falcon模型。这是一个非常适合入门的起点。该模型在自然语言对话方面表现出色。

接下来,你可以使用类似candle这样的库将模型加载到Rust程序中。candle是一个Rust机器学习框架,可以访问Hugging Face上的模型。

构建聊天机器人界面 💬

上一节我们介绍了模型的选择与加载,本节中我们来看看如何构建用户交互界面。

你可以使用cursive这样的crate来构建一个聊天机器人用户界面。cursive是一个基于终端的Rust界面库。这个界面可以接收用户输入并打印机器人的回复。

实现核心聊天逻辑 🔄

以下是实现核心聊天循环逻辑的步骤:

  1. 持续提示用户输入:程序会不断提示用户输入文本。
  2. 传递输入至模型:将用户输入的文本传递给之前加载的Hugging Face模型。
  3. 打印模型响应:将模型生成的回复打印出来。

这个Hugging Face模型将处理所有的自然语言处理工作,包括分析用户输入、生成连贯的回复,甚至考虑对话的上下文。Rust代码则负责促进这种交互。

功能扩展与展望 🚀

你可以添加额外的逻辑来增强机器人,例如进行情感分析,以使回复更加自然。在完成这个原型之后,你甚至可以将界面适配成一个Web应用程序。

总而言之,你需要做的是:

  1. 从Hugging Face选择合适的模型(例如最新的Falcon模型)。
  2. 将其加载到Rust中。
  3. 构建聊天用户界面。
  4. 利用Hugging Face模型实现聊天逻辑。
  5. 持续提示用户并生成回复。

总结

本节课中我们一起学习了构建Rust聊天推理机器人的完整流程。对于开发者来说,这是一个令人兴奋的时代,因为我们可以将所有工具直接集成到像Rust这样对命令行友好的语言中。Rust的独特之处在于,它在构建二进制文件方面拥有强大的能力。

123:35_02_01_LLMOps二进制文件持续构建 🚀

在本节课中,我们将学习LLMOps中的一个核心概念:持续构建。我们将探讨如何为特定的硬件架构(尤其是GPU)构建二进制文件,并创建可供后续使用的制品。这对于确保你的机器学习应用能在正确的环境中高效运行至关重要。


概述 📋

在LLMOps中,持续构建是一个非常重要的概念。你需要解决两个关键问题:如何构建一个针对正确硬件架构的二进制文件,以及如何创建一个可供后续使用的制品。本节将详细介绍这个流程。


持续构建流程详解 🔄

上一节我们介绍了持续构建的重要性,本节中我们来看看其具体的工作流程。整个过程可以清晰地分为几个步骤。

1. 源代码仓库

流程始于一个源代码仓库。这里存放着你的项目框架,例如,它可能是一个像 Rust Candle 这样的机器学习框架。当你对代码进行修改时,这些更改会触发构建系统。

2. 触发云构建主机

在LLMOps的上下文中,构建系统需要指向一个云主机。这是因为云主机通常配备了GPU,并且预先安装了正确的驱动程序(例如 CUDA 驱动程序)。这台云主机将作为构建主机,远程连接到你的构建系统。

以下是构建系统可能的选择:

  • GitHub Actions
  • AWS CodeBuild
  • 其他基于云的构建系统

具体使用哪个系统并不重要,核心在于它能连接到具备特定GPU环境的构建主机。

3. 编译与注册制品

一旦连接到正确的构建主机,系统就会为特定的GPU架构编译软件。编译完成后,生成的二进制文件(即制品)需要被注册到一个存储库中。

这是一个必要的步骤,因为你必须针对特定的硬件架构进行构建。其核心逻辑可以用伪代码表示:

// 在具备特定GPU驱动(如CUDA)的构建主机上执行
fn build_for_target_architecture(source_code: &str, target_gpu_arch: &str) -> BinaryArtifact {
    compile(source_code, target_gpu_arch); // 针对目标架构编译
    let artifact = generate_artifact(); // 生成二进制制品
    register_artifact(artifact); // 将制品注册到仓库
    artifact
}

4. 拉取与部署制品

当你准备使用这个已注册的二进制文件时,下一步就是从存储库中拉取该制品,并将其部署到一台GPU主机上。

此时,你的二进制文件已经就绪,可以用于:

  • 构建一个聊天循环
  • 构建一个API端点
  • 或任何你计划用该二进制文件实现的功能

5. 核心要点总结

LLMOps的思维方式与传统软件开发有所不同。关键在于:

  • 你需要准备正确的硬件(特别是GPU)。
  • 你需要设置自动化流程。
  • 你必须能够轻松拉取针对特定GPU类型构建的二进制文件,以便在自己的应用(例如聊天循环)中直接使用。

总结 🎯

本节课我们一起学习了LLMOps中的持续构建流程。我们了解到,从源代码变更开始,到最终在GPU主机上部署可用的二进制文件,整个过程涉及触发云构建主机针对特定架构编译注册制品以及拉取部署等关键步骤。掌握这个流程对于高效、可靠地部署机器学习应用至关重要。

124:使用StarCoder构建自定义聊天循环

概述

在本节课中,我们将学习如何利用大型语言模型构建自定义的聊天循环。具体来说,我们将使用Hugging Face的StarCoder模型,结合Rust的异步编程和类型安全特性,创建一个专属于编程任务的AI助手。这个助手能够理解代码上下文,并提供有意义的响应,而无需依赖外部付费服务。


构建自定义对话AI

上一节我们介绍了大型语言模型在聊天循环中的应用潜力。本节中,我们来看看如何实际构建一个自定义的对话AI。

使用Hugging Face的StarCoder模型构建自己的编程助手,是一件令人兴奋的事情。该模型的表现甚至超越了OpenAI的早期模型或其他竞争方案。

观察以下具体示例,你可以了解如何深入构建自己的定制化对话AI。

核心组件与流程

以下是构建聊天循环所需的核心组件和步骤。

首先,设计一个能够引发深思熟虑响应的提示词(prompt)。接着,调用StarCoder模型端点。这个端点可以部署在GPU实例上,例如AWS的G5实例。

使用异步客户端请求可以实现非阻塞的异步通信。这样,聊天机器人能够轻松快速地做出响应。

将每一组对话消息追加到对话历史中,可以为AI提供上下文信息。

使用serde库进行序列化和反序列化操作非常方便,它支持JSON格式。

利用Rust强大的类型系统和内存安全性,可以确保聊天机器人既健壮又安全。

使用Hugging Face的Candle推理引擎,可以轻松集成StarCoder大型语言模型。

最后,构建基于Tokio的自定义聊天循环。你可以看到,我们定义了一个pub async函数来实现聊天循环,并能够遍历和响应用户输入的评论。

总结

本节课中,我们一起学习了如何从零开始构建一个自定义的聊天循环。简而言之,整个过程非常直接:利用StarCoder的模型能力、Hugging Face的推理引擎、Tokio的异步运行时,以及围绕JavaScript的开源客户端等构建模块,你就可以打造属于自己的编程聊天循环,并且无需向任何人支付费用。

125:在AWS G5实例调用Rust Candle(第一部分)🚀

在本节课中,我们将学习如何利用Rust的包管理系统来调用大型语言模型。我们将使用Hugging Face的Candle框架,并在AWS的G5 GPU实例上运行它。整个过程旨在展示其简洁性,无需复杂的设置或依赖外部API服务。


概述

本周我们将探讨一个有趣的场景:使用Rust的包管理系统来调用大型语言模型。虽然很多人都在讨论大型语言模型的强大功能,但其应用流程往往显得复杂。然而,借助Rust和Cargo包管理系统的优势,我们可以通过简单的命令直接运行这些模型。本教程将使用Hugging Face Candle框架,并在AWS的G5 GPU实例上演示具体步骤。


选择AWS实例与镜像

上一节我们介绍了使用Rust包管理系统调用LLM的便利性。本节中,我们来看看如何在AWS上配置合适的运行环境。

首先,我们需要在AWS控制台中启动一个实例。建议选择“Deep Learning Base AMI”作为基础镜像。这个镜像预装了必要的CUDA驱动和cuDNN库,但没有多余的软件,非常适合我们的需求。

以下是启动实例的关键步骤:

  1. 在AWS控制台导航到“实例”页面,点击“启动实例”。
  2. 在“应用程序和镜像”部分,搜索并选择“Deep Learning Base AMI”。
  3. 接下来,实例类型的选择至关重要。

配置GPU实例类型

上一节我们选择了基础镜像,本节中我们来看看如何选择正确的GPU实例类型。

为了获得GPU加速,我们需要选择基于NVIDIA的实例类型。在筛选器中,可以选择“加速计算”或直接查看“G5”系列。G5实例配备了NVIDIA GPU,非常适合运行机器学习工作负载。

在本例中,我启动了一个名为“LLM Ops”的g5.16xlarge实例。连接该实例非常简单,只需在AWS控制台点击“连接”,然后选择“EC2 Instance Connect”即可,无需配置SSH密钥。


安装Rust与克隆代码库

现在我们已经有了可用的GPU实例,接下来需要安装运行环境。

连接到实例后,只需执行一个命令来安装Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装完成后,可以通过 rustc --version 验证。接着,我们需要克隆Hugging Face Candle的代码库:

git clone https://github.com/huggingface/candle.git

Candle是一个为Rust设计的极简机器学习框架。其代码简洁,但最令人兴奋的特点是,运行大型语言模型可能只需要一行命令。这使得它成为LLM Ops领域一个非常强大的工具。


总结

本节课中,我们一起学习了在AWS G5 GPU实例上配置环境以运行Rust Candle框架的初步步骤。我们了解了如何选择合适的基础镜像和GPU实例类型,以及如何安装Rust并获取必要的代码。这一切的准备工作,都是为了下一节实际运行模型打下基础。通过这种方式,我们可以直接、高效地利用本地硬件运行大型语言模型。

126:在AWS G5实例调用BigCode(第二部分)🚀

在本节课中,我们将学习如何在AWS G5实例上构建并运行开源的BigCode模型,将其作为一个本地的代码生成助手。我们将从克隆仓库开始,一步步完成模型的构建与调用。

概述

上一节我们介绍了AWS G5实例的配置。本节中,我们将实际操作,克隆BigCode项目,构建其Rust二进制文件,并最终运行它来生成代码。

克隆与准备项目

首先,你需要访问项目仓库,复制其地址并进行克隆。我已经完成了这一步。

如果项目已有本地副本,你可以使用 git pull 命令来获取最新的更新,因为该项目正在被积极开发。

完成这些后,我们就可以准备运行它了。具体的操作说明可以在项目文档中找到。

构建模型的方法

有两种主要方式来调用这些大型语言模型。一种是直接运行,但我认为更好的方式是先构建它,然后进入目标目录再使用模型。

例如,BigCode本质上是GitHub Copilot的开源竞争对手。如果我们想运行它,具体该怎么做呢?

以下是操作步骤。

构建二进制文件

我们需要执行一个类似下面的命令,但需要稍作调整。

我们使用 cargo build 命令,因为我们实际上想要构建一个可供使用的二进制文件。

同时,我们需要添加 --features 标志。具体命令是 cargo build --features cuda。这样做会启用针对神经网络的优化CUDA驱动支持。

我们可以通过查看项目的 Cargo.toml 文件来验证这一点,其中定义了 [features] 部分。

--features cuda 这个参数将指示构建系统为我们启用CUDA支持。

我认为目前使用这种方法的人还不够多。因为很多人都在注册每月20美元的昂贵服务,但实际上,你可以启动自己的实例并运行一个大型语言模型,这在很多情况下性能更好。

运行模型

构建完成后,进入目标目录。你会看到一个名为 bigcode 的可执行文件。只需输入 ./bigcode 即可启动。

这样,你就拥有了一个完整的大型语言模型编程助手。

现在,我可以输入 ./bigcode -p “构建一个将两个数字相加的Python函数” 来让它生成代码。

这非常强大,我惊讶于没有更多人讨论这个。这再次展示了Rust的能力,让你能够实现这样的功能。

我们可以看到它的速度非常快。如果我想要运行得更快,或者将其封装在bash脚本中以满足特定需求,我都可以做到。现在,我有了自己的结对编程助手,我们还可以进一步调整和优化它。

总结

本节课中,我们一起学习了如何在AWS G5实例上克隆、构建并运行开源的BigCode模型。我们了解了通过 cargo build --features cuda 命令构建支持GPU加速的二进制文件,并最终将其作为本地代码生成助手运行起来。这为获得高性能、可定制的AI编程辅助提供了一种强大的替代方案。

127:Rust BERT简介 🦀

在本节课中,我们将要学习一个名为 Rust BERT 的库。这个库为Rust程序提供了访问Hugging Face平台上预训练NLP模型的能力,使我们能够在纯Rust环境中高效地使用这些强大的模型。

概述

Rust BERT 是一个为Hugging Face模型提供Rust绑定的库。它的核心思想是,我们可以利用Hugging Face上已有的、预训练好的NLP模型(如BERT、GPT-2、BART等)。这些模型最初是用PyTorch等框架训练的,而Rust BERT这个crate则提供了与这些模型的绑定,从而让我们可以在自己的Rust代码中使用它们。

核心工作原理

上一节我们介绍了Rust BERT的基本概念,本节中我们来看看它是如何工作的。

简单来说,Rust程序现在可以通过Rust BERT访问Hugging Face Hub上的预训练模型。这些模型的原生Rust绑定可以被加载,并在Rust环境中高效执行,整个过程无需使用Python

Rust BERT 主要完成以下转换工作:

  • 模型格式转换:将Hugging Face上原始的PyTorch模型转换为Rust代码可用的格式。
  • 文本处理:在纯Rust环境中处理分词(tokenization)和其他文本预处理任务,这使其运行速度极快。

转换完成后,Rust代码便可以调用Rust BERT进行推理(inference),甚至对强大的NLP模型进行微调(fine-tuning)。最终的结果会被转换回Rust的原生类型。

这一切使得Rust能够充分利用Hugging Face的模型,同时避免了Python带来的性能开销。

主要优势与特点

了解了工作原理后,以下是使用Rust BERT带来的主要好处:

  • 直接绑定:提供了Rust到Hugging Face模型的直接绑定。
  • 模型加载:可以加载诸如BERT、GPT-2等多种预训练模型。
  • 原生执行:避免了Python,完全在Rust原生环境中执行,效率更高。
  • 高速处理:分词和预处理等任务均在Rust中完成。Rust是地球上最快的语言之一,因此这些操作极其迅速。
  • 便捷访问:能够轻松地在Rust代码中访问先进的NLP功能。

总而言之,Rust BERT 使得在Rust代码中无缝、直接地使用Hugging Face的NLP模型成为可能。

总结

本节课中我们一起学习了Rust BERT库。我们了解到它是一个桥梁,让Rust开发者能够直接、高效地利用Hugging Face生态系统中丰富的预训练NLP模型,同时享受Rust语言带来的高性能与安全性优势。通过避免Python中间层,它在保持功能强大的同时,提供了更快的执行速度。

128:LLMOps工具库安装与配置指南 🛠️

在本节课中,我们将学习如何安装和配置一个强大的Rust原生自然语言处理库。这个库是Hugging Face Transformer库的Rust移植版本,它利用了Rust的PyTorch绑定或ONNX运行时绑定,以及Rust分词器,能够提供多线程分词和GPU推理等高性能功能。

库的核心功能与优势

上一节我们介绍了课程目标,本节中我们来看看这个Rust库的主要特点和能力。

该库是一个用于自然语言处理的Rust原生工具。它本质上是Hugging Face Transformer库的Rust移植版本,使用了Rust的PyTorch绑定或ONNX运行时绑定,并集成了Rust分词器。

它的强大之处在于支持多线程的、基于Rust的分词处理,因此能提供卓越的性能。同时,它也支持GPU推理。

从使用方式上看,它非常简单直观。你只需要将几条不同的语句组合起来,就能够使用任何模型。

以下是该库支持的一些主要任务:

  • 翻译
  • 文本摘要
  • 多轮对话
  • 零样本分类

你可以浏览所有支持的功能。这是一个开始使用高性能Hugging Face模型与Rust结合的非常强大的方式。

安装注意事项与配置

了解了核心功能后,本节我们将重点关注安装过程中需要注意的几个关键问题。

使用该库时,它会有一个用于下载预训练模型的缓存文件夹。如果你的机器存储空间有限,需要注意这一点,可能需要定期清理缓存。有些模型可能达到数百兆字节甚至数GB。

你可以自定义这个缓存路径。例如,如果你在组织中与多人协作,并且有一个高性能的网络挂载文件服务器,你可以将环境变量设置指向那个网络挂载点。这样,每个开发者就不需要重复下载同一个可能高达10GB的模型文件。这对于大型公司来说可能是一个非常好的策略。

你还可以手动安装LibTorch。LibTorch本身非常庞大,是一个数GB的库。需要注意的是,除非你手动安装并创建符号链接指向它,或者设置相应的环境变量,否则构建工具(Cargo)会在每个项目安装时自动下载它。如果你只是进行一个独立项目,可以不进行特殊处理。但重要的是要意识到,你可能不希望每次创建一个新的Rust项目时都重复下载这个巨大的库。

在Windows和macOS上,你也可以设置相同的环境变量。如前所述,构建工具(Cargo)会自动为你下载LibTorch。但由于这是数GB的、支持CUDA的库,你肯定不希望每次执行 cargo new 命令时都下载它。这可能是安装过程中最需要注意的一点。

此外,该库也支持ONNX运行时,这非常棒,因为它允许我们拥有一个可移植的运行时环境。我们可以通过设置这些环境变量,为LLMOps(大语言模型运维)建立一个良好的安装方案。

这是一个非常值得了解的库,并且相当令人兴奋。

实战:验证安装步骤

理论介绍完毕,现在让我们进入实战环节,按照安装说明一步步验证环境是否正常工作。

我们将使用一个预先设置好的、名为“Rust PyTorch GPU模板”的GitHub开发环境。这个环境内部已经配置好了CUDA等所需组件。现在,让我们按照安装指南进行操作,验证一切是否正常。

首先,我们尝试执行一个命令来克隆仓库并验证。我们输入命令,系统提示权限被拒绝。为了解决这个问题,我们需要使用HTTPS版本进行克隆。

我们切换到代码目录,复制HTTPS克隆链接,然后回到终端环境,将命令中的URL部分替换为HTTPS版本。执行克隆命令。

克隆完成后,我们使用 cd 命令进入该仓库目录。

接下来,我们执行验证安装的命令,即运行示例程序。命令是:

cargo run --example sentence_embeddings

运行后,嵌入向量功能正常工作。安装过程中一个非常重要的环节就是在最后通过运行仓库中的这个示例cargo命令来进行完整性检查,以确保你已经正确导出LibTorch库路径,并且拥有正确的版本。

始终需要回到文档中注意版本信息,这里指明应该使用版本2,未来可能是版本3等等。查看文档非常重要。

最后,就像我们刚才做的那样,继续验证你的安装。我们可以看到,嵌入向量功能确实正常工作。


本节课中我们一起学习了如何安装和配置一个用于LLMOps的高性能Rust NLP库。我们了解了它的核心功能、安装时的关键注意事项(特别是模型缓存和LibTorch库的管理),并通过一个实战演示逐步验证了安装是否成功。正确配置环境变量和进行安装后验证是确保库能高效运行的关键步骤。

129:基础语法与模型加载

在本节课中,我们将学习如何在Rust环境中加载和使用预训练的机器学习模型。我们将通过一个具体的代码仓库示例,了解Rust生态系统中运行模型推理的基本流程,并与传统的Python方法进行简单对比。

环境准备与代码获取

上一节我们介绍了Rust环境的基本设置,本节中我们来看看如何获取并运行一个现成的模型示例。

我已经在Github Codespace环境中安装好了Rust。接下来,我想查看并运行通过Git拉取即可执行的不同示例。Rust生态系统的一大优势在于,只要我执行了git clone,并且拥有一个配置了Cargo的合适环境(例如Github Codespaces),我就可以非常轻松地运行每一个示例。

让我们来尝试一下。首先,查看这个代码仓库并执行git remote -v,可以看到我已经克隆了它。如果我展开目录并进入examples文件夹,会发现这里有许多非常酷的示例可以尝试,而且操作起来非常简单。

运行示例的基本命令

以下是运行示例的基本步骤:

运行一个示例只需要执行cargo run --example命令,后面跟上Rust文件的名称。

深入分析示例代码

现在,让我们以情感分析为例,深入了解一下具体发生了什么。

首先,代码中引入了两个库:anyhowrustbert。然后定义了一个main函数。这个单行示例的结构非常清晰。

具体流程如下:

  1. 创建分类器:这是一个情感分类器。
  2. 创建输入:输入是一个字符串向量,例如 ["string1", "string2", "string3"]
  3. 将输入传递给模型。
  4. 获取并输出情感分析结果。

通过深入研究这个示例仓库并实际运行代码,可以非常容易地复现这些示例。

Rust与Python的简单对比

我认为,相较于传统的基于Python的数据科学方法,这是一个巨大的优势,因为它尝试示例的过程非常直接明了。

本节课中我们一起学习了在Rust中加载和运行预训练模型的基本方法。我们了解了如何从Git仓库获取示例、运行命令的结构,并剖析了一个情感分析示例的代码流程。最后,我们看到了Rust在此类任务中展现出的简洁性和易用性优势。

130:构建情感分析命令行工具

在本节课中,我们将学习如何使用Rust构建一个情感分析命令行工具。我们将利用rust-bert库来调用预训练的自然语言处理模型,并结合clap库来解析命令行参数,最终创建一个可以轻松分发给他人使用的独立二进制程序。

概述

我们将创建一个名为sentiment的命令行工具。该工具接收一段文本作为输入,使用rust-bert库中的情感分析模型进行分析,并输出该文本的情感倾向(积极或消极)。整个过程将展示如何将复杂的机器学习模型封装成一个简单易用的命令行应用。


构建过程详解

上一节我们介绍了项目的目标,本节中我们来看看具体的代码实现和依赖配置。

1. 核心功能函数

首先,我们来看实现情感分析的核心函数。这个函数负责接收文本,调用模型,并返回分析结果。

use rust_bert::pipelines::sentiment::SentimentModel;

pub fn classify(input: &str) -> String {
    // 1. 加载预训练的情感分析模型
    let sentiment_classifier = SentimentModel::new(Default::default()).unwrap();

    // 2. 将输入字符串转换为模型所需的向量格式
    let input_as_vec = vec![input.to_string()];

    // 3. 进行情感预测
    let output = sentiment_classifier.predict(&input_as_vec);

    // 4. 格式化并返回结果
    format!("{:?}", output)
}

这个函数非常简洁。它首先初始化模型,然后将输入文本包装成向量,最后调用模型的predict方法并返回结果。

2. 项目依赖配置

为了使用上述功能并构建命令行界面,我们需要在Cargo.toml文件中声明以下依赖项。

以下是项目所需的三个核心依赖:

  • rust-bert:这是Hugging Face模型在Rust生态中的移植,提供了我们所需的情感分析等NLP功能。
  • clap:这是一个强大的命令行参数解析库,用于构建我们的工具界面。
  • anyhow:这个库简化了错误处理,便于我们返回和传播错误结果。
[package]
name = "sentiment"
version = "0.1.0"
edition = "2021"

[dependencies]
rust-bert = "0.21.0"
clap = { version = "4.0", features = ["derive"] }
anyhow = "1.0"

3. 主函数与命令行集成

现在,我们来看如何将核心功能与命令行参数解析结合起来。主函数main.rs是程序的入口点。

// 导入我们编写的分类函数和必要的库
use sentiment::classify;
use clap::Parser;
use anyhow::Result;

/// 一个简单的情感分析命令行工具
#[derive(Parser)]
#[command(name = "sentiment")]
#[command(about = "分析输入文本的情感倾向", long_about = None)]
struct Cli {
    /// 需要分析的文本
    #[arg(default_value = "I love you.")]
    input: String,
}

fn main() -> Result<()> {
    // 解析命令行传入的参数
    let args = Cli::parse();

    // 调用分类函数并打印结果
    let result = classify(&args.input);
    println!("情感分析结果: {}", result);

    Ok(())
}

代码首先定义了一个Cli结构体来描述命令行参数,这里我们定义了一个input参数,并设置了默认值。在main函数中,我们调用Cli::parse()来获取用户输入,然后将其传递给classify函数并输出结果。


工具的使用方法

我们已经完成了工具的构建,本节中我们来看看如何编译和运行它。

编译与运行

你可以使用Cargo直接运行程序,也可以编译成独立的二进制文件。

使用Cargo运行(开发模式)
在项目根目录下执行以下命令。默认情况下,程序会分析“I love you.”这句话。

cargo run

运行上述命令,你会看到类似“情感分析结果: [Sentiment { polarity: Positive, score: 0.999...”的输出,表明情感为积极。

传递自定义输入
你可以通过--input(或-i)参数来指定要分析的文本。

cargo run -- --input "The football season is terrible."

运行此命令,输出会显示情感为消极(Negative polarity)。

查看帮助信息
要了解所有可用参数,可以运行:

cargo run -- --help

分发独立二进制文件

Rust的一个强大之处在于可以轻松编译出独立的、可分发的二进制文件。

编译发布版本
以下命令会生成一个优化过的二进制文件,位于target/release/目录下。

cargo build --release

直接运行二进制文件
编译完成后,你可以直接运行生成的可执行文件,无需依赖Cargo。

# 进入编译输出目录
cd target/release
# 运行程序
./sentiment --input "It is beautiful today."

这种方式使得你可以将sentiment这个可执行文件轻松分享给团队成员或客户,即使他们的电脑上没有安装Rust环境也能使用。


总结

本节课中我们一起学习了如何使用Rust构建一个实用的情感分析命令行工具。我们主要完成了三件事:

  1. 利用rust-bert调用预训练的NLP模型,实现了核心的情感分析功能。
  2. 使用clap为工具添加了命令行参数解析能力,使其易于交互。
  3. 将两者结合,创建了一个可以编译为独立二进制程序的应用,展示了Rust在构建可分发工具方面的便利性。

通过这个项目,你不仅学会了如何集成复杂的机器学习库,也掌握了构建标准命令行工具的基本流程,这是开发现代化、可维护的Rust应用的重要一步。

131:Rust PyTorch简介

在本节课中,我们将学习一个名为tch-rs的Rust库,它提供了对PyTorch深度学习框架的Rust绑定。我们将了解它的基本原理、优势以及如何让Rust代码利用PyTorch的强大功能。

什么是Rust PyTorch(tch-rs)?

tch-rs是一个有趣的库,因为它为PyTorch提供了一个Rust接口。

首先简单介绍一下PyTorch。PyTorch是一个流行的深度学习框架,主要用Python编写,并使用C++和CUDA进行优化。它提供了张量运算、神经网络层、模型训练等多种功能。

而这里的tch-rs库,则提供了直接绑定到PyTorch C++后端的接口。本质上,它允许你在原生的Rust代码中使用PyTorch。

核心思想与优势

其核心思想是,通过在Rust程序中使用这个库,你可以利用PyTorch进行深度学习和张量运算。由于它直接绑定到PyTorch优化过的C++后端,并避免了Python的开销,因此能获得巨大的效率提升。

以下是tch-rs的几个关键点:

  • 绑定方式:这些绑定是用Rust编写的,而不是Python。
  • 功能调用:Rust能够使用PyTorch的原生功能,无需承受Python的开销、全局解释器锁(GIL)或像Conda这样的包管理系统带来的问题。
  • 类型转换:Rust类型可以与PyTorch的张量相互转换。
  • GPU加速:你可以利用GPU加速。

工作原理

Rust代码可以调用tch-rs库。该库会处理将Rust类型转换为PyTorch张量、调用运算操作,并将结果转换回Rust类型。

这个过程允许Rust在利用PyTorch模型和GPU加速的同时,避免了Python全局解释器锁的严重限制。

总结

本节课我们一起学习了tch-rs库。我们了解到它是一个为PyTorch提供Rust绑定的接口,能够让我们在Rust中直接使用PyTorch优化过的C++后端进行张量计算和深度学习,从而绕过Python的开销,获得更高的运行效率。

132:运行PyTorch Hello World 🚀

在本节课中,我们将学习如何在Rust项目中使用PyTorch的Rust绑定。我们将从一个简单的“Hello World”示例开始,了解如何创建项目、添加依赖以及执行基本的张量操作。


概述

PyTorch的C++ API的Rust绑定非常令人兴奋,因为它允许你使用Rust这样优雅的语言直接进入PyTorch的低层部分。让我们开始看看具体如何操作。

首先,这里有一个“Hello World”示例。但在运行它之前,我们先深入了解一下必要的准备工作。


项目初始化与依赖配置

要开始使用,首先需要使用cargo new命令创建一个新的项目骨架结构。

以下是创建项目所需的步骤:

  1. 创建新项目:使用cargo new命令。
  2. 配置依赖:在Cargo.toml文件中添加tch crate作为依赖。Cargo的一个优点是能轻松检查最新版本。我们可以确认已使用最新版本。
  3. 编写主函数:在main.rs文件中,我们可以创建“Hello World”张量并开始使用它。

实际上,只需几行代码就能开始。


代码解析与运行

让我们逐步解析代码。首先,我们声明一个类型为Tensor的变量t

let t = ...;

接着,我们创建一个切片,并将切片内的所有元素乘以2。

// 假设的切片操作和乘法
let result = t.slice(/* 参数 */).mul(2);

得到结果后,我们将其打印出来。

println!("{:?}", result);

要执行以上所有操作,我们只需在终端中输入cargo run。你会看到程序成功运行并开始计算。


扩展功能:创建辅助函数

如果你想为其添加更多功能,一个好方法是构建另一个函数。例如,我们可以创建一个辅助函数。

以下是创建辅助函数的步骤:

  1. 定义辅助函数:例如,可以命名为helper_function
  2. 封装逻辑:将部分张量操作逻辑放入这个辅助函数中。
  3. 调用函数:在主函数中调用这个辅助函数,例如hello_world_pytorch

修改后,再次运行cargo run。Cargo会重新编译,程序将再次执行计算。


进一步修改与操作

如果我想进一步修改,可以添加另一个操作。例如,再次乘以2并打印结果。

以下是添加新操作的步骤:

  1. 添加新操作:在现有逻辑后添加另一个乘法操作。
  2. 打印新结果:将新操作的结果打印出来。
  3. 运行程序:使用cargo run命令执行修改后的代码。

使用Rust绑定开始使用PyTorch非常简单直接。


总结

本节课中,我们一起学习了如何在Rust中集成并使用PyTorch。我们从创建项目、配置tch依赖开始,逐步编写并运行了一个基础的张量操作示例。我们还探讨了如何通过创建辅助函数来组织代码,以及如何添加额外的操作来扩展程序功能。通过这些步骤,你已经掌握了使用Rust进行PyTorch开发的基本流程。

133:使用PyTorch预训练模型部署机器学习服务 🚀

在本节课中,我们将学习如何利用Rust语言和PyTorch预训练模型,构建一个高性能、可部署的机器学习服务栈。我们将从系统编程开始,逐步了解如何将模型优化、封装并最终部署到生产环境。


上一节我们介绍了课程的整体背景,本节中我们来看看构建此服务栈所涉及的核心组件及其优势。

以下是构建该服务栈的关键技术组件:

  • Rust:Rust是一种系统编程语言,专注于速度内存安全并行性。这意味着它可以减少常见错误的来源。
  • Actix:Actix是全球最快的Web框架之一,适合构建可扩展的高性能Web服务。
  • PyTorch Rust绑定:这些绑定可用于在Rust中构建、训练和运行机器学习模型。它们封装了C++的PyTorch生态系统。
  • ONNX:ONNX是一个非常有趣的组件,因为它允许你考虑量化互操作性。量化模型可以在精度损失有限的情况下获得更好的性能。ONNX的互操作性意味着你可以使用不同的机器学习库(如PyTorch、TensorFlow或MXNet),并以统一的方式让它们协同工作。
  • Distroless:Distroless容器镜像允许你仅打包应用程序及其运行时依赖。这使得部署过程最小化、更安全且极其高效,因为它减少了容器镜像的攻击面,并使其非常适合推理任务。

了解了核心组件后,我们来看看它们是如何协同工作的。从系统编程的Rust开始,到实现互操作性的ONNX组件,最终阶段是准备部署。

这个过程可以分为三个主要阶段:

  1. 使用Rust(可能结合Actix)进行系统编程和Web服务开发。
  2. 将模型转换为ONNX格式,进行量化等优化以实现高性能和互操作性。
  3. 将最终的ONNX模型和应用程序打包到Distroless容器镜像中,为生产环境部署做好准备。

最后,我们来探讨为什么需要关注这个技术栈,而不是直接使用Python等其他方案。

与Python等方案相比,这个技术栈的优势在于:

  • Rust构建:Rust具有并发和安全特性,能够安全地管理多线程上下文中的状态,是构建高性能Web服务的理想环境。
  • 部署与扩展:由于最终产物是Distroless镜像,你可以将其轻松推送到各种生产云服务容器中,例如GCP、AWS、Azure的容器服务,或你自己的数据中心。

总而言之,由Actix、PyTorch Rust绑定、ONNX和Distroless构成的这个生态系统,是一个现代化的技术栈,是许多部署机器学习模型的组织应该考虑的方案。


本节课中我们一起学习了如何结合Rust、PyTorch预训练模型、ONNX和轻量级容器技术,构建一个从开发到生产部署的完整、高效且安全的机器学习服务流水线。

134:Linux命令行工具、LLMOps 🦀

第134课:运行PyTorch预训练模型

在本节课中,我们将学习如何将一个预训练的PyTorch模型与Rust Axum微服务结合运行。我们将从代码结构、构建过程、本地测试,到最终的Docker容器部署,完整地走一遍流程。


概述与项目结构 📁

这里有一个预训练的PyTorch模型和一个Rust Axum微服务。

让我们深入查看代码。我在GitHub Code Spaces中运行此项目,并拥有这个 r-torch-disk 目录。

首先,我们将进入该目录并开始操作。

cd r-torch-disk

现在,如果我们查看这个目录,我喜欢使用Makefile。你可以看到,如果我执行 make debug,它将构建Docker容器。如果我执行 make build,它将构建发布版本。


构建项目 🔨

让我们执行 make build。你可以看到它显示了我可以运行的具体命令。

cargo build --release

很好,我们之前已经构建过这个项目,所以它只是检查了二进制文件。

现在,如果我想运行它,我也可以直接输入 cargo run。这是运行项目的另一种方式。build 只是编译它,而 run 会运行它。这种基于本地二进制文件运行的好处是,它允许我测试一些东西,例如冒烟测试。

当它在运行时,我们可以看看冒烟测试是什么样子的。这个冒烟测试会遍历并让我们 curl 每一个路由。这里有第二个路由,第三个路由,第四个路由。冒烟测试的好处是,它会上传图像,以便我使用不同的端点进行图像预测。

让我们打开一个新的终端,进入 r-torch-disk 目录并运行冒烟测试。

./smoke_test.sh

你可以看到它遍历了每个端点并运行测试。因此,在构建微服务时,创建一个能轻松测试不同预测并添加良好日志记录的工具,是非常强大的做法。


日志记录与代码结构 📝

接下来,让我们看看我是如何设置所有这些日志记录的。

如果我们进入源代码,查看 main.rs 文件,请注意我的做法。实际上,我在这里使用了这个日志记录器。这将被传递到这个微服务中。

然后,如果我们进入实际的逻辑代码,你可以看到我有很多 info 消息,这些消息允许我查看我正在执行的操作的各个方面,以便我可以在生产环境中调试和查看问题。

因此,在构建微服务时,添加日志记录非常重要,这样你才能知道实际发生了什么。

关于路由,我需要指出的另一点是,在生产环境中我只会使用这个路由。但我确实设置了不同的路由。例如,这个路由用于检查PyTorch是否工作,这个用于检查图像上传是否工作,而这个用于检查我是否可以对本地磁盘上的图像进行预测。

我将这个特定问题分解为三个不同的子路由,以便验证微服务是否正常工作。这是一个很好的健康检查系统。我可能不会在生产环境中向用户公开所有这些路由,而只公开最下面的路由。但这些路由都可以作为自检类型的监控点,用于验证每个不同组件(安装、图像上传以及图像预测本身)是否正常工作。这是一个你可以使用的小技巧。


Docker容器化部署 🐳

如果我想进一步了解容器是如何工作的,让我们接下来尝试一下。我需要做的就是停止当前运行的服务。

再次查看 Dockerfile,它相当直接。我们有Docker代码,我下载了PyTorch,在这里设置了一些环境变量。在这个特定部分,这个 distroless 镜像,是我复制所有构件的地方,包括LibTorch和预训练模型,然后我直接运行二进制文件本身。

至于Makefile,我们可以看到Docker构建过程就是 docker build。要运行它,我们只需执行那个命令。

我将运行那个命令:

make run

这将运行Docker镜像。我喜欢使用Makefile,因为这个命令非常冗长,容易打错字。

我们可以看到,实际上,我之前所做的一切都完全相同地工作。冒烟测试有效。我可以看到我的应用程序很容易部署。


镜像大小与优势 📦

现在,我们可以查看的另一点是这个镜像的大小。这是这种方法的一个巨大优势。

如果我们查看 docker image ls,我们可以看到这是一个3GB的镜像。对于这个特定的PyTorch安装来说,PyTorch本身就有几个GB。因此,这里的大部分代码实际上只是PyTorch。模型本身相当小,不到100MB,而Rust代码本身可能大约20MB。所以,这里的大部分内容只是PyTorch及其部署环境。但这比一个可能达到6GB、8GB或10GB的常规镜像要小得多。因此,使用这种 distroless 类型的包是减少镜像大小的好方法。


测试与模型选择 🧪

我还要指出的另一点是,在这个结构中,在这个特定的测试里,我们有模型测试,它只是运行一些模型测试。我还有一些视觉测试,它运行绑定的安装测试。我还有一些Web测试,例如,验证索引是否正常工作的功能测试。这些都是需要了解的重要步骤。

如果我们还想看看模型本身,我实际上有这个模型。我有一系列不同的模型可供选择,可以放入这个构建过程中。在这个特定的例子中,我使用了一个ResNet模型,但同样,你可以使用任何类型的预训练模型。


总结 🎯

本节课中,我们一起学习了如何将PyTorch预训练模型集成到Rust微服务中。你亲眼看到,你可以使用二进制文件进行部署,也可以使用Docker distroless 镜像进行部署。一旦你完成了这个设置,就可以很容易地将它部署到任何可以处理容器的云服务上。

这些就是整个工作流程的概述。

135:ONNX简介 🧠

在本节课中,我们将要学习ONNX(Open Neural Network Exchange)的基本概念。ONNX是一个用于表示机器学习模型的标准开放格式,它在模型部署和框架互操作性方面扮演着关键角色。

什么是ONNX?

上一节我们介绍了机器学习模型部署的背景,本节中我们来看看ONNX如何解决其中的互操作性问题。

ONNX使用Protocol Buffers序列化和图格式来定义机器学习模型。其核心思想是提供一个中间表示,使得不同框架训练的模型能够被统一地存储和交换。

ONNX的核心优势

以下是ONNX带来的几个主要优势:

  • 框架互操作性:主流框架如PyTorch和TensorFlow都可以将模型导出为ONNX格式。这实现了不同框架之间的模型交换。
  • 模型可移植性:ONNX格式的模型可以脱离原始训练框架,在不同的硬件和环境中运行。
  • 优化部署:ONNX模型可以被专门优化的运行时(Runtime)加载和执行。这个运行时无需支持所有原始框架,只需理解ONNX格式即可。

ONNX与Rust的协同

ONNX与Rust语言的结合在LLMOps(大语言模型运维)领域表现得尤为出色。你可以将ONNX作为模型部署的标准格式。多个框架可以导出它,多个运行时可以导入它,这为构建稳健的MLOps流水线提供了极大便利。

简而言之,ONNX是一个用于机器学习模型的标准开放格式,它基于Protocol Buffers和计算图,被众多框架和工具支持,并拥有能够高效执行ONNX模型的优化运行时。

总结

本节课中我们一起学习了ONNX。它通过提供一个通用的模型表示,极大地促进了模型在不同框架间的交换,并实现了高效的部署。当你在Rust生态中进行LLMOps时,ONNX是一个非常契合的选择。

136:ONNX格式转换 🚀

在本节课中,我们将学习Rust中ONNX格式支持的具体含义及其应用。我们将了解如何利用ONNX运行时进行模型格式转换,以及如何将其集成到构建流程中,以实现模型的便携式部署。


ONNX支持概述

Rust的一个出色特性是它支持ONNX。接下来,我们具体看看这意味着什么。

ONNX运行时的作用

借助ONNX支持,你可以使用一个运行时,它允许你将模型从一种格式转换为另一种格式。此外,你还可以在这里设置ort路径,以便使用该运行时。例如,它可以是一个共享对象文件(.so文件)。这些文件可以作为你构建过程的一部分被下载。

假设你有一个构建过程,它编译必要的二进制文件,同时也可以拉取这个.so文件。这样,你就能为你的模型拥有一个真正便携的运行时。

使用Optimum库进行转换

这里还需要注意的是,你可以使用optimum库。对于Transformer模型,你实际上可以转换它们,并且我们有一些关于如何操作的信息。

让我们继续,看看如何从Hugging Face导出模型到ONNX。我们可以看到,这里展示了你可以进行安装,从而导出模型,之后你就能使用ONNX了。

为何选择ONNX?

那么,我们为什么要使用ONNX呢?如果我们查看这份指南,会发现它是一个开放标准。这意味着,即使你原本使用的是PyTorch或TensorFlow等框架,通过转换,你都能获得一个中间表示。这使你能够在不同框架之间切换。

即使你未来不打算使用其他框架,你的部署过程也可能因此变得更优,因为你拥有一个统一的系统来处理任何类型的模型。所以,这里的核心理念是:通过使用ONNX运行时,你可以进行优化,甚至可以包含量化步骤来真正缩小模型的体积。任何能够打包、提高效率的操作,都是人们使用ONNX的原因之一。


总结

本节课中,我们一起学习了Rust对ONNX格式的支持。我们探讨了ONNX运行时在模型格式转换和便携式部署中的作用,了解了如何利用optimum库从Hugging Face导出模型,并理解了采用ONNX作为开放标准所带来的优势,如框架间的互操作性和部署优化。

137:扩展Google Bard 🧠

在本节课中,我们将学习如何利用Google Bard的扩展功能,特别是其与Google Workspace的集成,来分析和处理个人文档与数据,从而获得更具针对性的AI辅助。


Google Bard提供了一些非常有趣的功能,其中包括使用扩展的能力。扩展功能的一个有趣之处在于,它允许你拥有个性化的上下文环境,你可以向AI助手询问独特的内容,例如航班、酒店、地图,甚至是你的工作空间文档或YouTube视频。

接下来,我们将看看如何让它帮助我处理正在开发的一个Coursera专项课程。

连接个人文档

在上图中,你可以看到我有一个关于“使用Rust进行大语言模型运维”的专项课程大纲。如果我复制这段文本,我可以请求我的Google Workspace Bard集成扩展来执行一些操作。

例如,我可以点击链接并输入指令:“给我这份文档的关键点概述。”

分析与总结

Bard将执行以下操作:它会进入我的Google Drive,找到我的文档,访问文档内容,查找其中的关键事实,然后给我一个摘要。

它总结道,这份文档将涵盖使用Rust进行LLM运维的好处,使用Rust构建大语言模型,大语言模型的部署,以及使用Rust的真实世界项目案例研究。我们可以看到,这里实际上包含了许多我正在处理的不同事项的信息。能够深入挖掘文档的这些特定方面并查看不同的信息片段,这非常酷。

深入探索与代码示例

更令人着迷的是,我还可以进一步深入。例如,我可以提出更具体的请求:“谢谢,你能给我一些我录制的Rust代码示例吗?比如.MP4文件中的内容。”

让我们看看它有多智能。它能否进入文件内部,以文本格式提取数据并提供一些样本?

结果显示,它给出了一些示例。不过,它似乎有点混淆,认为我想要处理MP4文件本身。因此,我换一种方式提问:“给我一个涵盖我课程中某个主题的Rust代码示例。”

生成新创意

我们来看看它是否能利用上下文给出一些新的创意想法。这是使用生成式AI的另一个新兴领域——不仅仅是利用其“魔法”,而是可以设定上下文并获得新灵感。

这里出现了一个代码片段,我在这个专项课程中确实涵盖了二叉搜索树。Bard正在给我一些关于如何在课程中讲解这个主题的新想法。

核心概念与应用模式

我认为这是一个伟大的新兴领域。其核心不仅在于盲目使用生成式AI,更在于在特定扩展的上下文中使用生成式AI,并最终利用你文档中的具体上下文。这样,你就能以更专业的方式直击问题核心,而不是盲目地请求通用想法。

核心交互模式可以概括为:
用户查询 + 扩展权限(访问特定数据源) + 个性化上下文 = 高度相关且具体的AI输出

我认为生成式AI的一个新兴领域,正是能够在解决特定应用或问题的背景下,与你的数据进行这类对话的能力。


本节课中,我们一起学习了如何激活并使用Google Bard的扩展功能,特别是与Google Workspace的集成,来分析和总结个人文档,并基于此上下文获取新的代码创意和课程设计灵感。关键在于结合个性化数据与AI能力,实现更精准、高效的辅助。

138:通过Bard探索Google Colab 🧪

在本节课中,我们将学习如何利用Google Bard与Google Colab的集成功能来辅助数据科学工作。我们将看到Bard如何生成代码,以及如何在实际操作中验证和调整这些生成的代码,以应对可能出现的“幻觉”问题。


Google Bard的一个实用功能超越了简单的摘要或传统的生成式AI技术。它与Google Colab有集成。我们可以利用它来帮助我们开始进行数据科学工作。

为了做到这一点,我将输入:“help me create a colab notebook for exploring life expectancy”。

现在,我们看到它将告诉我具体的操作指令:前往Google Colab,点击新建笔记本,为笔记本命名。完成这些后,我们可以将Bard生成的信息粘贴进去。这里展示了一个如何创建此类笔记本的示例。

现在,一个有趣的问题是:这些信息真的存在吗?这是我们接下来可以探索的事情之一。

让我们继续尝试,并将其放入一个笔记本中。


我们前往Colab,点击“文件”->“新建笔记本”。

然后,我将粘贴Bard生成的代码。它写道:“import life expectancy”。首先,我可能需要确认这个数据集是否真的存在。

因此,我们可以回到Bard,粘贴这段代码。请注意,对于GitHub上的资源,很多时候你需要双重检查它是否真实存在。

在这个案例中,它没有生效。逻辑看起来是对的,但我们没有正确的数据集。我们可以说:“嘿,帮我找另一个数据集。”

Bard给出的那个数据集返回了404错误。

没问题,这里还有一些其他数据集。我甚至可以进一步要求:“给我几个包含预期寿命数据的GitHub URL。” 这可能是绕过一些“幻觉”问题的方法之一。我需要继续深入。

我可以要求:“给我另一个数据集。”

这次,它说在这个位置有一个数据集。让我们尝试一下。


我们在这里确实获得了一些数据。Bard之前能够索引到这个数据集,我们可以看到这个特定的数据集看起来不错。我们可以继续,点击“Raw”获取原始数据链接。

让我们继续,把这个新链接替换到我们的Colab代码中。现在,我们可以基于之前生成的代码开始构建。

当然,不能保证列名是相同的或完全匹配,但这是一个良好的开端,看看我们能否让事情运转起来。这可能是遇到的第一个问题。

那么,我们如何解决这个问题呢?我们不必盲目遵循生成式AI的输出。

我可以在这里新建一个单元格,并且不运行之前有问题的那个单元格。我会重新运行当前有效的单元格,这样我就可以将工作的部分和有问题部分分离开。

在这个案例中,我可能甚至不需要那个有问题的单元格,我们只想从这个有效的部分开始继续开发。

例如,如果我在这个单元格下面再新建一个,我可以输入 df.describe() 并运行,来描述这个特定数据集的基本统计信息。


我在这里的一个建议是:当你处理生成式AI解决方案时,不要轻易放弃。假设总会遇到一些问题,本质上要“为失败而设计”,能够剔除有问题的部分并继续向前推进。

如果你采用这种方法,就像在现实世界中一样,即使生成式AI会产生“幻觉”,它仍然是一个有用的助手。


本节课总结

本节课中,我们一起学习了如何将Google Bard作为助手,在Google Colab中启动一个数据科学项目。我们实践了从Bard获取代码建议、验证数据源的真实性、以及当遇到“幻觉”或错误时如何通过迭代和隔离问题来继续推进工作。关键点在于将AI视为协作工具,始终保持验证和调试的主动性。

139:探索Colab AI 🧠

在本节课中,我们将探索Google Colaboratory(简称Colab)及其内置的AI助手功能。我们将学习如何利用Colab提供的强大硬件加速资源,并掌握如何有效地使用其生成式AI功能来辅助编程,特别是处理代码错误和迭代开发。


概述:Colaboratory与AI助手

Colaboratory是一个著名的云端Jupyter Notebook解决方案。它提供了一些非常强大的功能。

首先,我们可以查看运行时设置。在这里,我们可以更改运行时类型。请注意,Colab内置了多种出色的硬件加速类型,包括使用高性能Tensor处理单元(TPU)、A100 NVIDIA GPU、V100 NVIDIA GPU和T4 GPU的能力。这些是我们在此获得的非常强大的功能。

现在,我们可以做的另一项新兴功能是,直接在Notebook中使用生成式AI。


激活并使用Colab AI助手

接下来,让我们尝试使用这个功能。我们可以在界面中找到“Colab AI”选项。事实上,我们可以滚动查看,甚至可以将整个环境设置为专注于生成式AI。这本质上是一个隐藏在界面内的AI助手。

我们可以尝试使用默认查询。例如,输入“如何过滤pandas数据框”,我们会看到它给出了一个很好的示例。我们也可以返回并给它更多信息。例如,我们可以说“我想要一个如何进行简单线性回归的示例”。

好的,现在我们有了一个代码示例。让我们来试试看。复制这段代码,然后转到“文件”>“新建笔记本”。我们可以直接将代码粘贴进去。如果我们运行它,我们将能够获得一个可视化结果,展示具体发生了什么。


调试与迭代:与AI协作

我们遇到了一个问题:代码期望一个2D数组,但我们得到了一个1D数组。那么,如何修复这个问题呢?让我们在这里输入错误信息,看看AI会告诉我们什么。它建议我们尝试使用reshape方法将数组重塑为(-1, 1)。让我们试试看。

我们回到代码这里。我们可以修改数组部分。我们可以输入x = ...y = ...。注意,它甚至帮我自动补全了代码。这能解决我们的问题吗?哦,我们遇到了另一个问题:它提示“没有‘reshape’属性”。

那么,让我们把这个问题反馈给AI助手。看起来AI在将函数或方法当作数组使用。我可以回到AI助手这里,直接说“也许整个示例有问题”,或者“你能帮我修复这个吗?”。

AI给出了修正后的代码。好的,让我们把它粘贴回去,看看会发生什么。成功了!现在我们可以看到,线性回归的可视化结果正确显示了。


核心策略:有效使用AI助手的技巧

从这个过程中,你很容易被诱惑去盲目接受AI给出的答案,然后在它们不工作时就放弃。但就像处理真实的编程问题一样,使用生成式AI的一种方法是:假设AI生成的结果在某个频率下(比如25%的时间)总会存在问题。

现在的问题是,即使它没有给出完全正确的答案,你该如何继续推进?实现这一点的方法之一是缩小范围,并将错误追踪信息反馈给AI助手,持续向前推进。这样,你就能将问题二分为正确的部分和错误的部分。

这实际上是传统的调试思维,只是我们有了一个能提供大量帮助的新高级工具。通过Colab AI,你可以看到一个成功使用它的重要策略。


总结

本节课中,我们一起探索了Google Colab及其内置的AI助手功能。我们学习了如何利用Colab的硬件加速资源,并通过一个线性回归的代码示例,实践了如何与AI助手协作来调试和修复代码。关键在于理解AI辅助编程是一个迭代过程,需要你主动提出问题、反馈错误并引导对话,才能高效地解决问题。

140:探索Gen App Builder 🧠

在本节课中,我们将学习生成式AI领域的一项新兴技术:如何利用生成式AI来更快地构建应用程序。我们将以Google Cloud的Gen App Builder为例,了解其核心组件和功能。


概述

生成式AI领域的一项新兴技术是利用生成式AI来更快地构建应用程序。

核心组件

如果我们查看Google Cloud的Gen App Builder,可以看到这里有类型配置和数据。这是使用生成式AI构建应用程序的三个组成部分。

我们拥有搜索、聊天和推荐功能。

搜索功能

首先,我们来看看搜索功能。可以看到,您能够使用企业版。例如,您可以进行提取式回答。您也可以使用高级LLM功能,例如搜索摘要和带后续问题的搜索。这是在构建应用程序时,利用现成的LLM技术的一个强大功能。

聊天功能

接下来,我们看看可以构建的另一个功能:聊天。让我们来看看它是如何工作的。

如果我们进入聊天界面,我将能够构建一个新的应用程序,该应用程序使用聊天功能,并且我们可以使用Dialogflow API。在这个例子中,我们将其称为聊天机器人。

对于公司名称,我们称之为Pretend Co。对于数据存储,我们将选择数据源。我们可以选择网站URL,以便自动从列出的域名爬取网站内容。

让我们选择一个我拥有的域名,例如PL.com。然后我们点击继续,并将其命名为PIML。

构建过程

此时,我们将能够选择这个作为数据源,这同样是一个自定义数据源。然后,将构建一个基于LLM的聊天应用程序,这实际上只需要低代码甚至无代码技术。

优势

使用这类工具的优势在于,它将先进的大型语言模型技术的力量交到了开发者手中。这些开发者可能没有时间完全开发一个应用程序,或者不具备传统的软件开发技能。只要他们了解要搜索的上下文,他们就能非常快速地构建出应用程序。


总结

本节课中,我们一起学习了如何利用生成式AI,特别是通过Google Cloud的Gen App Builder这样的工具,来快速构建具备搜索、聊天等功能的应用程序。这项技术降低了开发门槛,让更多人能够高效地利用先进的AI能力。

141:基于AWS Bedrock的责任AI

概述

在本节课中,我们将深入学习AWS Bedrock,这是一个用于构建和部署负责任AI系统的核心工具集。我们将探讨其重要性、核心支柱、支持服务以及实际应用场景。


为什么需要负责任AI?🤔

上一节我们介绍了AWS Bedrock的基本概念,本节中我们来看看为什么构建负责任的AI系统如此重要。

考虑在刑事司法、医疗保健或版权法领域使用带有偏见的AI系统可能带来的严重后果,其风险极高。因此,确保AI的公平、透明和可靠至关重要。


AWS Bedrock的七大核心支柱 🏛️

AWS Bedrock不仅仅是一个工具,更是一种理念,它体现在以下七个关键支柱中。以下是这些支柱的详细介绍:

  1. 教育
    知识就是力量。确保团队接受培训,了解如何避免AI中的偏见。

  2. 多样性
    多元化的视角有助于我们发现可能被忽视的问题。

  3. 用例评估
    每个工具都有其正确和错误的使用方式,关键在于进行评估。

  4. 高质量数据
    “垃圾进,垃圾出”的思维模式至关重要,高质量、有代表性的数据是基础。

  5. 偏见测试
    持续进行偏见测试,以确保公平性。

  6. 人工审查
    在审查环节,人类的判断无可替代。

  7. 监控与再训练
    这不是一项一劳永逸的操作,意味着定期检查必不可少。


支持核心支柱的关键服务 ⚙️

了解了核心支柱后,我们来看看AWS提供哪些强大的服务来支持这些理念的实现。

  • Amazon SageMaker Clarify
    这项服务用于分析和解释模型的偏见,就像一个监督这些实践执行的“看门狗”。

  • Amazon Augmented AI (A2I)
    这项服务旨在促进“人在回路”的审查流程。

  • Amazon SageMaker Model Monitor
    这项服务如同模型的质量保证经理,持续监控模型性能。

  • Amazon SageMaker Data Wrangler
    这是进行数据准备和分析的首选工具。

这些服务共同构成了一个强大的、用于伦理AI开发的框架。


AWS Bedrock的实际应用场景 🌍

理论需要联系实际。那么,Bedrock在现实世界中是如何应用的呢?

  • 金融服务领域,它支撑着无偏见的贷款和风险模型。
  • 人力资源领域,它有助于消除简历筛选中的偏见。
  • 内容审核领域,它确保能够可靠地捕捉违反政策的内容。

在当今的AI环境中,信任的重要性怎么强调都不为过。


选择AWS平台的三大理由 ✅

为什么亚马逊是实施Bedrock理念的理想平台?主要有以下三个原因:

  • 全面的机器学习服务
    例如Amazon SageMaker,它涵盖了机器学习项目的完整生命周期。

  • 强大的工具集
    AWS拥有目前最全面的负责任AI工具集。

  • 专业的知识与支持
    AWS在AI/ML技术方面拥有丰富的知识宝藏,并愿意通过专家咨询等方式进行分享。


如何开始您的负责任AI之旅 🚀

最后,如果您想立即开始您的负责任AI实践,可以遵循以下步骤:

  1. 首先,阅读并理解AWS提供的全面Bedrock白皮书。
  2. 接着,访问AWS官网,查看“负责任AI”部分,进一步探索相关服务。
  3. 最后,联系AWS专家,他们可以为您的项目采用Bedrock提供量身定制的指导。

借助AWS Bedrock,合乎伦理的AI不仅是一种可能,更是一种保障。


总结

本节课中,我们一起学习了AWS Bedrock作为负责任AI框架的重要性。我们探讨了其七大核心支柱、支持这些支柱的具体AWS服务、Bedrock在多个行业的实际应用,以及选择AWS平台的优势。最后,我们了解了开始实践负责任AI的具体步骤。希望本教程能帮助您构建更公平、更可靠的AI系统。

142:AWS Bedrock与Claude模型 🎼

在本节课中,我们将要学习AWS Bedrock平台与Anthropic公司开发的Claude模型。我们将探讨Claude模型的核心概念、工作原理、能力特点,以及它如何与AWS Bedrock的负责任AI原则相结合。

概述:什么是宪法AI?

上一节我们介绍了课程主题,本节中我们来看看Claude模型背后的核心创新理念——宪法AI。

宪法AI是一种创新的方法,其核心关注点是安全性、诚实性和有益性。这与传统模型不同,其核心特质不仅仅是模型准确性,还包括确保系统安全性的具体措施。该方法从宪法法律中汲取灵感,旨在编程使AI避免暴力行为、避免盗窃、避免诽谤等。

这项技术之所以重要,是因为它包含了自我监督和基于价值的学习,这些构成了模型的基础,并融入了这项特定技术中。

Claude模型的命名与设计理念

了解了宪法AI的概念后,我们来看看Claude模型本身。首先,它为何被命名为Claude?

Claude被设计用于进行自然语言对话。这意味着它能提供响应迅速、有益且无害的交互。它不会说谎、不会诽谤、也不会评判用户。这在AI伦理方面是一大进步。此外,Claude能够基于现实世界的反馈持续改进。想象一下,拥有一个不仅能理解你意图,还能随着时间推移学会更好地协助你的AI助手。

Claude系统的工作原理

认识了Claude的设计目标后,本节我们来深入了解其内部工作机制。

首先,其核心秘诀在于对来自互联网的海量数据集进行自我监督学习,并且这些数据是以符合伦理的方式筛选的。但关键在于,其训练方向是面向人类价值和社会规范的,并且这是一个永不停歇的循环,会持续吸收人类反馈。本质上,这个AI将随着社会自身的演变而进化。

这是与其他技术平台的关键区别,因为它从设计之初就旨在真正有益于社会。

Claude的核心能力

理解了工作原理后,我们来看看Claude具体能做些什么。以下是Claude的一些关键能力:

  • 总结、任务跟踪与事实核查:例如,你可以给Claude一份商业报告或测验,要求它进行事实核查,以验证其中所有细节的准确性。
  • 文档总结:例如,你可以上传一份多页的PDF文档,要求Claude提取前三个关键点,以便在会议中进行总结。这能大大节省时间,让你更快地消化更多内容。
  • 个性化对话与推荐:如果正确设置了上下文窗口,你可以进行基于关键信息的深度对话。
  • 广泛的技能范围:它能够回答从技术话题、医学话题到当前新闻事件等各种主题的问题。

负责任AI与AWS Bedrock的整合

上一节我们介绍了Claude的能力,本节中我们来看看它如何与AWS的平台结合,并体现负责任AI的原则。

Anthropic的负责任AI方法与AWS Bedrock的负责任AI支柱相一致,它充当了防范风险(如不安全行为和算法偏见)的保障。这意味着你使用的不仅是一项智能技术,更是一项符合伦理的智能技术。随着大语言模型领域的竞争加剧,伦理基础将成为区分不同平台的关键因素之一。如果你能切实展示伦理考量、表明你减少了偏见、确保了安全性,你就能建立消费者的信任,并且不会发布可能对人类造成广泛危害的产品。这实际上可以成为一种竞争优势。

通过有效地将其作为公司声誉的基石,你将比那些不太可能将此视为核心特质的公司更具优势。这也是发展伦理AI的原因之一——它是一种竞争优势,而非劣势。

释放Claude负责任AI潜力的关键细节

了解了整合的重要性后,我们来具体看看如何利用Claude实现负责任AI。以下是关键细节:

  • 支持AWS Bedrock支柱:Anthropic系统支持AWS Bedrock的负责任AI支柱,这是将其用作竞争优势的关键原因之一。
  • 模型定制:你可以使用AWS技术(例如SageMaker)根据需求定制Claude模型。这种定制允许你针对特定的使用场景进行专业化调整。
  • 宪法AI与无害训练:我们知道,在数据集、模型及其训练方式上投入了大量思考,以确保它们对社会产生真正积极的影响。
  • 适用领域:在具体用例方面,它适用于对话、内容创作、推理和创造力等领域,是一个非常适合多种直接适用于商业的领域的模型。
  • 风险规避:它能够避免偏见或不安全行为等风险。短期内这可能看似不重要,但长期来看,如果不考虑这些方面,可能会导致大规模的声誉损害。因此,你能在未来问题发生之前就与之隔离,这是使用Claude这类模型的巨大战略优势。
  • 版本多样性:Claude有多个不同版本,例如功能最强大、最新的Claude 2,以及版本1.3和Claude Instant。这些版本提供了不同的特性,例如在速度、性能与准确性之间进行优化。

总结

本节课中,我们一起学习了AWS Bedrock与Anthropic的Claude模型。我们探讨了其背后的宪法AI理念、模型的设计目标与工作原理,以及其强大的总结、对话和跨领域问答能力。最重要的是,我们了解了Claude如何通过与AWS Bedrock负责任AI原则的深度整合,将伦理和安全作为核心竞争优势,为开发者提供了既强大又可靠的大语言模型选择。

143:使用Claude进行文本摘要

在本节课中,我们将学习如何利用Claude AI助手进行文本摘要与分析。我们将通过一个具体的例子——分析海明威的《老人与海》——来演示如何上传文档、请求摘要、提取关键信息,并最终结合AI的能力与我们自己的想法,生成新的内容。

概述与工具介绍

上一节我们介绍了AI助手的基本概念,本节中我们来看看如何具体应用。这里我们使用的是Anthropic公司开发的Claude,它是一个通用的AI助手。我们可以用它来处理和分析文本,例如,针对海明威的著名小说《老人与海》的文本内容提出问题。

上传文档与请求摘要

以下是使用Claude进行文本处理的第一步:上传文档。

  1. 首先,找到《老人与海》的文本内容。
  2. 将文本全部复制。
  3. 在Claude的界面中,可以直接粘贴文本。Claude能够自动识别并将其处理为一个文本文件。

这种技术的一个显著优势是能够处理上传的文档。这比单纯的聊天功能是一个重要的增强,因为你可以为AI提供更丰富的上下文信息,并且能够处理不同格式和风格的数据,而不仅仅局限于聊天界面。

接下来,我们可以请求AI对这段文本进行摘要。我们可以输入指令:“summarize this book, in a small, concise paragraph.”(用一小段简洁的文字总结这本书。)

分析摘要结果

Claude生成的摘要如下:

《老人与海》讲述了一位年迈的古巴渔夫在连续84天没有捕到鱼之后,终于钓到一条巨大的马林鱼。他与这条鱼搏斗了三天并将其杀死。在返回岸边的途中,鲨鱼袭击并吞噬了马林鱼,只留下它的骨架。尽管老人没有带回战利品,但他在整个磨难中表现出的坚韧不拔,彰显了他虽败犹荣的英雄气概。

这个摘要非常精准,展示了AI在总结归纳这类任务上的强大能力。我们甚至可以进一步要求它以更精炼的形式提取关键信息。

提取关键信息与互动

我们可以继续与AI互动,要求它提供更具体的信息。例如,输入指令:“give me three key lessons, and only use one word for the lessons.”(给我三个关键的启示,每个启示只用一个词。)

与AI助手互动的最佳方式之一,就是充分利用它最擅长的功能,比如总结归纳和快速阅读大量文本。这可以作为一种头脑风暴的工具,或者说,在处理生成式AI时,它就像一个协作伙伴。

AI返回了三个词:perseverance(坚持不懈)、fortitude(坚毅)、resilience(韧性)。

结合创意生成新内容

基于从AI获得的信息,我们可以进一步发挥自己的创意,要求AI进行扩展。例如,我们可以输入指令:“write a small synopsis based on sports that is at most three lines and discusses these traits in an American high jumper who goes to the Olympics.”(基于体育写一个不超过三行的简短剧情概要,描述一位参加奥运会的美国跳高运动员如何体现这些特质。)

将AI助手最擅长的能力与我们自己的生活经验和想法结合起来,产生协同效应,是使用这些AI助手的最佳方式之一。当然,我们也可以通过API或在云平台上实现这些功能,将基于云的生成式AI机器学习解决方案与我们现有的基础设施、用户界面和命令行工具相结合,从而开发新兴技术。

AI生成的概要如下:

一位多年来始终无法跳过七英尺高度的跳高运动员,经过不懈训练终于成功,并进军奥运会。在奥运赛场上,他首次试跳失败,但拒绝放弃,最终越过横杆赢得金牌。他的坚持与韧性闪耀全场。

总结

本节课中我们一起学习了如何使用Claude AI助手进行文本处理。我们演示了从上传文档、获取摘要、提取关键词到结合自身想法生成新内容的完整流程。这个过程表明,生成式AI并非魔法,而是一个我们可以用来构建新解决方案的有用工具。通过有效的反馈循环,我们可以更好地理解生成式AI的原理与应用方式。

144:使用AWS Bedrock API 🚀

在本节课中,我们将探讨AWS Bedrock API,这是一个允许开发者灵活使用多种大型语言模型(LLM)的服务。我们将了解其核心功能,包括如何查询不同公司的模型、如何微调自定义模型,以及如何在实际应用中调用这些模型。


概述

AWS Bedrock API为LLM操作(LLMOps)提供了强大的支持。它允许开发者构建能够访问多种大型语言模型的应用程序,而不仅仅依赖于单一公司的模型。这为应用开发带来了前所未有的灵活性和选择空间。

核心功能

上一节我们概述了Bedrock API的价值,本节中我们来看看它的具体功能。

1. 多模型查询

AWS Bedrock API的核心优势之一是能够访问来自不同公司的多种基础模型。这意味着你的应用程序可以集成一个多样化的模型生态系统。

以下是其工作方式:

  • 你的应用程序(例如一个移动应用)可以向Bedrock API发起查询。
  • API可以访问来自五、六家不同公司提供的基础模型。
  • 你的应用程序甚至可以将这些模型选择呈现给最终用户,让他们自行决定使用哪个模型。

2. 自定义模型微调

除了使用现成的基础模型,你还可以基于Bedrock系统中的这些模型,创建和微调属于你自己的定制化模型。

以下是你可以实现的目标:

  • 基于Bedrock中的基础模型构建你自己的定制模型。
  • 为用户提供高度定制化的体验。
  • 构建仅供企业内部使用的应用程序,确保只使用你自己的数据进行微调,模型完全为你所有。

3. 提示与模型调用

对于最终用户而言,他们可以通过简单的操作来利用这些强大的模型。

用户的核心操作流程如下:

  • 用户提供提示(prompt)。
  • 应用程序调用(invoke)所选模型来处理该提示。

未来展望与总结

本节课中我们一起学习了AWS Bedrock API的关键特性。我们看到,大型语言模型的未来并非盲目接受由单一公司主导的、受严格监管的AI。

未来的趋势将是:

  • 竞争涌现:不同公司和模型之间将展开竞争。
  • 能力多样化:各种模型将发展出不同的专长和能力。

像AWS这样的云服务提供商将成为一个绝佳的选择,原因在于:

  • 你不仅可以从众多大型语言模型中进行选择。
  • 还可以使用你自己微调的模型

因此,AWS Bedrock API是一个非常值得关注和尝试的新特性,它为构建下一代AI应用打开了大门。

145:AWS CodeWhisperer实时编码(第一部分)🚀

概述

在本节课中,我们将学习如何结合使用AWS CodeWhisperer和Cargo Lambda工具,在本地Visual Studio Code环境中进行实时编码,以创建和部署运行在AWS上的Rust函数。

开始使用

首先,我打开了名为“简单浏览器”的工具。你可以从命令面板中找到它。输入“website”即可打开。这个工具的好处是可以在编码时查看文档,同时将所有内容保持在同一个屏幕上。

接下来,我们转到CDK CodeWhisperer。你可以看到这些都是AWS开发者工具集的一部分。我们可以看到CodeWhisperer实际上已经启用。

设置Cargo Lambda

我首先要做的是查看入门指南,确保所有功能都能正常工作。我们在本地进行设置,因为我运行的是OS 10系统,不过这个工具在Linux上也能运行。

以下是设置步骤:

  1. 输入 brew tap cargo-lambda/cargo-lambda 命令。
  2. 我们看到它已经存在。
  3. 如果输入 brew install cargo-lambda,它不仅会检查是否已安装,还会在需要时进行更新。我已经全部设置好了,所以不需要做任何操作。

创建Lambda项目

现在我们已经有了Cargo Lambda,我们可以用它来创建一个新的Lambda项目。输入以下命令:

cargo lambda new lambda_project

然后进入该目录:

cd lambda_project

我将这个项目命名为“S3 Bucket Lister”。创建过程中,系统会询问一些问题。例如,这个函数是否是HTTP函数?我选择“否”。然后,我选择不接收任何事件。这样,我们就有了一个良好的起点。

查看项目代码

进入项目目录后,我需要查看其中的代码。在主函数中,我们可以看到一些文档注释。为了缩短代码长度,便于在屏幕上完整显示,我将删除这些注释。

以下是代码的主要部分:

  1. 请求和响应部分,这是Lambda函数的标准组成部分。
  2. 函数处理程序的主体部分,负责执行所有工作。
  3. 一些样板代码,你不需要过多担心。

为了保持代码紧凑,我还会删除其他注释。

使用Makefile

接下来,我们想使用Makefile。我可能在其他地方有一个现成的Makefile。我会快速从另一个项目中复制一个过来。

操作步骤如下:

  1. 转到我的另一个项目。
  2. 快速复制一个Makefile。
  3. 将其粘贴到当前项目中。

然后,我使用 touch 命令创建一个新文件。现在,我已经有了Makefile,接下来可以查看其中可用的不同功能。

例如,运行 make release-version 命令来检查一切是否正常工作。但首先,我需要确保Makefile保存在正确的位置。保存后,再次运行命令,一切就正常工作了。

Makefile中还有一些其他命令,如测试等。回到项目,我们可以看到接下来的步骤。例如,运行 cargo lambda watch 命令,这将编译代码并实时监控变化。

总结

本节课中,我们一起学习了如何在本地Visual Studio Code环境中,结合AWS CodeWhisperer和Cargo Lambda工具进行实时编码。我们设置了开发环境,创建了一个新的Lambda项目,查看了项目代码结构,并引入了Makefile来管理构建和监控过程。这为后续在AWS上部署和运行Rust函数奠定了基础。

146:AWS CodeWhisperer实时编码(第二部分)🎯

概述

在本节课中,我们将学习如何使用AWS CodeWhisperer辅助编写一个更复杂的AWS Lambda函数。我们将扩展之前的基础函数,使其能够与AWS S3服务交互,具体实现列出账户中所有S3存储桶的功能。我们将逐步介绍如何添加依赖、编写业务逻辑以及处理异步编程模型。

本地测试环境

上一节我们介绍了如何设置和运行一个基础的Lambda函数。本节中我们来看看如何利用现有的本地测试环境。

视频中展示了一个内置的简单浏览器界面,用于查看Lambda函数的输出,无需切换到其他应用程序。

接下来,测试功能会启动。由于代码尚未修改,测试应该能通过。

测试会发送一个特定的负载(payload):

{"command": "echo", "data": "Hello"}

为了执行测试,我们需要在终端中运行命令。以下是操作步骤:

  1. 打开一个分屏终端。
  2. 手动输入测试命令(因为视频中复制粘贴功能出现了问题):
    cargo lambda invoke --data-ascii '{"command":"echo","data":"Hello"}'
    
  3. 命令成功执行,输出结果符合预期,证明本地Lambda函数运行正常。

扩展函数功能:集成AWS SDK

目前,我们的函数功能比较简单。现在,让我们让它做一些更有趣的事情,例如与AWS服务交互。这正是CodeWhisperer可以大显身手的地方。

一个非常实用的Lambda函数用例是结合AWS SDK调用其他服务。我们将使用Rust版的AWS SDK。

首先,我们需要为项目添加必要的依赖。查看已有的S3列表示例,可以发现其Cargo.toml文件中包含了aws-configaws-sdk-s3

因此,我们需要在当前项目的依赖项中添加这两项。原始的Cargo.tomlcargo-lambda工具生成,已包含lambda_runtimetokio等。我们手动添加以下两行:

[dependencies]
aws-config = "0.56.1"
aws-sdk-s3 = "0.28.1"

添加后,可以运行cargo build来验证依赖是否正确解析并能被编译。

使用CodeWhisperer编写业务逻辑

接下来,我们将编写一个列出所有S3存储桶的辅助函数。我们将尝试使用CodeWhisperer来生成这部分代码。

我们计划创建一个名为list_buckets_in_account的函数。在代码编辑器中,我们输入函数签名和简单的描述作为提示:

// 使用AWS SDK for S3列出存储桶,并返回一个字符串向量
fn list_buckets_in_account

CodeWhisperer开始提供建议。它生成的代码框架大致如下:

async fn list_buckets_in_account() -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let shared_config = aws_config::load_from_env().await;
    let client = aws_sdk_s3::Client::new(&shared_config);
    let resp = client.list_buckets().send().await?;

    let mut buckets = Vec::new();
    for bucket in resp.buckets() {
        buckets.push(bucket.name().unwrap_or("").to_string());
    }
    println!("{:?}", buckets);
    Ok(buckets)
}

这段代码基本符合我们的需求:加载配置、创建S3客户端、调用API、将结果收集到向量中并返回。CodeWhisperer还自动处理了异步(async)和错误处理(Result)。

调试与集成

生成的代码并非一帆风顺,我们遇到了一些需要手动修复的编译错误:

  1. await 关键字错误:提示await只允许在async函数中使用。这提醒我们需要将函数标记为async,我们已照做。
  2. 未找到 unwrap 方法:错误提示resp上没有unwrap方法。这是因为client.list_buckets().send()返回的是一个Future,需要.await来获取结果,我们已经使用了await?
  3. 值移动(move)错误:提示“use of moved value”。这通常发生在尝试在循环中使用一个已经被消耗的所有权时。解决方案是获取resp.buckets()的引用,例如使用.iter()或直接迭代。CodeWhisperer生成的for bucket in resp.buckets()实际上已经正确处理了借用。

修复错误后,我们得到了一个可以编译的辅助函数。但由于尚未在主处理函数中调用它,编译器会提示“dead code”警告。

修改主处理器

现在,我们需要在Lambda函数的主处理器(func)中调用这个新的辅助函数。

我们暂时保留空的负载处理逻辑,然后调用list_buckets_in_account函数。我们需要处理其返回的Result类型,并将得到的存储桶列表转换为Lambda响应。

我们尝试让CodeWhisperer帮助完成这部分集成。我们给出提示:“在响应中以字符串形式返回桶列表”。

经过一些调整,最终的主函数逻辑可能如下:

async fn func(event: LambdaEvent<Value>) -> Result<Response, Error> {
    // 调用辅助函数列出存储桶
    let buckets_result = list_buckets_in_account().await;

    let message = match buckets_result {
        Ok(buckets) => {
            // 将桶列表连接成一个字符串
            buckets.join(", ")
        }
        Err(e) => {
            format!("Failed to list buckets: {}", e)
        }
    };

    // 构建并返回Lambda响应
    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body(message.into())
        .map_err(Box::new)?;
    Ok(resp)
}

在这个过程中,我们可能会遇到类型不匹配的错误(例如,期望的返回类型是Result<Response, Error>,但函数返回的是其他类型)。为了快速验证功能,我们可以采用一个更简单的方法:暂时不修改返回结构,而是在主函数中直接打印出存储桶列表。

最终测试

完成代码编写和初步修复后,我们进行最终测试。

  1. 首先运行代码格式化工具保持代码整洁:
    cargo fmt
    
  2. 启动cargo lambda watch模式,它会监控代码变化并自动重新构建。
  3. 构建成功后,在另一个终端使用之前的命令调用这个Lambda函数:
    cargo lambda invoke --data-ascii '{}'
    
  4. 函数成功执行,并在输出中列出了AWS账户中的所有S3存储桶名称。

总结

本节课中我们一起学习了如何利用AWS CodeWhisperer加速开发一个集成了AWS服务的Rust Lambda函数。我们经历了以下步骤:

  1. 环境确认:在本地成功测试了基础Lambda函数。
  2. 添加依赖:在Cargo.toml中添加了aws-configaws-sdk-s3以使用AWS服务。
  3. AI辅助编码:使用CodeWhisperer生成与S3交互、列出存储桶的核心业务逻辑代码。
  4. 调试与集成:手动修复了异步标记、所有权等常见的Rust编译错误,并将新功能集成到主Lambda处理器中。
  5. 功能验证:最终通过本地调用,验证了Lambda函数能够成功查询并返回S3存储桶列表。

对于一门我仍在熟悉中的语言(Rust)和一个仍在学习的框架(Lambda),CodeWhisperer提供了相当不错的开发体验,它能有效帮助生成样板代码和解决常见模式,让开发者能更专注于业务逻辑。

147:AWS CodeWhisperer实时编码(第三部分)

在本节课中,我们将学习如何将一个使用AWS CodeWhisperer辅助编写的Rust Lambda函数,进行构建、部署到AWS平台,并进行远程调用测试。我们将重点关注发布构建、跨平台编译以及Lambda权限配置等关键步骤。

构建发布版本

上一节我们完成了Lambda函数的代码编写,本节中我们来看看如何将其构建为可部署的版本。

首先,我们需要使用Cargo Lambda工具构建一个发布版本。发布版本经过优化,更适合在生产环境中运行。

以下是构建命令:

cargo lambda build --release

此命令会生成一个针对默认平台架构的优化二进制文件。

跨平台编译以节省成本

构建完成后,我们可以考虑进行跨平台编译。AWS Graviton处理器(基于ARM64架构)通常比同性能的x86实例成本更低。

为了针对ARM64架构(Graviton)进行编译,我们需要先清理之前的构建产物,然后指定目标平台。

以下是相关步骤:

  1. 清理构建目录:rm -rf target
  2. 针对ARM64架构构建:cargo lambda build --release --arm64

部署到AWS Lambda

构建成功后,下一步是将函数部署到AWS Lambda。

部署过程非常简单,只需一条命令:

cargo lambda deploy

由于部署的是编译好的二进制文件,这个过程通常非常快速。部署完成后,Lambda函数会被创建在您的AWS账户中。

配置Lambda函数权限

部署后尝试远程调用函数时,可能会遇到权限错误。这是因为新创建的Lambda函数默认的执行角色可能没有访问其他AWS服务(如S3)的权限。

为了解决这个问题,我们需要为Lambda函数的执行角色添加必要的权限策略。

以下是配置权限的途径:

  • 通过AWS管理控制台,导航到该Lambda函数的“配置”->“权限”选项卡,编辑其执行角色,附加所需的策略(例如AmazonS3ReadOnlyAccess)。
  • 如果使用基础设施即代码工具(如AWS CDK),可以在代码中定义权限。

测试与调用Lambda函数

权限配置完成后,就可以测试Lambda函数了。

有多种方式可以调用已部署的Lambda函数:

  • 使用cargo lambda invoke命令进行远程调用。
  • 直接在AWS管理控制台的Lambda函数界面中,使用“测试”功能创建并执行测试事件。
  • 通过AWS CLI或其他SDK进行调用。

在控制台测试时,您可以即时看到函数执行的日志输出和返回结果,便于调试。

代码管理与后续计划

功能测试通过后,最后一步是将本项目的代码提交到版本控制系统(如Git)进行管理。

以下是常用的Git命令序列:

git status
git add .
git commit -m "添加Lambda函数演示"

本节课中我们一起学习了使用Rust和Cargo Lambda工具进行函数构建、针对ARM架构的跨平台编译、部署到AWS Lambda、配置权限以及远程测试的完整流程。整个过程展示了如何高效地开发和部署无服务器函数。基于此次成功的尝试,后续可以探索构建更复杂的Lambda函数应用。

148:使用AWS CodeWhisperer命令行工具

在本节课中,我们将学习如何在Shell环境中使用AWS CodeWhisperer命令行工具。这个工具能够根据自然语言描述生成相应的Shell命令,不仅能即时解决问题,还能帮助我们学习和积累知识。

概述

AWS CodeWhisperer是一个强大的AI编程助手,它不仅能帮助编写代码,还能在命令行环境中根据你的自然语言描述生成Shell命令。这对于快速执行复杂查询、学习新命令以及提高工作效率非常有帮助。

在Shell中使用CodeWhisperer

上一节我们介绍了CodeWhisperer的基本概念,本节中我们来看看如何在Mac系统的Shell环境中实际使用它。

首先,打开你的终端并进入一个包含源代码的目录。你可以通过输入自然语言问题来获取命令建议。例如,输入一个以#开头的描述。

# 统计当前目录下所有文件的数量(递归)

按下回车后,CodeWhisperer会生成一个命令建议,例如使用findwc命令的组合。你可以选择执行这个命令,它会递归地计算当前目录下的文件总数。

find . -type f | wc -l

执行后,你可能会看到类似“256000”的输出,这表示你的目录中有大量文件。

将命令保存为别名

生成命令不仅是为了即时使用,更是一个绝佳的学习机会。你可以将这些复杂的命令保存为Shell别名,方便日后调用。

以下是创建别名的方法:

  1. 打开你的Shell配置文件(例如Zsh用户的~/.zshrc)。
  2. 添加别名行,例如:alias countfiles='find . -type f | wc -l'
  3. 保存文件并执行source ~/.zshrc使别名生效。

通过这种方式,你可以逐步建立一个个性化的命令库,将学到的知识固化下来。

使用CWAI进行长格式提问

除了使用#前缀,你还可以使用cwai命令进行更详细的长格式提问。这种方式允许你提出更复杂、更具体的问题。

例如,你可以输入:

cwai 查找所有CPU使用率超过10%的进程

CodeWhisperer会分析你的请求,并生成一个相应的ps命令组合来列出符合条件的进程。

ps aux | awk '$3 > 10 {print $0}'

执行这个命令,你就能看到当前系统中所有CPU使用率超过10%的进程列表。这替代了以往需要去搜索引擎或技术论坛查找命令的步骤,极大地提升了效率。

总结

本节课中我们一起学习了AWS CodeWhisperer命令行工具的核心用法。我们了解到,它可以通过#前缀或cwai命令,将自然语言问题转化为可执行的Shell命令。这不仅是一个高效的即时助手,更能通过将生成的命令保存为别名,帮助我们系统地学习和积累命令行知识,从而持续提升工作效率。

posted @ 2026-03-29 09:35  布客飞龙I  阅读(0)  评论(0)    收藏  举报