AI编程基础
使用Python中的fastapi构建AI服务
使用 FastAPI 构建一个 AI 服务是一个非常强大和灵活的解决方案。FastAPI 是一个快速的、基于 Python 的 Web 框架,特别适合构建 API 和处理异步请求。它具有类型提示、自动生成文档等特性,非常适合用于构建 AI 服务。下面是一个详细的步骤指南,我们可以从零开始构建一个简单的 AI 服务。
1. 构建基本的 FastAPI 应用
首先,我们创建一个 Python 文件(如 main.py
),在其中定义基本的 FastAPI 应用。
1 | from fastapi import FastAPI |
这段代码创建了一个基本的 FastAPI 应用,并定义了一个简单的根路径 /
,返回一个欢迎消息。
2. 引入 AI 模型
接下来,我们将引入一个简单的 AI 模型,比如一个预训练的文本分类模型。假设我们使用 Hugging Face 的 Transformers 库来加载模型。
在我们的 main.py
中加载这个模型:
1 | from fastapi import FastAPI |
在这个例子中,我们加载了一个用于情感分析的预训练模型,并定义了一个 POST 请求的端点 /predict/
。用户可以向该端点发送文本数据,服务会返回模型的预测结果。
3. 测试我们的 API
使用 Uvicorn 运行我们的 FastAPI 应用:
1 | uvicorn main:app --reload |
main:app
指定了应用所在的模块(即main.py
中的app
对象)。--reload
使服务器在代码更改时自动重新加载,适合开发环境使用。
启动服务器后,我们可以在浏览器中访问 http://127.0.0.1:8000/
查看欢迎消息,还可以向 http://127.0.0.1:8000/docs
访问自动生成的 API 文档。
4. 通过 curl 或 Postman 测试我们的 AI 服务
我们可以使用 curl
或 Postman 发送请求来测试 AI 服务。
使用 curl
示例:
1 | curl -X POST "http://127.0.0.1:8000/predict/" -H "Content-Type: application/json" -d "{\"text\":\"I love WeThinkIn!\"}" |
我们会收到类似于以下的响应:
1 | { |
5. 添加请求数据验证
为了确保输入的数据是有效的,我们可以使用 FastAPI 的 Pydantic 模型来进行数据验证。Pydantic 允许我们定义请求体的结构,并自动进行验证。
1 | from fastapi import FastAPI |
现在,POST 请求 /predict/
需要接收一个 JSON 对象,格式为:
1 | { |
如果输入数据不符合要求,FastAPI 会自动返回错误信息。
6. 异步处理(可选)
FastAPI 支持异步处理,这在处理 I/O 密集型任务时非常有用。假如我们的 AI 模型需要异步调用,我们可以使用 async
和 await
关键字:
1 | from fastapi import FastAPI |
在这个例子中,我们假设 classifier
可以使用 await
异步调用。
7. 部署我们的 FastAPI 应用
开发完成后,我们可以将应用部署到生产环境。常见的部署方法包括:
使用 Uvicorn + Gunicorn 进行生产级部署:
1
gunicorn -k uvicorn.workers.UvicornWorker main:app
部署到云平台,如 AWS、GCP、Azure 等。
使用 Docker 构建容器化应用,便于跨平台部署。
工具介绍:
Gunicorn,全称 Green Unicorn,是一个被广泛使用的 WSGI 服务器,专为处理同步 Web 请求而设计。它基于预分叉(pre-fork)工作模型,可以与多种 Web 框架如 Django、Flask 等无缝结合。
Uvicorn 是一个基于 ASGI 标准的超快速 ASGI 服务器,使用 uvloop 和 httptools 提供高性能的异步 HTTP 请求处理。它特别适合于需要高并发、低延迟的现代异步 Web 应用。
传统同步 Web 应用 (Traditional Synchronous Web Application):基于经典的 HTTP 请求-响应模型。用户的每一个操作(如点击链接、提交表单)都会向服务器发送一个同步请求。浏览器会等待服务器处理完成并返回一个完整的 HTML 页面,然后浏览器重新加载并渲染整个新页面。
现代异步 Web 应用 (Modern Asynchronous Web Application):利用 JavaScript (特别是 AJAX 技术,现在常用 Fetch API 或 Axios 等库) 在后台与服务器进行异步数据交换。浏览器发送请求后无需等待,用户可以继续与当前页面交互。当服务器返回数据(通常是 JSON 或 XML 格式)后,JavaScript 会动态地更新页面相关部分的内容,而无需重新加载整个页面。
AI服务的Python代码用PyTorch框架重写优化的过程中,有哪些方法论和注意点?
在AI行业中,不管是AIGC、传统深度学习还是自动驾驶领域,对AI服务的性能都有持续的要求,所以我们需要将AI服务中的Python代码用PyTorch框架重写优化。有以下方法论和注意点可以帮助我们提升AI服务的代码质量、性能和可维护性:
1. 方法论
1.1. 模块化设计
- 分离模型与数据处理:
- 使用
torch.nn.Module
定义模型,将模型的逻辑与数据处理逻辑分开。 - 利用 PyTorch 的
DataLoader
和Dataset
进行数据加载和批处理。
- 使用
- 函数式编程与可复用性:
- 将优化器、损失函数、学习率调度器等单独封装为独立函数或类,便于调整和测试。
1.2. 面向性能优化
张量操作优先:
- 避免循环操作,尽可能使用 PyTorch 的张量操作(Tensor operations)来实现并行计算。
混合精度训练:
- 使用
torch.cuda.amp
提升 GPU 计算效率,同时减少内存占用。
- 使用
模型加速工具:
使用
torch.jit
对模型进行脚本化(scripting)或追踪(tracing)优化。使用
torch.compile
(若适用的 PyTorch 版本支持)进一步优化模型性能。- Python 的本质: Python 是一种解释型、动态类型的语言。它的标准编译器(如 CPython)将 Python 源代码编译成字节码 (Bytecode),然后由 Python 虚拟机 (PVM) 解释执行。
- 优点: 灵活、易用、开发速度快。
- 缺点:
- 性能开销: 解释执行比直接执行编译后的机器码慢。每次操作都需要类型检查和查找。
- GIL (全局解释器锁): CPython 中的 GIL 限制了同一进程中同一时间只有一个线程能执行 Python 字节码,这使得纯 Python 代码难以利用多核 CPU 进行并行计算(尽管 I/O 密集型任务可以通过线程切换受益)。
- 不直接面向硬件优化: Python 字节码是通用的,不针对特定的 CPU 指令集或 GPU 架构进行优化。
- 深度学习的需求: 深度学习涉及大量的数值计算(矩阵乘法、卷积等),这些计算通常:
- 计算密集: 需要极高的计算性能。
- 可并行化: 非常适合在 GPU 等大规模并行处理器上运行。
- 结构化: 计算过程可以表示为一个计算图 (Computational Graph)。
- Python 的局限性: 直接用 Python 解释器执行这些大规模数值计算会非常慢,并且无法有效利用 GPU。
- PyTorch 编译器的目标: PyTorch 编译器(无论是 TorchScript 还是
torch.compile
)的目标是克服 Python 的这些局限性,为深度学习任务提供高性能执行:- 消除 Python 开销: 将计算密集的部分从 Python 解释器中“提取”出来。
- 图优化: 分析计算图,进行数学上等价但计算更高效的转换(如算子融合 - Operator Fusion,将多个操作合并成一个 GPU Kernel)。
- 硬件特定代码生成: 将优化后的图编译成针对目标硬件(CPU 的向量指令集如 AVX,或 GPU 的 CUDA Kernel)的高度优化的底层代码。
- 序列化与部署: 将模型转换为一种可以在没有 Python 依赖的环境(如 C++ 服务器、移动设备)中运行的格式。
PyTorch加速 :
TorchScript
(尤其是 Scripting 模式) 试图创建一个更完整、自包含的、静态类型的 IR,目标是直接优化和部署。FX Tracing
则提供了一种更灵活、更 Python 友好的方式来捕获计算图,主要用于程序变换 (Program Transformation),可以作为更高级编译器的前端。你可以把 FX Graph 看作是一种更现代、更易于操作的中间步骤。torch.compile
(PyTorch 2.0 的核心特性) 的出现是为了解决 TorchScript 和 FX Tracing 的局限性,提供一个更强大、更易用、性能更好的解决方案:- 易用性 (处理任意 Python 代码,最少更改):
- TorchScript (Scripting) 的痛点: 需要用户花费大量精力去修改代码,使其符合 TorchScript 支持的 Python 子集,这非常繁琐且限制了表达能力。
torch.compile
的优势: 其前端 TorchDynamo 使用了更先进的技术(如字节码分析、解释器帧评估挂钩)来捕获 PyTorch 操作图。它能够安全地处理绝大多数 Python 代码,包括许多 TorchScript 无法处理的动态特性和控制流。当遇到无法处理的代码(称为 “graph break”)时,它会自动回退 (fallback) 到标准的 Python Eager 模式执行那部分代码,然后再尝试捕获后续的代码。这意味着用户通常只需添加@torch.compile
装饰器,而无需或只需很少的代码修改。
- 性能:
- TorchScript 的局限: 虽然能提供优化,但其优化能力和后端代码生成有时不如更现代的编译器技术。
torch.compile
的优势:它使用 TorchInductor 作为主要的后端。TorchInductor 可以将捕获到的 FX Graph 编译成非常高效的底层代码:- GPU: 生成 Triton 代码。Triton 是一种用于编写高效 GPU Kernel 的 Pythonic DSL,可以实现非常高级的优化(如算子融合、内存规划)。
- CPU: 生成优化的 C++ 代码,并使用标准的 C++ 编译器编译。
- 这通常能带来比 TorchScript 更显著的性能提升。
- 灵活性与可扩展性:
torch.compile
的架构是模块化的 (Dynamo 前端 + AOTAutograd 中间处理 + 多后端),允许接入不同的编译器后端(除了 Inductor,理论上还可以接入 ONNX、TVM 等)。
- Python 的本质: Python 是一种解释型、动态类型的语言。它的标准编译器(如 CPython)将 Python 源代码编译成字节码 (Bytecode),然后由 Python 虚拟机 (PVM) 解释执行。
2. 注意点
2.1. 正确性与鲁棒性
模型初始化:
- 使用适当的权重初始化方法(如 Xavier 或 He 初始化)。
- 检查
requires_grad
属性,确保需要优化的参数被正确更新。- 打破对称性: 如果所有权重都初始化为相同的值(比如 0),那么同一层的所有神经元在训练过程中将进行完全相同的更新,无法学习到不同的特征。随机初始化打破了这种对称性。
- 避免梯度消失/爆炸: 不恰当的初始化(太大或太小的值)可能导致在前向传播或反向传播过程中,激活值或梯度变得非常大(爆炸)或非常小(消失),尤其是在深层网络中。这会严重阻碍模型的学习。
requires_grad
是torch.Tensor
的一个布尔属性。如果为True
,PyTorch 的自动求导引擎 (Autograd) 会追踪对该张量的所有操作,以便在调用.backward()
时计算梯度。如果为False
,则不会追踪操作,也不会计算梯度
梯度检查:
用
torch.autograd.gradcheck
检查梯度计算是否正确。使用
torch.autograd.gradcheck
函数来数值验证你手动计算或由 Autograd 自动计算的梯度是否正确。它通过有限差分法(finite differences)来近似计算梯度,并将其与 Autograd 计算的梯度进行比较。
数值稳定性:
对损失函数(如交叉熵)使用内置函数以避免数值问题。
在训练中加入梯度裁剪(Gradient Clipping)以防止梯度爆炸。
避免在计算过程中出现极端值(如
inf
,-inf
,NaN
)或精度损失,这些问题可能导致训练失败或模型性能下降。梯度爆炸 (Exploding Gradients)。在反向传播过程中,梯度值可能变得异常大,导致权重更新过大,模型参数跳出最优区域,甚至变成
NaN
,使训练发散。这在循环神经网络 (RNNs) 中尤其常见,但也可能发生在其他深层网络中
2.2. 性能与效率
- 数据管道优化:
- 确保
DataLoader
中的num_workers
和pin_memory
设置合理。 - 对数据预处理操作(如归一化)进行矢量化实现。
- 确保
- 批量大小调整:
- 在显存允许的情况下增大批量大小(batch size),提高 GPU 利用率。
- 避免重复计算:
- 对固定张量或权重计算结果进行缓存,避免多次重复计算。
2.3. GPU 与分布式训练
- 设备管理:
- 确保张量和模型都正确移动到 GPU 上(
to(device)
)。 - 使用
torch.nn.DataParallel
或torch.distributed
进行多卡训练。
- 确保张量和模型都正确移动到 GPU 上(
- 同步问题:
- 在分布式环境中确保梯度同步,尤其在使用自定义操作时。
2.4. 可维护性
- 文档与注释:
- 为复杂的模块和函数提供清晰的注释和文档。
- 版本兼容性:
- 检查所使用的 PyTorch 版本及其依赖库是否兼容。
2.5. 安全性与复现
- 随机种子:
- 固定随机种子以确保实验结果可复现(
torch.manual_seed
、torch.cuda.manual_seed
等)。
- 固定随机种子以确保实验结果可复现(
- 环境隔离:
- 使用虚拟环境(如 Conda 或 venv,更好的uv)管理依赖,避免版本冲突。
3. 额外工具与库
- 性能监控:
- 使用
torch.profiler
分析性能瓶颈。
- 使用
- 调试工具:
- 使用
torch.utils.checkpoint
实现高效的内存检查点功能。
- 使用
- 辅助库:
- PyTorch Lightning:提供简化的训练循环管理。
- Hydra:便于管理复杂配置。
- Hugging Face Transformers:用于自然语言处理领域的预训练模型。
在Python中,图像格式在Pytorch的Tensor格式、Numpy格式、OpenCV格式、PIL格式之间如何互相转换?
在Python中,图像格式在 PyTorch 的 Tensor 格式、Numpy 数组格式、OpenCV 格式以及 PIL 图像格式之间的转换是AI行业的常见任务。
1. 格式概览
PyTorch Tensor: PyTorch 的张量格式,形状通常为 (C,H,W) ,通道在最前(Channel-First)。
Numpy 数组: 一种通用的多维数组格式,形状通常为 (H,W,C) ,通道在最后(Channel-Last)。
- 使用 Pandas 和 NumPy 进行前期处理,它们是更适合这些特定任务的工具。它们提供了处理真实世界中各种复杂、异构数据的灵活性和强大功能,拥有成熟易用的 API,并与整个数据科学生态系统良好集成。当数据被整理成干净的、适合模型输入的数值格式后,再使用
torch.from_numpy()
将其高效地转换为 PyTorch Tensor,以便利用 PyTorch 的 GPU 加速和自动微分功能进行模型训练。
OpenCV 格式: 一种常用于计算机视觉的图像格式,通常以 Numpy 数组存储,颜色通道顺序为 BGR。
- 使用 Pandas 和 NumPy 进行前期处理,它们是更适合这些特定任务的工具。它们提供了处理真实世界中各种复杂、异构数据的灵活性和强大功能,拥有成熟易用的 API,并与整个数据科学生态系统良好集成。当数据被整理成干净的、适合模型输入的数值格式后,再使用
PIL 图像格式: Python 的图像库,格式为
PIL.Image
对象,支持 RGB 格式。使用 PIL/Pillow:
- 当你需要进行基本的图像操作,如裁剪、缩放、旋转、格式转换。
- 当你需要处理多种不常见的图像文件格式。
- 当你在 Web 框架(如 Django, Flask)中处理用户上传的图片。
- 当你需要绘制简单的文本或形状。
使用 OpenCV:
- 当你需要执行复杂的计算机视觉任务,如人脸识别、物体检测、图像分割、特征匹配。
- 当你需要处理视频流。
- 当你对性能有较高要求,需要利用 NumPy 的高效数组运算。
- 当你需要使用更高级的图像滤波和分析算法。
通道顺序: 注意 OpenCV 使用 BGR,而 PyTorch 和 PIL 使用 RGB。
形状差异: PyTorch 使用 (C,H,W) ,其他通常使用 (H,W,C) 。
归一化: Tensor 格式通常使用归一化范围 [0,1] ,而 Numpy 和 OpenCV 通常为整数范围 [0,255] 。
2. 转换方法
2.1. PyTorch Tensor <-> Numpy
Tensor 转 Numpy:
1
2
3
4import torch
tensor_image = torch.rand(3, 224, 224) # 假设形状为 (C, H, W)
numpy_image = tensor_image.permute(1, 2, 0).numpy() # 转为 (H, W, C)Numpy 转 Tensor:
1
2
3
4import numpy as np
numpy_image = np.random.rand(224, 224, 3) # 假设形状为 (H, W, C)
tensor_image = torch.from_numpy(numpy_image).permute(2, 0, 1) # 转为 (C, H, W)
2.2. Numpy <-> OpenCV
Numpy 转 OpenCV(不需要额外处理): Numpy 格式和 OpenCV 格式本质相同,只需要确认通道顺序为 BGR。
1
2numpy_image = np.random.rand(224, 224, 3) # 假设为 RGB 格式
opencv_image = numpy_image[..., ::-1] # 转为 BGR 格式OpenCV 转 Numpy:
1
2opencv_image = np.random.rand(224, 224, 3) # 假设为 BGR 格式
numpy_image = opencv_image[..., ::-1] # 转为 RGB 格式
2.3. PIL <-> Numpy
PIL 转 Numpy:
1
2
3
4
5from PIL import Image
import numpy as np
pil_image = Image.open('example.jpg') # 打开图像
numpy_image = np.array(pil_image) # 直接转换为 Numpy 数组Numpy 转 PIL:
1
2numpy_image = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8) # 假设为 RGB 格式
pil_image = Image.fromarray(numpy_image)
2.4. OpenCV <-> PIL
OpenCV 转 PIL:
1
2
3
4
5
6from PIL import Image
import cv2
opencv_image = cv2.imread('example.jpg') # BGR 格式
rgb_image = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2RGB) # 转为 RGB 格式
pil_image = Image.fromarray(rgb_image)PIL 转 OpenCV:
1
2
3pil_image = Image.open('example.jpg') # PIL 格式
numpy_image = np.array(pil_image) # 转为 Numpy 格式
opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR) # 转为 BGR 格式
2.5. PyTorch Tensor <-> PIL
Tensor 转 PIL:
1
2
3
4from torchvision.transforms import ToPILImage
tensor_image = torch.rand(3, 224, 224) # (C, H, W)
pil_image = ToPILImage()(tensor_image)PIL 转 Tensor:
1
2
3
4from torchvision.transforms import ToTensor
pil_image = Image.open('example.jpg')
tensor_image = ToTensor()(pil_image) # 转为 (C, H, W)
2.6. PyTorch Tensor <-> OpenCV
Tensor 转 OpenCV:
1
2
3
4
5
6
7import torch
import numpy as np
import cv2
tensor_image = torch.rand(3, 224, 224) # (C, H, W)
numpy_image = tensor_image.permute(1, 2, 0).numpy() # 转为 (H, W, C)
opencv_image = cv2.cvtColor((numpy_image * 255).astype(np.uint8), cv2.COLOR_RGB2BGR)OpenCV 转 Tensor:
1
2
3opencv_image = cv2.imread('example.jpg') # BGR 格式
rgb_image = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2RGB)
tensor_image = torch.from_numpy(rgb_image).permute(2, 0, 1) / 255.0 # 转为 (C, H, W)
Python中对图像进行上采样时如何抗锯齿?
在Python中进行图像上采样时,抗锯齿的核心是通过插值算法对像素间的过渡进行平滑处理。
通俗示例:用Pillow库实现抗锯齿上采样
1 | from PIL import Image |
- 无抗锯齿(如
NEAREST
插值):边缘呈明显锯齿状,像乐高积木 - 有抗锯齿(如
LANCZOS
):边缘平滑,类似手机照片放大效果
抗锯齿原理
当图像放大时,插值算法通过计算周围像素的加权平均值,填充新像素点。例如:
LANCZOS:基于sinc函数,考虑周围8x8像素区域,数学公式:
L(x)=sin(πx)sin(πx/a)π2x2/a
其中 a 为窗口大小(通常取3)。
领域应用案例
1. AIGC(生成式AI)
案例:Stable Diffusion图像超分辨率
- 问题:直接生成高分辨率图像计算成本高(如1024x1024需16GB显存)
- 解决方案:
- 先生成512x512的低分辨率图像
- 使用LANCZOS上采样到1024x1024(抗锯齿保边缘)
- 通过轻量级细化网络(如ESRGAN)增强细节
- 优势:节省50%计算资源,同时保持图像质量
2. 传统深度学习
案例:医学影像病灶分割
- 问题:CT扫描原始分辨率低(256x256),小病灶难以识别
- 解决方案:
- 预处理时用双三次插值上采样到512x512(抗锯齿保留组织边界)
- 输入U-Net模型进行像素级分割
- 效果:肝肿瘤分割Dice系数提升12%(数据来源:MICCAI 2022)
3. 自动驾驶
案例:车载摄像头目标检测
- 问题:远距离车辆在图像中仅占20x20像素,直接检测易漏判
- 解决方案:
- 对ROI区域进行4倍双线性上采样(平衡速度与质量)
- 输入YOLOv8模型检测
- 结合雷达数据融合判断
- 实测:在100米距离检测准确率从68%提升至83%
各上采样技术对比
方法 | 计算速度 | 抗锯齿效果 | 适用场景 |
---|---|---|---|
最近邻 | ⚡⚡⚡⚡ | ❌ | 实时系统(如AR/VR) |
双线性 | ⚡⚡⚡ | ✅ | 自动驾驶实时处理 |
双三次 | ⚡⚡ | ✅✅ | 医学影像分析 |
LANCZOS | ⚡ | ✅✅✅ | AIGC高质量生成 |
注意事项
- 计算代价:LANCZOS比双线性慢3-5倍,实时系统需权衡
- 过度平滑:抗锯齿可能模糊高频细节(如文字),可配合锐化滤波
在基于Python的AI服务中,如何规范API请求和数据交互的格式?
一、规范API交互的核心方法
1. 使用 FastAPI
+ pydantic
框架
- FastAPI:现代高性能Web框架,自动生成API文档(Swagger/Redoc)
- pydantic:通过类型注解定义数据模型,实现自动验证和序列化
pydantic 库的 BaseModel 能够定义一个数据验证和序列化模型,用于规范 API 请求或数据交互的格式。通过 pydantic.BaseModel
,AI开发者可以像设计数据库表结构一样严谨地定义数据交互协议,尤其适合需要高可靠性的工业级AI服务应用场景。
2. 定义三层结构
1 | # 请求模型:规范客户端输入 |
二、通俗示例:麻辣香锅订购系统
假设我们开发一个AI麻辣香锅订购服务,规范API交互流程:
1. 定义数据模型
1 | from pydantic import BaseModel |
三、在 AIGC 中的应用
场景:规范图像生成 API 的请求参数
案例:Stable Diffusion 服务接收生成请求时,验证参数合法性:
1 | from fastapi import FastAPI |
四、在传统深度学习中的应用
场景:训练任务配置管理
案例:定义训练参数模板,避免配置文件错误:
1 | class TrainingConfig(BaseModel): |
五、在自动驾驶中的应用
场景:传感器数据接收协议
案例:验证来自不同传感器的数据格式:
1 | class SensorConfig(BaseModel): |
六、API规范化带来的收益
维度 | 传统方式问题 | 规范化方案优势 |
---|---|---|
开发效率 | 需要手动编写验证逻辑 | 声明式定义,减少重复代码 |
错误排查 | 调试困难,错误信息不明确 | 自动返回具体字段验证失败原因 |
协作成本 | 前后端需要口头约定格式 | Swagger文档自动同步 |
安全性 | 可能接收非法参数导致崩溃 | 输入过滤防止注入攻击 |
扩展性 | 添加新字段需要多处修改 | 只需修改模型类定义 |
Python中处理OBJ文件的操作大全
下面是Python处理OBJ文件的完整操作指南,涵盖 读取、编辑、转换、可视化、优化 等核心功能。
一、OBJ 文件基础
OBJ 文件 是 Wavefront 3D 模型格式,包含以下主要元素:
- 顶点数据:
v
(顶点坐标)、vt
(纹理坐标)、vn
(法线) - 面定义:
f
(面索引,支持顶点/纹理/法线组合) - 材质引用:
mtllib
(材质库文件)、usemtl
(使用材质)
二、环境准备
安装所需库:
1 | pip install trimesh numpy pywavefront matplotlib pyrender |
三、核心操作详解
1. 读取 OBJ 文件
使用 trimesh
(推荐)
1 | import trimesh |
使用 pywavefront
1 | from pywavefront import Wavefront |
2. 编辑 OBJ 内容
修改几何体
1 | # 平移所有顶点 |
修改材质
1 | from PIL import Image |
合并多个模型
1 | mesh1 = trimesh.load("model1.obj") |
3. 导出 OBJ 文件
1 | # 创建新网格(立方体) |
4. 格式转换
OBJ → GLB
1 | mesh = trimesh.load("model.obj") |
5. 可视化渲染
使用 PyRender(3D 交互)
1 | import pyrender |
使用 Matplotlib(2D 投影)
1 | import matplotlib.pyplot as plt |
Python中日志模块loguru的使用
在服务开发中,日志模块是核心基础设施之一,它解决了以下关键问题:
1. 问题定位与故障排查
场景:服务崩溃、请求超时、数据异常。
作用:
- 记录关键步骤的执行路径(如请求参数、中间结果)。
- 自动捕获未处理的异常(如
loguru.catch
)。 - 通过日志级别(DEBUG/INFO/WARNING/ERROR)快速过滤问题。
示例:
1. 作为装饰器 (
@logger.catch
)这是最常见的用法,用于装饰函数或方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36PYTHONimport sys
from loguru import logger
# 配置 logger (可选,可以用默认配置)
# logger.add("error.log", level="ERROR", rotation="10 MB") # 例如,记录到文件
@logger.catch # <--- 使用装饰器
def divide_by_zero(a, b):
logger.info(f"Attempting division: {a} / {b}")
result = a / b # 这里会产生 ZeroDivisionError
return result
@logger.catch
def process_data(data):
logger.info(f"Processing data: {data}")
if not isinstance(data, dict):
# 这里会产生 TypeError,如果 data 不是字典
value = data.get("key")
else:
value = data.get("key", "default")
logger.info(f"Value found: {value}")
# 模拟另一个错误
x = 1 / 0 # ZeroDivisionError
if __name__ == "__main__":
logger.info("Starting script...")
# 调用第一个函数
divide_by_zero(10, 0) # 异常会被捕获并记录
logger.info("-" * 20)
# 调用第二个函数 (传入非字典类型)
process_data([1, 2, 3]) # 异常会被捕获并记录
logger.info("Script finished (or rather, continued after caught exceptions).")运行上述代码,你会在日志输出(控制台或文件)中看到类似以下内容(格式可能略有不同):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19TEXT2023-10-27 10:30:00.000 | INFO | __main__:<module>:30 - Starting script...
2023-10-27 10:30:00.001 | INFO | __main__:divide_by_zero:12 - Attempting division: 10 / 0
2023-10-27 10:30:00.005 | ERROR | __main__:<module>:33 - An error has been caught in function 'divide_by_zero', process 'MainProcess' (12345), thread 'MainThread' (54321):
Traceback (most recent call last):
File "your_script.py", line 13, in divide_by_zero
result = a / b # 这里会产生 ZeroDivisionError
~ ^ ~
ZeroDivisionError: division by zero
> Arguments: a = 10, b = 0
2023-10-27 10:30:00.006 | INFO | __main__:<module>:35 - --------------------
2023-10-27 10:30:00.007 | INFO | __main__:process_data:18 - Processing data: [1, 2, 3]
2023-10-27 10:30:00.010 | ERROR | __main__:<module>:38 - An error has been caught in function 'process_data', process 'MainProcess' (12345), thread 'MainThread' (54321):
Traceback (most recent call last):
File "your_script.py", line 21, in process_data
value = data.get("key")
^^^^^^^^^^
AttributeError: 'list' object has no attribute 'get'
> Arguments: data = [1, 2, 3]
2023-10-27 10:30:00.011 | INFO | __main__:<module>:40 - Script finished (or rather, continued after caught exceptions).关键点:
- 程序没有因为
ZeroDivisionError
或AttributeError
而崩溃。 - 错误被记录为
ERROR
级别(默认)。 - 日志包含了完整的 Traceback,并且 Loguru 尝试显示了导致错误的函数参数值 (
a = 10, b = 0
,data = [1, 2, 3]
),这对于调试非常有帮助。
2. 作为上下文管理器 (
with logger.catch():
)如果你只想保护一小段代码块而不是整个函数,可以使用上下文管理器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26PYTHONimport sys
from loguru import logger
def some_operation(config):
logger.info("Performing some operation...")
# ... 一些代码 ...
with logger.catch(message="Error during critical section"): # <--- 上下文管理器
# 保护这段代码
host = config["network"]["host"] # 可能引发 KeyError 或 TypeError
port = int(config["network"]["port"]) # 可能引发 KeyError, TypeError, ValueError
logger.info(f"Connecting to {host}:{port}")
# 模拟连接失败
raise ConnectionError("Failed to connect to server")
# ... 后续代码 (如果上面的异常被捕获且未重新抛出,这里会继续执行) ...
logger.info("Operation continues after potential error...")
if __name__ == "__main__":
bad_config = {"network": {"port": "abc"}} # 缺少 host, port 是无效的 int
good_config = {"network": {"host": "localhost", "port": "8080"}}
some_operation(bad_config)
logger.info("-" * 20)
some_operation(good_config) # 这个会触发 ConnectionError输出会包含类似:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20TEXT... (之前的日志) ...
2023-10-27 10:35:00.100 | INFO | __main__:some_operation:46 - Performing some operation...
2023-10-27 10:35:00.105 | ERROR | __main__:<module>:56 - Error during critical section # <--- 自定义消息
Traceback (most recent call last):
File "your_script.py", line 50, in some_operation
host = config["network"]["host"] # 可能引发 KeyError 或 TypeError
~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'host'
> Variables: config = {'network': {'port': 'abc'}}
2023-10-27 10:35:00.106 | INFO | __main__:some_operation:54 - Operation continues after potential error...
2023-10-27 10:35:00.107 | INFO | __main__:<module>:57 - --------------------
2023-10-27 10:35:00.108 | INFO | __main__:some_operation:46 - Performing some operation...
2023-10-27 10:35:00.109 | INFO | __main__:some_operation:52 - Connecting to localhost:8080
2023-10-27 10:35:00.115 | ERROR | __main__:<module>:58 - Error during critical section # <--- 自定义消息
Traceback (most recent call last):
File "your_script.py", line 53, in some_operation
raise ConnectionError("Failed to connect to server")
ConnectionError: Failed to connect to server
> Variables: config = {'network': {'host': 'localhost', 'port': '8080'}}, host = 'localhost', port = 8080
2023-10-27 10:35:00.116 | INFO | __main__:some_operation:54 - Operation continues after potential error...- 程序没有因为
2. 系统监控与健康检查
场景:服务运行时的性能、资源占用、错误率监控。
作用:
- 统计请求量、响应时间、错误频率(结合日志分析工具如 ELK)。
- 发现潜在风险(如高频错误日志触发告警)。
示例:
1
2
3start_time = time.time()
# ...处理请求...
logger.info(f"Request processed in {time.time() - start_time:.2f}s")
3. 行为审计与合规性
场景:金融交易、用户隐私操作等敏感场景。
作用:
- 记录用户关键操作(如登录、支付、数据修改)。
- 满足法律法规(如 GDPR、HIPAA 的审计要求)。
示例:
1
logger.info(f"User {user_id} updated profile: {changes}")
4. 性能分析与优化
场景:接口响应慢、资源瓶颈。
作用:
- 通过日志统计耗时操作(如数据库查询、外部 API 调用)。
- 定位代码热点(结合
logging
的计时功能)。
示例:
1
2with logger.catch(message="Database query"):
result = db.query("SELECT * FROM large_table")
loguru
是一个 Python 日志库,设计简洁且功能强大,相比标准库的 logging
模块更易用。以下是 loguru
的核心用法:
1. 安装
1 | pip install loguru |
2. 基础用法
直接导入 logger
实例即可使用,无需复杂配置:
1 | from loguru import logger |
3. 自动捕获异常
使用 logger.catch()
自动记录异常:
1 | @logger.catch |
4. 配置日志格式
通过 add()
方法自定义日志格式:
1 | logger.add( |
5. 日志级别控制
动态调整日志级别:
1 | logger.remove() # 移除默认输出 |
6. 高级功能
文件轮转与压缩
1 | logger.add( |
自定义颜色
1 | logger.add(sys.stderr, colorize=True, format="<green>{time}</green> <level>{message}</level>") |
7. 多模块使用
直接在入口文件配置一次,全局生效:
1 | # main.py |
8. 禁用默认输出
1 | logger.remove(handler_id=None) # 移除所有已添加的处理器 |
示例:完整配置
1 | from loguru import logger |
注意事项
- 默认会输出到
stderr
,通过logger.remove()
可移除。 - 支持结构化日志(JSON 格式)和异步日志。
- 可通过
enqueue=True
参数保证多进程/线程安全。