在日常办公和学习中我们经常会遇到大量图片需要统一调整尺寸、格式转换、添加水印或者需要将多个图片合并成PDF、从PDF中提取图片等繁琐任务。手动一张张处理不仅效率低下还容易出错。本文将围绕Python自动化批量处理图片与PDF的核心需求手把手带你搭建一套完整的处理流程。无论你是需要快速处理项目文档的职场人士还是希望提升编程实战能力的学生都能从本文获得可直接复用的代码方案。本文将重点介绍如何使用Python的PILPillow库进行图片批量处理以及PyMuPDFfitz库进行PDF操作涵盖从环境搭建、核心代码编写到异常处理的全流程。学完后你将能够轻松实现图片尺寸调整、格式转换、水印添加、PDF合并拆分、图片提取等常见需求彻底告别重复劳动。1. 核心工具与库的选择在开始实战之前我们先了解几个核心的Python库及其适用场景。正确选择工具是高效完成任务的第一步。1.1 Pillow图像处理的瑞士军刀Pillow是Python中最流行的图像处理库它提供了广泛的文件格式支持、强大的图像处理能力和简单的API。无论是基本的尺寸调整、格式转换还是复杂的滤镜应用、绘制操作Pillow都能胜任。主要功能包括支持数十种图像格式JPEG、PNG、GIF、BMP等图像缩放、旋转、裁剪、翻转等几何变换颜色空间转换、图像增强、滤镜效果文本和图形绘制可用于添加水印图像序列处理GIF、WebP动画1.2 PyMuPDF高性能PDF处理利器PyMuPDFfitz是一个功能强大且速度极快的PDF处理库相比其他PDF库它在处理大型文档时表现尤为出色。核心能力涵盖PDF文档的读取、创建和编辑页面提取、合并、旋转、删除文本和图像提取添加注释、书签和水印文档转换PDF转图像、HTML转PDF等1.3 其他备选方案对比虽然本文以Pillow和PyMuPDF为主但了解其他选项有助于你在不同场景下做出合适选择OpenCV更适合计算机视觉任务如图像识别、视频处理ReportLab专注于PDF生成适合创建复杂的报表文档pdf2image专门用于PDF到图像的转换基于poppler后端Img2PDF轻量级的图片转PDF工具API极其简单对于大多数办公自动化场景Pillow PyMuPDF的组合已经足够强大且易于上手。2. 环境准备与安装指南在编写代码之前我们需要搭建合适的开发环境。以下是详细的安装步骤和版本说明。2.1 Python版本要求推荐使用Python 3.7及以上版本本文示例基于Python 3.9开发测试。你可以通过以下命令检查当前Python版本python --version # 或 python3 --version如果尚未安装Python建议从Python官网下载最新稳定版或使用Anaconda发行版。2.2 安装必要的库使用pip安装所需的库建议创建虚拟环境以避免依赖冲突# 创建并激活虚拟环境可选 python -m venv image_pdf_env source image_pdf_env/bin/activate # Linux/Mac image_pdf_env\Scripts\activate # Windows # 安装核心库 pip install Pillow PyMuPDF # 可选安装其他辅助库 pip install opencv-python pdf2image img2pdf2.3 验证安装安装完成后通过简单的导入测试验证库是否正常工作# 测试脚本check_installation.py try: from PIL import Image, ImageDraw, ImageFont import fitz # PyMuPDF print(✓ Pillow和PyMuPDF安装成功) except ImportError as e: print(f✗ 导入失败: {e})2.4 开发工具推荐虽然任何文本编辑器都可以编写Python代码但使用合适的IDE能显著提升开发效率VS Code轻量级插件丰富适合初学者PyCharm专业Python IDE调试功能强大Jupyter Notebook适合交互式开发和数据探索本文示例使用VS Code编写但代码在任何环境中都能正常运行。3. 图片批量处理实战现在进入实战环节我们先从图片处理开始。假设你有一个包含数百张图片的文件夹需要统一进行处理。3.1 项目结构设计在开始编码前先规划好项目目录结构image_pdf_processor/ ├── src/ │ ├── image_processor.py # 图片处理核心模块 │ ├── pdf_processor.py # PDF处理核心模块 │ └── utils.py # 工具函数 ├── input/ # 输入文件目录 │ ├── images/ # 待处理的图片 │ └── pdfs/ # 待处理的PDF ├── output/ # 输出文件目录 │ ├── processed_images/ # 处理后的图片 │ └── processed_pdfs/ # 处理后的PDF ├── config/ # 配置文件 └── requirements.txt # 依赖列表3.2 图片批量缩放与格式转换最常见的需求之一是将大量图片统一调整为指定尺寸并转换格式。以下是完整的实现代码# 文件路径src/image_processor.py import os from PIL import Image from pathlib import Path class ImageProcessor: def __init__(self, input_dir, output_dir): self.input_dir Path(input_dir) self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def batch_resize_and_convert(self, target_size(800, 600), output_formatJPEG, quality85): 批量调整图片尺寸并转换格式 Args: target_size: 目标尺寸 (宽, 高) output_format: 输出格式 (JPEG, PNG, WEBP) quality: 输出质量 (1-100)仅对JPEG有效 supported_formats {.jpg, .jpeg, .png, .bmp, .gif, .webp} for img_path in self.input_dir.iterdir(): if img_path.suffix.lower() in supported_formats: try: with Image.open(img_path) as img: # 调整尺寸保持宽高比 img.thumbnail(target_size, Image.Resampling.LANCZOS) # 转换模式如PNG的RGBA转JPEG的RGB if output_format JPEG and img.mode in (RGBA, P): img img.convert(RGB) # 生成输出路径 output_path self.output_dir / f{img_path.stem}_resized.{output_format.lower()} # 保存图片 save_kwargs {quality: quality} if output_format JPEG else {} img.save(output_path, formatoutput_format, **save_kwargs) print(f✓ 处理完成: {img_path.name} - {output_path.name}) except Exception as e: print(f✗ 处理失败 {img_path.name}: {e}) # 使用示例 if __name__ __main__: processor ImageProcessor(input/images, output/processed_images) processor.batch_resize_and_convert(target_size(1024, 768), output_formatJPEG)关键参数说明target_size使用thumbnail方法会保持原图宽高比确保图片不变形output_format根据需求选择格式JPEG适合照片PNG适合需要透明度的图片qualityJPEG质量参数85是较好的平衡点数值越大文件越大3.3 批量添加水印功能为图片添加水印可以保护版权或标注来源。以下是带文字水印的实现# 在ImageProcessor类中添加方法 def add_watermark(self, watermark_text, font_size30, opacity128, positioncenter): 为图片批量添加文字水印 Args: watermark_text: 水印文字 font_size: 字体大小 opacity: 透明度 (0-255) position: 水印位置 (center, bottom-right, top-left等) try: # 尝试加载字体失败则使用默认字体 try: font ImageFont.truetype(arial.ttf, font_size) except IOError: font ImageFont.load_default() for img_path in self.input_dir.iterdir(): if img_path.suffix.lower() in {.jpg, .jpeg, .png, .bmp}: with Image.open(img_path).convert(RGBA) as base_img: # 创建水印层 watermark Image.new(RGBA, base_img.size, (0, 0, 0, 0)) draw ImageDraw.Draw(watermark) # 计算文字尺寸和位置 bbox draw.textbbox((0, 0), watermark_text, fontfont) text_width bbox[2] - bbox[0] text_height bbox[3] - bbox[1] # 根据位置参数计算坐标 if position center: x (base_img.width - text_width) // 2 y (base_img.height - text_height) // 2 elif position bottom-right: x base_img.width - text_width - 20 y base_img.height - text_height - 20 else: # top-left x 20 y 20 # 绘制水印文字带透明度 draw.text((x, y), watermark_text, fontfont, fill(255, 255, 255, opacity)) # 合并图层 combined Image.alpha_composite(base_img, watermark) # 转换回RGB模式如果需要 if base_img.mode RGB: combined combined.convert(RGB) output_path self.output_dir / f{img_path.stem}_watermarked.png combined.save(output_path) print(f✓ 水印添加完成: {output_path.name}) except Exception as e: print(f水印处理异常: {e})3.4 高级功能批量重命名与EXIF信息处理对于摄影爱好者或需要管理大量图片的用户重命名和EXIF信息处理也很重要import exifread from datetime import datetime def batch_rename_by_date(self, pattern{date}_{counter:04d}): 根据EXIF信息中的拍摄日期批量重命名图片 for img_path in self.input_dir.iterdir(): if img_path.suffix.lower() in {.jpg, .jpeg}: try: # 读取EXIF信息 with open(img_path, rb) as f: tags exifread.process_file(f) # 获取拍摄日期 date_tag tags.get(EXIF DateTimeOriginal) if date_tag: date_str str(date_tag).replace(:, ).replace( , _) date_obj datetime.strptime(str(date_tag), %Y:%m:%d %H:%M:%S) date_formatted date_obj.strftime(%Y%m%d_%H%M%S) else: # 如果没有EXIF日期使用文件修改时间 date_formatted datetime.fromtimestamp( img_path.stat().st_mtime).strftime(%Y%m%d_%H%M%S) # 生成新文件名 new_name pattern.format(datedate_formatted, counter1) new_path self.output_dir / f{new_name}{img_path.suffix} # 处理重名文件 counter 1 while new_path.exists(): new_name pattern.format(datedate_formatted, countercounter) new_path self.output_dir / f{new_name}{img_path.suffix} counter 1 # 复制文件 import shutil shutil.copy2(img_path, new_path) print(f✓ 重命名: {img_path.name} - {new_path.name}) except Exception as e: print(f✗ 重命名失败 {img_path.name}: {e})4. PDF处理全流程实战处理完图片我们转向PDF文档的自动化处理。PDF处理的需求同样多样且频繁。4.1 PDF与图片互转将图片合并为PDF# 文件路径src/pdf_processor.py import fitz # PyMuPDF from PIL import Image import os from pathlib import Path class PDFProcessor: def __init__(self, input_dir, output_dir): self.input_dir Path(input_dir) self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def images_to_pdf(self, pdf_nameoutput.pdf, page_size(595, 842)): 将多张图片合并为一个PDF文件 Args: pdf_name: 输出的PDF文件名 page_size: 页面尺寸 (A4默认: 595x842 points) try: # 创建新PDF文档 doc fitz.open() # 获取所有图片文件 image_files sorted([f for f in self.input_dir.iterdir() if f.suffix.lower() in [.jpg, .jpeg, .png, .bmp]]) if not image_files: print(未找到图片文件) return for img_path in image_files: try: # 创建新页面 page doc.new_page(widthpage_size[0], heightpage_size[1]) # 计算图片在页面中的位置和尺寸 img Image.open(img_path) img_width, img_height img.size # 保持宽高比缩放图片以适应页面 page_width, page_height page_size ratio min(page_width/img_width, page_height/img_height) * 0.9 # 留边距 new_width int(img_width * ratio) new_height int(img_height * ratio) # 居中放置图片 x (page_width - new_width) / 2 y (page_height - new_height) / 2 # 插入图片到PDF页面 page.insert_image(fitz.Rect(x, y, x new_width, y new_height), filenamestr(img_path)) print(f✓ 添加页面: {img_path.name}) except Exception as e: print(f✗ 处理图片失败 {img_path.name}: {e}) # 保存PDF output_path self.output_dir / pdf_name doc.save(output_path) doc.close() print(fPDF生成完成: {output_path}) except Exception as e: print(fPDF创建失败: {e}) # 使用示例 if __name__ __main__: processor PDFProcessor(input/images, output/processed_pdfs) processor.images_to_pdf(我的相册.pdf)从PDF中提取图片def extract_images_from_pdf(self, pdf_name, output_dirextracted_images, dpi150): 从PDF中提取所有图片 Args: pdf_name: PDF文件名 output_dir: 图片输出目录 dpi: 输出图片分辨率 pdf_path self.input_dir / pdf_name extract_dir self.output_dir / output_dir extract_dir.mkdir(parentsTrue, exist_okTrue) try: doc fitz.open(pdf_path) image_counter 0 for page_num in range(len(doc)): page doc[page_num] # 获取页面中的所有图片 image_list page.get_images() for img_index, img in enumerate(image_list): # 提取图片 xref img[0] pix fitz.Pixmap(doc, xref) if pix.n - pix.alpha 4: # 非CMYK图片 # 保存为PNG output_path extract_dir / fpage_{page_num1}_img_{img_index1}.png pix.save(output_path) image_counter 1 print(f✓ 提取图片: {output_path.name}) pix None # 释放内存 doc.close() print(f共提取 {image_counter} 张图片) except Exception as e: print(f图片提取失败: {e})4.2 PDF页面操作与合并拆分合并多个PDF文件def merge_pdfs(self, pdf_list, output_namemerged.pdf): 合并多个PDF文件 Args: pdf_list: 要合并的PDF文件名列表 output_name: 合并后的文件名 try: merged_doc fitz.open() for pdf_name in pdf_list: pdf_path self.input_dir / pdf_name if pdf_path.exists(): doc fitz.open(pdf_path) merged_doc.insert_pdf(doc) # 插入整个文档 doc.close() print(f✓ 已添加: {pdf_name}) else: print(f✗ 文件不存在: {pdf_name}) output_path self.output_dir / output_name merged_doc.save(output_path) merged_doc.close() print(f合并完成: {output_path}) except Exception as e: print(fPDF合并失败: {e}) # 使用示例 pdf_list [文档1.pdf, 文档2.pdf, 文档3.pdf] processor.merge_pdfs(pdf_list, 合并文档.pdf)拆分PDF文件def split_pdf(self, pdf_name, split_rangesNone): 按页面范围拆分PDF文件 Args: pdf_name: 要拆分的PDF文件名 split_ranges: 拆分范围列表如 [(1,3), (4,6)] 表示拆分为1-3页和4-6页 pdf_path self.input_dir / pdf_name if not split_ranges: # 如果没有指定范围默认每页拆分为一个文件 doc fitz.open(pdf_path) split_ranges [(i1, i1) for i in range(len(doc))] doc.close() try: for i, (start_page, end_page) in enumerate(split_ranges): doc fitz.open(pdf_path) new_doc fitz.open() # 调整页码从0开始 start_idx max(0, start_page - 1) end_idx min(len(doc) - 1, end_page - 1) # 插入指定页面范围 new_doc.insert_pdf(doc, from_pagestart_idx, to_pageend_idx) output_name f{pdf_path.stem}_part_{i1}.pdf output_path self.output_dir / output_name new_doc.save(output_path) new_doc.close() doc.close() print(f✓ 生成拆分文件: {output_name} (页码 {start_page}-{end_page})) except Exception as e: print(fPDF拆分失败: {e})4.3 PDF内容处理与优化为PDF添加水印def add_watermark_to_pdf(self, pdf_name, watermark_text, output_nameNone): 为PDF每一页添加文字水印 if output_name is None: output_name f{Path(pdf_name).stem}_watermarked.pdf pdf_path self.input_dir / pdf_name output_path self.output_dir / output_name try: doc fitz.open(pdf_path) for page_num in range(len(doc)): page doc[page_num] # 创建水印文本 watermark page.insert_text((page.rect.width/2, page.rect.height/2), watermark_text, fontsize40, color(0.8, 0.8, 0.8), # 浅灰色 rotate45, # 45度倾斜 opacity0.3) # 透明度 doc.save(output_path) doc.close() print(f水印添加完成: {output_path}) except Exception as e: print(f添加水印失败: {e})压缩PDF文件大小def compress_pdf(self, pdf_name, output_nameNone, qualitynormal): 压缩PDF文件大小 Args: quality: 压缩质量 (high, normal, low) if output_name is None: output_name f{Path(pdf_name).stem}_compressed.pdf quality_map { high: 0.8, # 高质量压缩 normal: 0.6, # 正常压缩 low: 0.4 # 高压缩率 } compression quality_map.get(quality, 0.6) try: doc fitz.open(self.input_dir / pdf_name) doc.save(self.output_dir / output_name, garbage4, # 清理无用对象 deflateTrue, # 压缩流 cleanTrue, # 清理文档结构 compresscompression) # 图片压缩率 # 比较文件大小 original_size (self.input_dir / pdf_name).stat().st_size compressed_size (self.output_dir / output_name).stat().st_size reduction (1 - compressed_size/original_size) * 100 print(f压缩完成: {original_size/1024:.1f}KB - {compressed_size/1024:.1f}KB f(减少{reduction:.1f}%)) doc.close() except Exception as e: print(fPDF压缩失败: {e})5. 完整工作流整合现在我们将图片处理和PDF处理功能整合成一个完整的工作流实现真正的一条龙处理。5.1 自动化流水线设计# 文件路径src/workflow_manager.py from pathlib import Path from image_processor import ImageProcessor from pdf_processor import PDFProcessor import shutil class WorkflowManager: def __init__(self, base_dir.): self.base_dir Path(base_dir) self.setup_directories() def setup_directories(self): 创建标准目录结构 directories [ input/images, input/pdfs, output/processed_images, output/processed_pdfs, output/final_results, temp ] for dir_path in directories: (self.base_dir / dir_path).mkdir(parentsTrue, exist_okTrue) def process_photo_collection(self, image_patternsNone): 完整的图片处理流水线重命名 → 调整尺寸 → 添加水印 → 生成PDF相册 if image_patterns is None: image_patterns [*.jpg, *.jpeg, *.png] # 初始化处理器 img_processor ImageProcessor( self.base_dir / input/images, self.base_dir / temp/resized_images ) pdf_processor PDFProcessor( self.base_dir / temp/resized_images, self.base_dir / output/final_results ) try: print( 开始图片处理流水线 ) # 步骤1: 按日期重命名 print(步骤1: 批量重命名图片) img_processor.input_dir self.base_dir / input/images img_processor.output_dir self.base_dir / temp/renamed_images img_processor.output_dir.mkdir(parentsTrue, exist_okTrue) # 这里调用重命名方法需在实际代码中实现 # 步骤2: 调整尺寸和格式 print(步骤2: 调整图片尺寸和格式) img_processor.input_dir self.base_dir / temp/renamed_images img_processor.output_dir self.base_dir / temp/resized_images img_processor.batch_resize_and_convert( target_size(1200, 800), output_formatJPEG, quality90 ) # 步骤3: 添加水印 print(步骤3: 添加水印) img_processor.input_dir self.base_dir / temp/resized_images img_processor.output_dir self.base_dir / temp/watermarked_images img_processor.add_watermark( © My Collection 2024, font_size24, positionbottom-right ) # 步骤4: 生成PDF相册 print(步骤4: 生成PDF相册) pdf_processor.input_dir self.base_dir / temp/watermarked_images pdf_processor.images_to_pdf(我的照片集.pdf) print( 处理完成 ) except Exception as e: print(f流水线执行失败: {e}) finally: # 清理临时文件可选 # shutil.rmtree(self.base_dir / temp) pass # 使用示例 if __name__ __main__: workflow WorkflowManager() workflow.process_photo_collection()5.2 配置文件管理为了提升代码的灵活性我们可以添加配置文件支持# 文件路径config/settings.yaml image_processing: default_size: [1200, 800] output_format: JPEG quality: 90 watermark: text: © Confidential font_size: 24 position: bottom-right pdf_processing: page_size: [595, 842] # A4 compression_quality: normal workflow: auto_clean_temp: true keep_original: true# 配置文件读取工具 import yaml from pathlib import Path def load_config(config_pathconfig/settings.yaml): 加载配置文件 try: with open(config_path, r, encodingutf-8) as f: return yaml.safe_load(f) except FileNotFoundError: print(配置文件不存在使用默认配置) return get_default_config() def get_default_config(): 返回默认配置 return { image_processing: { default_size: [1200, 800], output_format: JPEG, quality: 85 }, pdf_processing: { page_size: [595, 842], compression_quality: normal } }6. 常见问题与解决方案在实际使用过程中你可能会遇到各种问题。以下是常见问题的排查指南。6.1 图片处理常见问题问题1内存不足错误现象处理大图片时出现MemoryError原因高分辨率图片占用内存过大解决方案在处理前先调整图片尺寸使用流式处理不要同时加载所有图片增加系统虚拟内存# 内存优化版本的处理函数 def memory_efficient_resize(self, target_size): 内存友好的图片处理 for img_path in self.input_dir.iterdir(): try: # 逐步处理及时释放内存 with Image.open(img_path) as img: img.thumbnail(target_size) # 立即保存并释放内存 output_path self.output_dir / f{img_path.stem}_resized.jpg img.save(output_path, optimizeTrue) except Exception as e: print(f处理失败 {img_path.name}: {e})问题2格式兼容性问题现象某些图片无法打开或保存原因格式不支持或文件损坏解决方案使用Pillow的格式检测功能添加异常处理和日志记录def safe_image_open(self, img_path): 安全的图片打开方式 try: with Image.open(img_path) as img: # 验证图片完整性 img.verify() img Image.open(img_path) # 重新打开已验证的图片 return img except Exception as e: print(f图片损坏或格式不支持: {img_path.name} - {e}) return None6.2 PDF处理常见问题问题1中文显示乱码现象PDF中的中文显示为乱码原因字体缺失或编码问题解决方案确保系统安装中文字体在代码中指定中文字体路径# 使用中文字体添加水印 def add_chinese_watermark(self, watermark_text): 支持中文的水印添加 try: # 尝试加载系统中文字体 font_paths [ C:/Windows/Fonts/simhei.ttf, # Windows黑体 /System/Library/Fonts/PingFang.ttc, # Mac字体 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf # Linux ] font None for font_path in font_paths: if Path(font_path).exists(): font ImageFont.truetype(font_path, 30) break if font is None: raise Exception(未找到合适的中文字体) # 使用找到的字体添加水印 # ... 其余水印代码 ... except Exception as e: print(f中文字体处理失败: {e})问题2PDF权限限制现象无法编辑或提取受保护的PDF原因PDF设置了密码保护或权限限制解决方案合法的密码解除保护需有权限使用专门的PDF解锁工具联系文档所有者获取权限6.3 性能优化建议当处理大量文件时性能成为关键因素。以下是一些优化技巧批量处理优化from concurrent.futures import ThreadPoolExecutor import multiprocessing def parallel_image_processing(self, max_workersNone): 使用多线程并行处理图片 if max_workers is None: max_workers multiprocessing.cpu_count() image_files [f for f in self.input_dir.iterdir() if f.suffix.lower() in {.jpg, .jpeg, .png}] def process_single_image(img_path): try: with Image.open(img_path) as img: img.thumbnail((800, 600)) output_path self.output_dir / f{img_path.stem}_processed.jpg img.save(output_path) return True except Exception as e: print(f处理失败 {img_path.name}: {e}) return False with ThreadPoolExecutor(max_workersmax_workers) as executor: results list(executor.map(process_single_image, image_files)) success_count sum(results) print(f处理完成: {success_count}/{len(image_files)} 成功)内存使用监控import psutil import os def memory_usage(): 监控内存使用情况 process psutil.Process(os.getpid()) return process.memory_info().rss / 1024 / 1024 # MB def process_with_memory_check(self): 带内存检查的处理函数 for img_path in self.input_dir.iterdir(): if memory_usage() 500: # 如果内存使用超过500MB print(内存使用过高建议分批处理) break # 正常处理逻辑 self.process_image(img_path)7. 最佳实践与工程化建议将脚本升级为可维护的工程代码需要遵循一些最佳实践。7.1 错误处理与日志记录完善的错误处理import logging from datetime import datetime def setup_logging(): 配置日志系统 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(fprocessing_{datetime.now().strftime(%Y%m%d_%H%M%S)}.log), logging.StreamHandler() ] ) class RobustImageProcessor(ImageProcessor): 增强的错误处理版本 def safe_batch_process(self): 带完整错误处理的批量处理 success_count 0 error_count 0 error_details [] for img_path in self.input_dir.iterdir(): try: self._validate_image(img_path) self._process_single_image(img_path) success_count 1 except Exception as e: error_count 1 error_details.append(f{img_path.name}: {str(e)}) logging.error(f处理失败 {img_path.name}: {e}) # 根据错误类型采取不同措施 if corrupt in str(e).lower(): self._handle_corrupt_file(img_path) elif memory in str(e).lower(): self._handle_memory_issue() # 生成处理报告 self._generate_report(success_count, error_count, error_details) def _validate_image(self, img_path): 验证图片文件 if not img_path.exists(): raise FileNotFoundError(f文件不存在: {img_path}) if img_path.stat().st_size 0: raise ValueError(文件大小为0) # 简单的格式验证 valid_extensions {.jpg, .jpeg, .png, .bmp} if img_path.suffix.lower() not in valid_extensions: raise ValueError(f不支持的格式: {img_path.suffix})7.2 配置管理与环境隔离使用环境变量管理敏感信息import os from dotenv import load_dotenv load_dotenv() # 加载.env文件 class Config: 配置管理类 property def input_dir(self): return os.getenv(INPUT_DIR, input) property def output_dir(self): return os.getenv(OUTPUT_DIR, output) property def max_file_size(self): return int(os.getenv(MAX_FILE_SIZE, 104857600)) # 100MB默认值 property def allowed_formats(self): formats os.getenv(ALLOWED_FORMATS, jpg,jpeg,png,pdf) return set(format.strip().lower() for format in formats.split(,))7.3 单元测试与质量保证编写测试用例# tests/test