☰
Python中Parser的超详细用法实例
2026/10/28 3:28:52 网站建设 项目流程

前言


标题里的「Parser」在 Python 语境下通常指命令行参数解析器。标准库里有专门的argparse模块,但不少初学者看到「Parser」这个词会先想到语法解析(parsing),于是去翻ast、ply之类,绕了远路。本文按最主流的理解来写:用argparse构建命令行参数解析器,把位置参数、可选参数、type与choices、nargs、default、子命令、parse_args与parse_known_args的区别讲透。如果你的实际需求是解析表达式或源码语法,那要用的不是argparse,是别的工具。


另一个常见误解是「参数不多,自己切sys.argv就够了」。手写版本一旦要支持--key=value、短选项、-h帮助、类型转换、缺参报错,很快就会写成一团。argparse把这些都内置了,而且自动生成-h/--help,不需要你写一行帮助文本的模板。


本文示例基于 Python 3.8 及以上;3.9 才有的exit_on_error、BooleanOptionalAction,以及更新版本才有的能力,都会单独注明。另外,Python 2.7 已于2020 年 1 月 1 日停止维护,它的print是语句而非函数,老教程里在parse_args()后面写print args.times的写法在 Python 3 里是语法错误,必须写成print(args.times);下文所有示例都用函数形式。


一、最小解析器与自动生成的 -h


# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser(
prog="demo", # 帮助里显示的程序名;默认取 sys.argv[0]
description="一个演示用的小工具。",
)
parser.add_argument("name", help="要处理的名字")
parser.add_argument("-n", "--times", type=int, default=1, help="重复次数")

args = parser.parse_args()

for _ in range(args.times):
print(f"你好,{args.name}")

存成demo.py后:


python demo.py 世界
python demo.py 世界 -n 3
python demo.py --help

parse_args()默认从sys.argv[1:]取参数,返回一个Namespace对象,取值用属性访问。只要构造ArgumentParser时没把add_help设为False,-h与--help就会自动出现,且不会占用你自己定义的选项名(想用-h干别的事就得先add_help=False)。


二、位置参数与可选参数


add_argument的完整形式是:


ArgumentParser.add_argument(name or flags..., *[, action][, nargs][, const]
[, default][, type][, choices][, required][, help][, metavar][, dest]
[, deprecated])

判断位置参数还是可选参数,只看名字里有没有前缀字符(默认是-):


# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("src", help="源文件(位置参数,必填)")
parser.add_argument("dst", nargs="?", default="out.txt",
help="目标文件(位置参数,可省略,默认 out.txt)")
parser.add_argument("-v", "--verbose", action="store_true",
help="是否输出详细信息(store_true 得到一个布尔)")
parser.add_argument("--level", type=int, default=1, help="级别")

几条容易记错的规则:



  • 位置参数默认必填,且不受default影响(nargs="?"或nargs="*"时才会用到default)。

  • 可选参数的dest默认取第一个长选项名,并把-换成_:--max-size对应args.max_size。所以要访问args.times而不是args.times之外的花样写法。

  • 想定义「必须给的可选参数」,用required=True。文档说明这种情况下default会被忽略。




写法类型取值方式备注



add_argument("x")位置args.x默认必填

add_argument("x", nargs="?")位置args.x可省略时才用default

add_argument("-f", "--foo")可选args.foodest取foo

add_argument("--max-size")可选args.max_size-变_

add_argument("--flag", action="store_true")可选args.flag出现即True



三、type、choices 与 default


type是一个可调用对象,argparse拿原始字符串去调它,返回值就是最终值。传int、float、open之类都行:


# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=8000)
parser.add_argument("--ratio", type=float, default=0.5)
parser.add_argument("--mode", choices=("debug", "release"), default="debug")
args = parser.parse_args()
print(args.port, args.ratio, args.mode)


  • type转换失败时会自动报错并退出,提示信息里带上原始输入和期望的类型。所以--port abc不会让你的代码拿到一个字符串,而是直接失败。

  • choices限定取值集合,取值不在其中会报错,并把可选值列出来。

  • 两者不要机械叠加:type=int, choices=[1, 2, 3]是「先转 int 再判断是否在集合里」,逻辑上通顺;但type=int, choices=["1", "2"]永远不可能命中,因为转换后是整数。


