命令行参数解析:从sys.argv到argparse
写过脚本的人都知道,最头疼的事之一就是硬编码参数。路径写死了、IP写死了、端口号也写死了。换一台机器或者改一次环境,就得打开脚本改代码。我个人习惯是所有可变参数都从命令行传进来。今天就聊聊怎么在Python里把这件事做漂亮。
认识sys.argv:最原始的参数获取方式
sys.argv是Python解释器自动维护的一个列表。你在命令行敲的东西,会被按空格拆成字符串,放进这个列表里。以我自己为例,看看下面这个最简单的脚本:
import sys
print(f"脚本名:{sys.argv[0]}")
print(f"参数个数:{len(sys.argv) - 1}")
print(f"参数列表:{sys.argv[1:]}")
假设这个文件叫demo.py,你在终端里执行:
python demo.py --host 192.168.1.1 --port 8080
输出会是什么?
脚本名:demo.py
参数个数:4
参数列表:['--host', '192.168.1.1', '--port', '8080']
sys.argv[0]永远是脚本本身的路径。真正的参数从下标1开始。我在项目里遇到过一个坑:如果脚本路径包含空格,sys.argv[0]会包含那个路径,但跨平台行为略有差异。嗯,这里要注意。
sys.argv的用法极其简单,但功能也很有限。我刚开始写脚本时只用它,后来发现每次都要写if-else去解析参数,代码又臭又长。你想想看,如果需要5个参数,就得写5组if判断,还容易搞混位置。
argparse:帮你把参数解析变成一句话
argparse是Python标准库,专门用来解析命令行参数。我建议你用标准方式导入:
import argparse
它的核心思路是三步走:定义参数 → 解析参数 → 使用参数。先看一个极简版:
import argparse
parser = argparse.ArgumentParser(description='批量对接参数调试脚本')
parser.add_argument('--host', type=str, help='目标服务器IP')
parser.add_argument('--port', type=int, help='端口号')
args = parser.parse_args()
print(f"连接 {args.host}:{args.port}")
执行方式不变:
python demo.py --host 192.168.1.1 --port 8080
输出:
连接 192.168.1.1:8080
看到了吗?port自动变成整数了。argparse帮你处理了类型转换、参数校验,甚至帮你生成了帮助信息。执行下面这个命令试试:
python demo.py --help
输出:
usage: demo.py [-h] [--host HOST] [--port PORT]
批量对接参数调试脚本
options:
-h, --help 显示帮助信息并退出
--host HOST 目标服务器IP
--port PORT 端口号
这是我个人很偏爱的一个特性:免费帮助文档。团队里其他人用你的脚本,记不住参数就执行--help,省了多少沟通成本。
run.sh或者run.bat,里面写好完整的命令行调用。然后让argparse自动生成帮助信息。这样新人上手最快。
位置参数 vs 可选参数:选对场景很重要
argparse里参数分两种:位置参数(也叫必填参数)和可选参数(也叫命名参数)。
位置参数:必须按顺序传,不加参数名
位置参数就是使用时不需要写--xxx,直接写值。根据位置决定含义。
import argparse
parser = argparse.ArgumentParser(description='文件处理工具')
parser.add_argument('input_file', type=str, help='输入文件路径')
parser.add_argument('output_file', type=str, help='输出文件路径')
args = parser.parse_args()
print(f"从 {args.input_file} 读,写到 {args.output_file}")
执行:
python file_tool.py /tmp/in.csv /tmp/out.csv
我见过有团队把所有参数都设计成位置参数。结果就是脚本里面有十几个位置参数,调用时稍微传错一个顺序,整个结果就变了。后来花了两天重构。我以前吃过亏,现在坚持:变量参数超过2个,优先用--xxx可选参数。
可选参数:显式命名,顺序自由
可选参数必须用--或-前缀指定。你可以定义短名和长名:
import argparse
parser = argparse.ArgumentParser(description='数据库备份脚本')
parser.add_argument('-H', '--host', type=str, required=True, help='数据库地址')
parser.add_argument('-P', '--port', type=int, default=5432, help='端口号,默认5432')
parser.add_argument('--verbose', action='store_true', help='启用详细日志')
args = parser.parse_args()
if args.verbose:
print(f"连接 {args.host}:{args.port}")
else:
print("开始备份...")
执行方式灵活多样:
python backup.py -H 10.0.0.50 -P 3306 --verbose
python backup.py --host 10.0.0.50 --verbose
python backup.py -H 10.0.0.50
注意action='store_true'这个用法:你把--verbose当成一个开关,不带参数。脚本里有它就算True,没有就是False。
我写脚本时配参数的习惯是:必填项用位置参数或required=True的可选参数,可选项给默认值。让用户少打几个字,但又不失灵活性。
| 特性 | 位置参数 | 可选参数 |
|---|---|---|
| 使用时前缀 | 无,直接写值 | --xxx或-xxx |
| 顺序是否敏感 | 是,严格按定义顺序 | 否,可以任意顺序 |
| 是否可设置默认值 | 否,除非是nargs='?' | 是,default参数 |
| 是否可设为可省略 | 默认必填 | 可设为required=False |
| 典型适用场景 | 路径、文件、必须的标识 | 配置、开关、可选项 |
组合实战:一个完整的调参脚本
写一个批量对接参数调试的小工具,把上面讲的都用上:
import argparse
import json
import sys
def parse_args():
parser = argparse.ArgumentParser(
description='批量参数调试工具 v2.0',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='示例: python debug.py config.json --timeout 30 --log-level DEBUG'
)
# 位置参数:配置文件
parser.add_argument('config_file', type=str,
help='JSON格式的配置文件路径')
# 可选参数:连接相关
parser.add_argument('--timeout', type=int, default=10,
help='连接超时时间,单位秒,默认10')
parser.add_argument('--retry', type=int, default=3,
help='失败重试次数,默认3')
# 可选参数:调试开关
parser.add_argument('--log-level', type=str,
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
default='INFO',
help='日志级别,默认为INFO')
parser.add_argument('--dry-run', action='store_true',
help='仅验证参数,不执行实际对接')
# 可选参数:输出控制
parser.add_argument('--output', type=str,
help='结果输出文件,默认输出到终端')
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
print(json.dumps(vars(args), indent=2, ensure_ascii=False))
调用和输出:
python debug.py test.json --timeout 30 --log-level DEBUG --dry-run
{
"config_file": "test.json",
"timeout": 30,
"retry": 3,
"log_level": "DEBUG",
"dry_run": true,
"output": null
}
你看,choices限制了日志级别的取值范围,action='store_true'让dry-run变成一个布尔开关,default缺省值让脚本可以用最少参数就跑起来。这些设计都是在真实项目中磨出来的。我建议你记住这个模式,以后每写一个新脚本都可以快速套用。
从上面的SVG图可以清楚地看到,命令行输入的原始字符串经过sys.argv进入Python程序,然后我们通过argparse把它拆解成结构化的对象。位置参数和可选参数只是add_argument()时的一行配置不同。本质上,它们都在同一个命名空间里。
最后分享一个我最近在批量对接项目里的经验:脚本参数设计时,先定好哪些参数是本次调试必有的(比如服务器地址、认证token)——用位置参数或required=True。哪些是可以给默认值的(比如超时时间、日志级别)——用可选参数+default。这样既保证脚本不会因为缺参数就报错,又给了用户足够的自定义空间。
实战中,我再提醒一个细节:参数名的风格要统一。用了--log-level就用kebab-case到底,别中间混个--targetIp又用驼峰。argparse解析后会自动把--log-level转成args.log_level(下划线形式)。保持这个规则,你的参数接口看起来就非常专业。