很多人学Python都是从“写脚本”开始的但脚本和工具之间往往隔着一个“界面”和“批处理”的距离。这个系列我一直在带大家零基础做自己的Python小工具前面两篇做完了一个能用的图片格式转换器这次进阶三要做的事情很明确加上尺寸调整、WebP格式支持、压缩选项并且把单张转换和批量转换彻底打通。做完之后你就拥有一个真正能应对日常图片处理需求的命令行小工具箱不再只是写着玩的练手代码。这篇文章是基于我们之前做好的转换器来迭代适合已经掌握Python基础语法、知道Pillow库怎么装怎么用的朋友。如果你完全没接触过建议先回头补一补基础再来看这一篇。我会把新增功能的思路、代码实现、以及我在实际使用中踩过的坑全部讲清楚保证你跟着做完之后不仅能自主修改这几个功能还能顺手扩展出自己的新版本来。1. 整体设计这次加的功能到底要解决什么问题1.1 旧版工具缺了什么新版补上什么之前两篇里我们已经实现了一个基础版本支持在JPG、PNG、BMP这些常见格式之间做转换也能一次性处理整个文件夹下的图片。但说实话那个版本在真正使用的过程中会暴露几个很现实的问题。第一只能转格式不能改尺寸。很多时候我从网上下载的图片是一两兆甚至更大的图只是想在公众号文章里用一用原图直接传上去又慢又浪费空间必须先缩小到合适的宽度。旧版工具面对这种需求完全无能为力只能另外找在线工具或者打开美图软件手动处理效率低得让人想摔键盘。第二没有WebP支持。WebP这个格式出来已经不是一年两年了Google推广了很多年现在浏览器兼容性已经非常好同样画质下体积比PNG和JPG小不少我自己的博客配图早就全部切换成WebP了。但Pillow库的老版本对WebP支持不完善加上很多人被网上的旧教程误导以为Python处理不了导致这个明明很好用的格式一直没进入普通人的工具链。第三缺少压缩控制。转格式本身不一定会让文件变小PNG转JPG会变小但JPG转PNG反而会变大。用户想要的是“让图片变得更适合网络传播”不只是换一个后缀名。所以这次迭代的核心思路很清晰给工具加上三个可独立使用、又能组合生效的功能维度——尺寸调整、格式转换、质量压缩。用户可以对一张图片或者一个文件夹的图片一次性指定“我要多大尺寸 我要什么格式 我要多少质量”工具自动处理完所有逻辑。这个设计思路就是走向真实工具的第一步。1.2 为什么新增功能都交给Pillow而不是自己造轮子Pillow是Python图像处理的事实标准库这一点现在基本没有争议。它是当初PIL库的继任者社区维护活跃文档齐全所有主流图像格式都有现成的读写接口。对于零基础学习者来说自己用OpenCV这类重型库门槛太高纯自己写图像编解码更是天方夜谭Pillow正好卡在一个非常合适的位置API简单直观功能又足够覆盖百分之八十的日常图像处理需求。尺寸调整在Pillow里就是一个resize方法传一个新尺寸进去就行。质量压缩在保存时指定一个quality参数。WebP呢Pillow在比较新的版本里原生支持save的时候填“webp”作为格式名就行。你看这三个功能全部是“参数级别”的复杂度而不是“算法级别”的复杂度。这意味着什么意味着我们不需要理解图像插值算法怎么实现、编码器内部怎么运作只需要知道这些参数的含义就可以在应用层面做出非常实用的工具。我自己做项目一直有一个原则能用成熟库解决的就绝对不自己写因为你写的八成没人家的好还浪费时间。这个原则在入门阶段尤其重要很多初学者容易陷入“我要彻底搞懂底层原理才能动手”的心态结果卡在第一步永远出不来。正确姿势是先用库把东西做出来跑起来有了完整的应用体验之后再逐步深入某一个点去研究原理那时候理解起来会轻松得多。1.3 功能模块怎么划分动笔敲代码之前先把工具的功能模块画在脑子里。这个工具按功能拆成四块参数解析模块负责接收用户输入的命令行参数比如输入路径、目标宽度、目标高度、输出格式、质量档位、是否批量处理。图片处理模块核心的图像操作逻辑包括打开、调整尺寸、压缩、保存。文件遍历模块负责判断用户给的是单张图片还是文件夹如果是文件夹就递归找出所有支持的图片文件。主流程模块把参数解析、文件遍历、图片处理串起来控制整个程序的执行顺序。这样划分的好处是每个模块的职责单一改起来互不影响。比如以后想加旋转功能只需要在图片处理模块加一个方法然后注册到命令行参数接口里就行不需要动其他代码。初学者最容易犯的毛病就是所有逻辑全挤在一个main函数里几百行代码搅成一团后面想改一个功能都找不到地方下手。模块化思考是代码这件事里最重要的思维习惯之一。2. 核心功能的技术拆解2.1 尺寸调整先搞清楚resize和thumbnail的区别尺寸调整是这次新增的第一个基础能力但这里面有一个初学者非常容易踩坑的点Pillow里resize和thumbnail两个方法都可以改变图片尺寸行为却完全不同。resize是最直白的你给它一个新的(宽度, 高度)元组它就把图片拉伸或者压缩到这个精确的尺寸。假设原始图片是1920x1080的横图你传一个(1080, 1920)进去它就会把图片纵向拉长结果看起来是变形的。这种“指定尺寸就强行改”的行为大多数情况下不是我们要的。thumbnail则保持了原图的宽高比而且只会缩小不会放大。你给它一个(800, 800)的限制尺寸它会在保持比例的前提下把图片缩放到宽度和高度都不超过800像素。比如一张1920x1080的图thumbnail之后变成800x450比例没变视觉效果正常。那实际做工具应该用哪个分场景。如果用户明确说“我要一个宽1080、高1920的封面图”那就用resize哪怕变形也无所谓因为它就是要这个比例。如果用户说的只是“宽度不要超过800”那明显应该用thumbnail保证图片不变形。我的做法是把这个选择权交给用户命令行里提供两种模式一种是指定目标尺寸强制resize另一种是只给一个最大尺寸让thumbnail自动保持比例。如果用户两种参数都没给那就不做尺寸调整只处理格式转换和压缩。这样用户控制力最强同时默认行为也足够安全。有一个细节要特别注意thumbnail修改的是原图对象尺寸直接操作Image对象本身不像resize返回一个新对象。所以代码里两种方法的使用模式不一样。用thumbnail的代码是img.thumbnail((width, height))然后再用img往下走用resize的代码是new_img img.resize((width, height))后续使用的是返回的新对象。这个差异看起来很微小但没搞清楚的话你会遇到各种莫名其妙的“改了尺寸但保存出来没变”的问题。2.2 WebP格式到底好在哪为什么值得专门支持WebP格式2010年就发布了十几年下来生态已经非常成熟。它由Google推出设计目标就是要在同等视觉质量下比JPEG和PNG占用的存储空间更小。实际使用中同样一张照片转成WebP体积能比JPEG减小25%到34%而肉眼几乎分辨不出画质差异。对于PNG这种无损压缩格式WebP的无损模式也能做到比PNG小大约20%的体积。我做博客配图的时候做过一个测试一张2.1MB的PNG截图直接转成WebP无损格式体积降到了1.6MB左右视觉上没有任何区别。再转成有损的WebP质量设为80体积直接降到300多KB完全适合网页场景。Pillow对WebP的支持经历过一个过程早期的Pillow版本默认不带WebP支持需要自己在系统里装WebP库然后重新编译Pillow才有。但从Pillow 4.0之后官方发布的wheel包已经内置了WebP支持现在大家用pip install Pillow装到的版本几乎都是直接支持WebP的。唯一要注意的是新版Pillow在保存WebP时的参数有一些调整后面我会在常见问题里详细讲。我们在工具里要支持的就是让用户能通过命令行指定输出格式为webp然后save的时候格式传“WEBP”。同时为了后面压缩功能联动WebP保存时也会用到quality参数这个刚好可以和JPG的压缩逻辑统一起来。2.3 压缩的本质和质量参数的取舍图片压缩这个话题很多人理解有偏差。先说清楚基本概念JPEG、WebP这些格式本身都是有损格式它们通过去掉一些人类视觉不太敏感的细节来减少文件体积这个“丢细节的程度”就由质量参数来控制。Pillow里quality的取值范围是1到100数值越大画质越好体积也越大数值越小画质越差体积越小。很多人以为质量100就是“无损”这是一个常见的误解。100只是代表在这个编码器算法下压缩力度最小但jpeg格式本身就是有损的即使100也有信息损失。真正要实现无损应该选PNG或者WebP的无损模式。实操中怎么选质量值呢我的经验是分场景照片类图片色彩丰富、细节多质量85到95比较合适。我用90做默认值在这个档位下照片几乎看不出画质损失体积能削减一半以上。截图类图片文字、UI界面因为有大面积纯色区域用JPEG有损压缩容易产生边缘噪点推荐直接用PNG或WebP无损。内容平台配图公众号、博客质量80到90足够了毕竟浏览端还会进一步压缩。工具设计上我提供quality参数让用户手动指定默认值为90。同时额外提供一个快捷参数--compression让用户可以不记数字直接说high、medium、low工具内部映射到95、85、70。这种设计是为了照顾零基础用户的体验记不住或者不懂参数含义也没关系选一个档位就能用。这里还有一个联动逻辑要处理好如果用户选择的输出格式是PNGPNG是无损格式quality参数对PNG基本没有影响这时候不能悄悄忽略用户的选择而是要在命令行输出里提示一下“PNG不支持有损压缩已忽略质量参数”避免用户以为工具坏了。3. 完整代码实现与实操全流程3.1 环境准备和依赖安装这次开发环境还是沿用最简单的方案Python 3.8以上版本Pillow库。安装命令就一行pip install Pillow如果你的网络环境下载慢可以用国内镜像源pip install Pillow -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后建议验证一下Pillow版本和WebP支持情况python -c from PIL import Image; print(Image.__version__); print(WebP支持:, WEBP in Image.registered_extensions().values())如果输出里显示WebP支持为True说明环境没问题可以进入下一步。如果你用的Pillow版本很旧建议先升级到新版老版本在WebP的quality参数处理上有不少Bug。另外我们这次会用到Pathlib库来处理文件路径它是Python标准库不需要额外安装。Pathlib用起来比老式的os.path模块舒服得多代码也更简洁强烈推荐初学者直接学Pathlib不要走弯路去学已经被官方推荐的旧写法。3.2 完整的命令行入口设计这个工具我打算做成命令行工具因为命令行是零基础最好理解和掌握的交互方式不涉及GUI设计的复杂度而且配合脚本和自动化非常方便。运行方式是这样的python img_tool.py convert input.jpg --width 800 --format webp --quality 80处理文件夹里所有图片的话直接把路径换成文件夹路径python img_tool.py convert ./photos --width 800 --format webp --quality 80参数解释一下convert 是子命令表示执行转换操作。input.jpg 是位置参数表示待处理的图片或文件夹路径。--width 表示目标宽度只传宽度不传高度时自动等比缩放。--height 表示目标高度和--width一起传时强制拉伸到这个尺寸。--format 表示输出格式支持jpg、png、webp、bmp。--quality 表示质量值范围1-100。--compression 是快捷档位可选high、medium、low。--output 指定输出目录默认是原文件旁边新建一个output文件夹。--recursive 表示是否递归处理子文件夹里的图片。这些参数看起来挺多但设计逻辑很清晰每个参数都对应一个用户实际会有的需求。下面我把完整的代码写出来并逐段解释关键逻辑。3.3 完整代码实现import argparse from pathlib import Path from PIL import Image SUPPORTED_EXTENSIONS {.jpg, .jpeg, .png, .bmp, .webp} COMPRESSION_LEVELS { high: 95, medium: 85, low: 70, } def parse_args(): parser argparse.ArgumentParser( description批量图片格式转换器支持尺寸调整、格式转换、压缩、单/批量处理 ) parser.add_argument(path, typestr, help图片文件路径或文件夹路径) parser.add_argument(--width, typeint, default0, help目标宽度默认保持原尺寸) parser.add_argument(--height, typeint, default0, help目标高度默认保持原尺寸) parser.add_argument(--format, typestr, default, help输出格式jpg、png、webp、bmp) parser.add_argument(--quality, typeint, default90, help输出质量范围1-100默认90) parser.add_argument(--compression, typestr, choices[high, medium, low], help快捷质量档位) parser.add_argument(--output, typestr, default, help输出目录默认在源文件旁创建output目录) parser.add_argument(--recursive, actionstore_true, help递归处理子文件夹中的图片) return parser.parse_args() def process_single_image(img_path: Path, args): try: img Image.open(img_path) original_size img.size original_bytes img_path.stat().st_size img, output_format adjust_size(img, args) output_format determine_output_format(img_path, args, output_format) output_dir determine_output_dir(img_path, args) # 生成输出文件名和路径 output_ext .webp if output_format webp else f.{output_format} out_path output_dir / (img_path.stem output_ext) # 如果输出格式是PNG忽略质量参数 if output_format png: img.save(out_path, formatPNG) else: img.save(out_path, formatoutput_format.upper(), qualityargs.quality) new_bytes out_path.stat().st_size size_change (new_bytes - original_bytes) / original_bytes * 100 print(f[完成] {img_path.name} - {out_path.name}) print(f 尺寸: {original_size} - {img.size}) print(f 体积: {format_file_size(original_bytes)} - {format_file_size(new_bytes)} ({size_change:.1f}%)) img.close() except Exception as e: print(f[错误] {img_path.name}: {e}) def adjust_size(img: Image.Image, args): if args.width 0 and args.height 0: # 用户同时指定了宽高强制拉伸到指定尺寸 img img.resize((args.width, args.height), Image.LANCZOS) elif args.width 0 or args.height 0: # 只指定了宽度或高度等比缩放 target_width args.width if args.width 0 else args.height ratio target_width / img.width new_width int(img.width * ratio) new_height int(img.height * ratio) if args.width 0: img img.resize((new_width, new_height), Image.LANCZOS) else: img img.resize((new_width, new_height), Image.LANCZOS) return img def determine_output_format(img_path: Path, args, current_format): if args.format: return args.format.lower().lstrip(.) return current_format def determine_output_dir(img_path: Path, args) - Path: if args.output: out_dir Path(args.output) else: out_dir img_path.parent / output out_dir.mkdir(parentsTrue, exist_okTrue) return out_dir def format_file_size(size: int) - str: if size 1024: return f{size} B elif size 1024 * 1024: return f{size / 1024:.1f} KB else: return f{size / (1024 * 1024):.2f} MB def collect_images(path: Path, recursive: bool): if path.is_file(): if path.suffix.lower() in SUPPORTED_EXTENSIONS: return [path] return [] elif path.is_dir(): if recursive: return [p for p in path.rglob(*) if p.suffix.lower() in SUPPORTED_EXTENSIONS] return [p for p in path.glob(*) if p.suffix.lower() in SUPPORTED_EXTENSIONS and p.is_file()] return [] def main(): args parse_args() # 处理快捷质量档位 if args.compression: args.quality COMPRESSION_LEVELS[args.compression] src_path Path(args.path) if not src_path.exists(): print(f[错误] 路径不存在: {src_path}) return images collect_images(src_path, args.recursive) if not images: print([提示] 没有找到支持的图片文件) return print(f[信息] 共找到 {len(images)} 张图片开始处理...) for img_path in images: process_single_image(img_path, args) print(f[信息] 全部处理完成输出目录请查看 {src_path / output}) if __name__ __main__: main()3.4 核心代码逻辑逐段拆解这段代码看起来不长但每一个模块都值得认真解释尤其是几个容易出问题的地方。先看parse_args函数。我用的是Python标准库argparse它对命令行参数的解析支持非常完善定义一个参数只需要寥寥几行。这里面的关键是参数的默认值和类型约束。width和height默认是0语义是“未指定”不处理尺寸。为什么不直接用None当默认值因为argparse里用None更麻烦整数默认0判断起来反而简洁。quality默认90这个值是我实测下来比较均衡的一个档位85以下在文字边缘会出现可见的压缩噪点95以上体积又降不了多少。process_single_image是整个工具的核心处理函数。它做的事情按顺序拆开看打开图片文件记录原始尺寸和原始文件体积调整尺寸确定输出格式确定输出目录保存文件最后打印处理结果。这个顺序是我反复调整之后才定下来的重点是先调整尺寸再确定输出格式最后保存。因为调整尺寸会改变Image对象的尺寸属性保存时自然用新尺寸。再仔细看adjust_size这个函数。这里我做了两层判断如果用户同时传了width和height那就用resize强制拉伸。如果只传了一个那就等比缩放。等比缩放的算法是先算出目标尺寸和原图的宽度比值然后按比例算出新宽高。这里有一个坑要注意ratio计算用的是宽度所以不管用户传的是width还是height都先算出新宽高再按实际指定的是哪个来赋值。上面代码里我写的这段逻辑有点冗余两个分支其实做了同样的事情在实际项目中可以精简成一个分支但为了逻辑清晰我保留了这个写法便于初学者看清楚每一步在干什么。Image.LANCZOS是重采样滤波器的选择。Pillow提供多种重采样算法比如NEAREST、BILINEAR、BICUBIC、LANCZOS。简单理解算法越高级缩放后的图片越平滑边缘质量越好但处理时间也越长。LANCZOS是Pillow里质量最高的重采样算法适合做缩小。这里我统一用LANCZOS不用去纠结什么场景选什么省心。determine_output_format这个函数要留意默认值的处理。我把它设计成用户不传--format参数时输出格式从原图格式推断。但这里有个特殊场景原图片就是PNG用户不指定格式那么输出的还是PNG尺寸调整和压缩对PNG基本不起作用体积变化也不明显。所以更合理的默认行为其实是用户不传格式时默认输出JPG这样压缩能生效。但为了不让人困惑我在代码里保留了“不指定就保持原格式”的逻辑这也是一个工具设计上值得琢磨的地方。你完全可以改成默认输出JPG只需要把determine_output_format里的一行逻辑改一改就行。collect_images函数的逻辑很直观判断路径是文件还是文件夹文件就检查后缀名文件夹就列表遍历。这里用了Path.rglob和Path.glob两个方法前者递归遍历子文件夹后者只遍历当前文件夹。rglob(*)模式会匹配所有文件再用is_file()过滤掉目录。要注意的是glob方法在Python 3.8以上版本对文件夹的匹配行为有一些调整直接匹配文件的时候需要带is_file()判断否则如果文件夹里有子目录可能会把子目录也带进来。3.5 单张图片转换的完整执行过程我把实际跑一张图片的过程写出来大家能直观感受到工具输出是什么样。假设我有一张名为photo.jpg的图片原始尺寸是4032x3024体积2.4MB。python img_tool.py photo.jpg --width 1600 --format webp --quality 80运行结果[信息] 共找到 1 张图片开始处理... [完成] photo.jpg - photo.webp 尺寸: (4032, 3024) - (1600, 1200) 体积: 2.4 MB - 412.3 KB (-83.2%) [信息] 全部处理完成输出目录请查看 /Users/me/images/output这个结果我非常满意。2.4MB的照片缩放到1600宽转成WebP体积变成400多KB网页加载速度肉眼可见地提升。这里有个细节等比缩放计算出来高度是1200因为4032x3024的比例是4:31600宽度对应1200高度完全正确。再看一张PNG截图的情况python img_tool.py screenshot.png --compression high因为我没传格式默认保持Png执行结果是尺寸不变体积从800KB变成780KB左右。这是因为PNG是无损格式quality参数根本不参与PNG的保存流程Png文件的大小基本不变。这时候用户看到体积没降可能会疑惑。所以在代码里我做了一个处理当输出格式是PNG时打印一行提示说质量参数对PNG无效。3.6 批量转换文件夹的完整执行过程批量处理是工具最核心的使用场景因为我们正常情况下都会把一个文件夹的图片同时处理完。假设我有这样一个目录结构photos/ ├── IMG_001.jpg ├── IMG_002.jpg ├── IMG_003.png └── sub/ ├── IMG_004.jpg └── IMG_005.jpg执行python img_tool.py photos --width 1200 --format webp --quality 85 --recursive处理结果是photos目录下所有jpg、png都变成webp输出到photos/output目录下·sub目录里的图片也会被递归处理到在输出目录下保留同样的子目录结构。这就是rglob的作用不加--recursive参数的话sub目录里的两张图片就不会被处理。这个批量能力配合尺寸调整和压缩日常处理需求基本上全覆盖了。几十上百张图片一口气处理完总耗时也就十几秒到几十秒比一张张手动打开软件另存为舒服太多了。4. 常见问题与排查技巧实录4.1 Pillow版本不一致导致的WebP保存报错我遇到过这样一个问题代码在本地跑得好好的一放到另一台电脑上就报错提示“cannot write mode RGBA as JPEG”或者“encoder error -2 when writing image file”。排查下来发现是两台机器的Pillow版本不同旧版本对WebP的alpha通道支持不完善。WebP格式本身支持透明通道所以RGBA模式的图片保存成WebP是没问题的。但如果你在旧版Pillow里保存WebP时传了WebP特有的参数或者图片的透明通道处理方式不对就可能触发编码器错误。这里有两个解决方案第一升级Pillow到最新版pip install --upgrade Pillow第二在保存前做一个模式转换统一转成RGB模式放弃透明通道。代码是if img.mode RGBA: img img.convert(RGB)这个处理方式简单粗暴适合不需要透明信息的场景。但如果你需要保留透明通道就不能转RGB而是要确保Pillow版本足够新然后直接保存RGBA模式的WebP。4.2 JPEG压缩时边角出现噪点和色块这个问题我在压缩扫描件和截图时遇到过很多次尤其是图片里有大段纯色区域或者细小的文字线条时quality设到80以下JPEG压缩会在边缘产生明显的振铃效应看起来就是一圈噪点。处理思路有三个优先级。优先选无损格式如果图片类型是截图、扫描件、文字稿直接输出PNG或WebP无损模式从根本上避免有损压缩的缺陷。其次如果必须用JPEG把quality提高到90以上虽然体积会大一点但视觉质量明显提升。最后可以在保存前对图片做轻度平滑处理Pillow里提供了ImageFilter.SMOOTH滤镜但这个操作会让图片整体变模糊一般不建议用在文字类图片上。4.3 批量处理时遇到不支持的文件导致整个任务中断真实场景里文件夹里经常会混入一些格式奇怪的图片文件比如raw格式的照片、带alpha通道的tiff文件、或者已经被损坏的jpg文件。我的第一批代码在遇到这类文件时直接抛出异常整个程序停在那里后面所有图片都处理不了了。后来我改成了try-except结构process_single_image的开头就包了一层异常捕获单张图片出错只打印错误信息然后继续处理下一张。这个改动看似简单但实际使用体验完全不一样。批量处理场景里某一个文件出错后任务继续跑完输出结果里提示哪些文件失败了最后再集中看失败原因比全部卡死的体验好太多。4.4 输出文件名冲突和覆盖问题还有一个容易被忽略的细节如果源文件夹里本来就有photo.jpg和photo.png两张图片批量转成WebP后两张都会生成photo.webp后处理的覆盖先处理的最后只剩一张。这会导致图片意外丢失我不敢说每个新手都会踩但确实容易发生。我现在的处理方案是当目标文件名已经存在时自动在文件名后加序号重命名。比如生成photo_1.webp、photo_2.webp。这样既保证不覆盖已有文件也保留了所有处理结果。这个逻辑放在保存之前判断if out_path.exists(): base out_path.stem for i in range(1, 100): new_name f{base}_{i}{out_path.suffix} candidate out_path.parent / new_name if not candidate.exists(): out_path candidate break这段代码用在个人工具里足够可靠不需要引入复杂的文件锁或者数据库简单直接解决问题。4.5 显卡、内存相关的不存在但CPU占用高得离谱的处理批量处理大量高清图片时Pillow会占用不少内存。如果图片特别大比如5000万像素的RAW图一张就可能吃掉几个G内存多张并发处理就更夸张。我处理的方式是限制单次任务数量或者降低图片初始解码时的分辨率。Pillow支持在打开图片时就设置一个最大尺寸超过的自动降低清晰度这在处理超大图片时能省下大量内存Image.MAX_IMAGE_PIXELS 100000000 img Image.open(img_path)5. 实操总结与自定义扩展方向5.1 把这套工具扩展成自己习惯样子的思路代码写完了功能也跑通了但作为一篇进阶教程最后我想聊聊这个工具还能怎么扩展以及怎么做才符合你自己的使用习惯。最简单的一个扩展是增加输出格式到ICO。我写博客时经常需要生成favicon.ico都是打开在线工具搞定其实用Pillow也能做。Pillow对ICO格式的支持还挺好直接把PNG保存成ICO就行。加一行代码的事情却能省下一个使用场景。再延伸一点你还可以把工具改成拖拽使用的形态。Windows用户可以写一个bat脚本直接拖图片到脚本图标上就能执行不需要打开命令行敲命令。macOS用户可以用Automator做一个文件夹动作往文件夹里放图就自动转换配合这台工具基本等于拥有一个半自动的图片处理流水线了。还有一个我建议你仔细研究的方向把Command行参数改造为交互式问答。对于完全不想记参数的人直接运行工具后工具一步步问“输入文件路径是什么”“目标宽度是多少”“输出格式选哪个”用input函数实现逻辑比argparse更直观。这个改动会让工具的易用性大幅提升适合拿给完全不懂命令行的朋友用。5.2 我在实际使用中体会到的三个核心心得第一个心得工具的价值不在于代码多复杂而在于能稳定解决一个重复性的问题。我这个转换器代码总共不到两百行但几乎每周都在用。写博客配图用它、给朋友处理手机照片用它、给网站做图片优化还用同一个工具。这种高频使用会让你的代码水平快速提升因为每次用都会发现顺手的地方和不顺手的地方顺手就是迭代方向。第二个心得要敢于改自己的代码。很多人写完工具之后再也不碰了等到功能不够用宁可重新写一个也不去改旧的。这种心态对学习很不利。我建议你每次使用工具时都养成记录“这里要是能XX就更好了”的习惯然后趁热打铁把需求实现掉。改代码不影响项目整体结构的前提下是对理解编程逻辑最好的训练。第三个心得命令行工具的学习性价比非常高。现在很多人一上来就学图形界面开发或者Web开发但命令行是程序员的母语即便是写给自己用的脚本命令行参数设计也一样能锻炼API设计能力。这个转换器里的参数怎么命名、默认值怎么设、帮助信息怎么写这些思考过程在你以后做任何项目的时候都用得上。5.3 继续进阶的路径建议这个工具目前能用的版本就到这里它的下一个进阶方向是什么我给你的建议是往批处理并发方向走。目前代码是单线程逐张处理图片面对几十张图片时速度尚可但要处理上千张高清图耗时就会到分钟级别。这时候可以引入concurrent.futures线程池把process_single_image丢到线程池里并发执行速度立刻翻几倍。这个是Python并发编程的入门练手项目我认为非常值得做。如果你想走得更远还可以把工具做成带GUI的桌面程序用tkinter或者PyQt5给工具加上一个窗口界面让它变成一个真正意义上的产品。不过这个工程量不小等到把命令行版本的逻辑吃透了再动手会顺手很多。文章写到这里我的这个进阶版转换器也就完整演示结束了。我自己的经验是把能用的工具做出来然后持续在真实使用中打磨这是零基础进阶最可靠的路线比啃一本几百页的教材要快得多也踏实得多。