default的值不会再经过type转换。也就是说default="8000"配type=int时,用户不给--port拿到的会是字符串"8000"而不是整数 8000。要保证类型一致,default就自己写对类型。


Python 3.9 起可以用argparse.BooleanOptionalAction一次性得到--foo/--no-foo两个方向:


# 适用于 Python 3.9+
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--cache", action=argparse.BooleanOptionalAction,
default=True)
args = parser.parse_args(["--no-cache"])
print(args.cache) # False

3.8 没有这个 action,等价写法是分别定义--cache与--no-cache两个store_true/store_false参数,并让它们指向同一个dest。


四、nargs 的几种取值


nargs控制「这个参数吃几个命令行词」:




取值含义结果类型



不写刚好一个单个值

"?"0 或 1 个单个值或default

"*"0 个或多个列表

"+"1 个或多个列表

整数N恰好 N 个列表



# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--tags", nargs="*", default=[], help="零个或多个标签")
parser.add_argument("--pair", nargs=2, type=int, help="恰好两个整数")
parser.add_argument("files", nargs="+", help="一个或多个文件")
args = parser.parse_args()

print(args.tags) # 列表
print(args.pair) # 长度为 2 的列表,或 None
print(args.files) # 至少一个元素的列表

有个细节值得记牢:nargs="*"配default时,如果命令行完全没有出现这个参数,得到的是default;一旦出现(哪怕后面一个值都不跟),得到的是空列表。这是nargs="*"与nargs="?"行为不同的地方,写「默认用某组值、显式给空表示不用」的逻辑时容易踩到。


五、子命令 add_subparsers


当一个程序有多个子功能、每个子功能的参数又不一样时(git commit、git checkout那样),用add_subparsers:


# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser(prog="tool")
parser.add_argument("--verbose", action="store_true")
sub = parser.add_subparsers(dest="command", required=True)

p_add = sub.add_parser("add", help="新增一条")
p_add.add_argument("text", help="要新增的内容")

p_list = sub.add_parser("list", help="列出全部")
p_list.add_argument("--limit", type=int, default=10)

args = parser.parse_args()
print(args.command)

要点:



  • add_subparsers()返回一个特殊对象,它只有一个常用方法add_parser(名字, **ArgumentParser 构造参数),返回一个普通的ArgumentParser。

  • 传入dest="command",解析结果里才会有「用户选了哪个子命令」这个属性;不传的话不会自动加。

  • 传required=True(Python 3.7 起支持)可以要求必须选一个子命令。

  • 文档特别提醒:解析结果里只包含被选中的那个子解析器的属性,其他子解析器的属性不会出现;被选中的子命令的-h也只打印它自己的帮助。

  • 若子解析器和父解析器定义了同一个dest,两者会共用同一个命名空间属性,父解析器原来的值会被覆盖。给它们不同的dest才是稳妥做法。


六、parse_args 与 parse_known_args


parse_args(args=None, namespace=None)遇到不认识的参数会报错并退出;parse_known_args(args=None, namespace=None)则不报错,而是把不认识的部分原样返回。官方示例:


>>> parser.parse_known_args(['--foo', '--badger', 'BAR', 'spam'])
(Namespace(bar='BAR', foo=True), ['--badger', 'spam'])

返回值是一个二元组:(命名空间, 剩余的未知参数列表)。当你写的脚本是某个更大程序的一环、需要把不认识的参数透传给下游时,这个特性很有用:


# 适用于 Python 3.8+
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--mine", action="store_true")

args, rest = parser.parse_known_args()
print("自己的参数:", args.mine)
print("透传给下游的:", rest)

文档也提醒了一个边界:parse_known_args有时会「吃掉」一个其实属于下游的前缀选项,因为解析器可能把某个已知选项的前缀当成它自己。所以透传场景要尽量让两边选项名不撞车,或者显式用--分隔。


七、报错与自定义错误


默认情况下,参数不合法时argparse会打印用法和错误信息,然后抛SystemExit直接结束进程。Python 3.9 起可以用exit_on_error=False改变这个行为,让它改抛ArgumentError,便于在测试里捕获:


# 适用于 Python 3.9+
import argparse

parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument("--n", type=int)

try:
args = parser.parse_args(["--n", "abc"])
except argparse.ArgumentError as exc:
print("解析失败:", exc)

另外还有parser.error(message),它会打印用法并把message作为错误输出,同样以SystemExit结束——适合在你自己做完额外校验后发现参数组合不合理时调用。Python 3.14 起,帮助与错误信息默认用 ANSI 颜色输出(color参数可关闭),并且新增了suggest_on_error,能在选项拼错时给出「也许你想输入的是……」的提示。


常见坑点


坑点 1:default不会走type转换。❌parser.add_argument("--port", type=int, default="8000")—— 用户不传时拿到字符串"8000",后面port + 1就炸。 ✅ 把默认值写成正确类型:default=8000。校验参数类型时别只看命令行分支,default分支同样要照顾。


坑点 2:以为位置参数也可以用default省略。❌parser.add_argument("src", default="a.txt")后不传src—— 仍是必填,会报错退出。 ✅ 想让它可省,配nargs="?";default只在nargs="?"/nargs="*"时才生效。


坑点 3:choices与type组合顺序想当然。❌type=int, choices=["1", "2", "3"]—— 转换后是整数,永远匹配不上字符串集合。 ✅type=int, choices=[1, 2, 3];或用type=str保持字符串比较。


坑点 4:在可复用模块里让parse_args直接退进程。❌ 在被别人import的函数里调parse_args()—— 参数一错就抛SystemExit,把调用方的进程也带走了。 ✅ 传入明确的列表parse_args(argv),或把解析和main()分开;测试里用exit_on_error=False(3.9+)。


坑点 5:子解析器和父解析器用了同一个dest。❌ 父解析器有--debug(dest 即debug),子解析器又定义了一个debug—— 后解析的会覆盖前者,父值丢失。 ✅ 给它们不同的dest,例如子命令里写成--debug但用dest="sub_debug"。


坑点 6:以为parse_known_args一定把不认识的参数留全。❌ 指望它把下游的--foo原样留着 —— 若你的解析器有--foobar,--foo可能被当成缩写吃掉。 ✅ 关掉缩写(allow_abbrev=False)或用--显式分隔,别让两套选项名互相前缀。


坑点 7:required=True和default一起用。❌add_argument("--mode", required=True, default="debug")—— 用户不传照样报错,default形同虚设。 ✅ 要么必填、不要默认值;要么可选、用默认值。两者语义冲突,文档也说必填时default被忽略。


坑点 8:忘了add_help=True会占用-h。❌ 自己想用-h表示--host,结果跑起来报冲突。 ✅ 构造时add_help=False,再自定义-h/--help;或把主机选项改成-H。


总结




需求用法关键点



一个必填的输入add_argument("name")位置参数默认必填

可选的输入add_argument("name", nargs="?")这时default才生效

开关action="store_true"得到布尔值

类型转换type=int失败自动报错退出,default不转换

限定取值choices=(...)与type的顺序要相配

多个值nargs="+" / "*" / N结果是列表

子命令add_subparsers(dest=...)只出现被选中分支的属性

透传未知参数parse_known_args()返回(Namespace, 剩余列表)

测试期不退出exit_on_error=False需要 Python 3.9+



argparse的价值是把「解析 → 校验 → 报错 → 帮助」这条链子标准化:你只负责声明参数长什么样,剩下的事它按一致的方式处理。用之前先想清楚三件事——哪些必填、哪些有默认值、默认值的类型对不对;用之后记得把解析结果当成数据对象(Namespace)往上抛,而不是到处sys.argv乱翻。这样脚本长大以后也不会失控。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询