Python argparse 子命令的依赖与冲突
我想用 argparse 来创建一个带有子命令的工具。可能的用法示例如下:
/tool.py download --from 1234 --interval 60
/tool.py download --build 1432
/tool.py clean --numbers 10
所以我想用 argparse 来实现以下功能:
- 确保 '--from' 和 '--interval' 总是一起使用
- 确保 '--build' 不能和其他参数一起使用
但是我找不到方法把 '--from' 和 '--interval' 组合成一组,然后让这一组和 '--build' 互斥。
下面是我现在的代码,它只让 '--from' 和 '--build' 互斥。既没有确保 '--from' 和 '--interval' 一起出现,也没有确保 '--interval' 和 '--build' 互斥。
parser = argparse.ArgumentParser(description='A Tool')
subparsers = parser.add_subparsers(help='sub-command help')
#create the parser for the 'download' command
download_parser = subparsers.add_parser('download', help='download help')
download_parser.add_argument('--interval', dest='interval', type=int,help='interval help')
group = download_parser.add_mutually_exclusive_group()
group.add_argument('--from',type=int, help='from help')
group.add_argument('--build', type=int, help='interval help')
举个例子,
/tool.py download --from 1234
这个用法是不允许的,因为 '--from' 必须和 '--interval' 一起使用。但我的代码却默默接受了这个用法。
还有,
/tool.py download --interval 1234 --build 5678
这个用法也不允许,因为 '--build' 不能和其他参数一起使用。但我的代码也接受了这个。
任何建议都非常感谢。谢谢。
1 个回答
6
你可以使用自定义动作来实现这个功能:
import argparse
import sys
class VerifyNoBuild(argparse.Action):
def __call__(self, parser, args, values, option_string=None):
# print 'No: {n} {v} {o}'.format(n=args, v=values, o=option_string)
if args.build is not None:
parser.error(
'--build should not be used with --from or --interval')
setattr(args, self.dest, values)
class VerifyOnlyBuild(argparse.Action):
def __call__(self, parser, args, values, option_string=None):
# print 'Only: {n} {v} {o}'.format(n=args, v=values, o=option_string)
if getattr(args, 'from') is not None:
parser.error('--from should not be used with --build')
if getattr(args, 'interval') is not None:
parser.error('--interval should not be used with --build')
setattr(args, self.dest, values)
parser = argparse.ArgumentParser(description='A Tool')
subparsers = parser.add_subparsers(help='sub-command help')
# create the parser for the 'download' command
download_parser = subparsers.add_parser('download', help='download help')
download_parser.add_argument('--interval',
type=int, help='interval help',
action=VerifyNoBuild)
download_parser.add_argument('--from',
type=int, action=VerifyNoBuild)
download_parser.add_argument('--build',
type=int, action=VerifyOnlyBuild)
args = parser.parse_args('download --from 1234 --interval 60'.split())
print(args)
# Namespace(build=None, from=1234, interval=60)
args = parser.parse_args('download --build 1432'.split())
print(args)
# Namespace(build=1432, from=None, interval=None)
args = parser.parse_args('download --build 1432 --from 1234'.split())
print(args)
# usage: test.py download [-h] [--interval INTERVAL] [--from FROM] [--build BUILD]
# test.py download: error: --build should not be used with --from or --interval
args = parser.parse_args('download --build 1432 --interval 60'.split())
print(args)
# usage: test.py download [-h] [--interval INTERVAL] [--from FROM] [--build BUILD]
# test.py download: error: --build should not be used with --from or --interval
不过我觉得其实有更简单、更短的方法:
def parse_options():
parser = argparse.ArgumentParser(description='A Tool')
subparsers = parser.add_subparsers(help='sub-command help')
#create the parser for the 'download' command
download_parser = subparsers.add_parser('download', help='download help')
download_parser.add_argument('--interval', type=int, help='interval help')
download_parser.add_argument('--from', type=int)
download_parser.add_argument('--build', type=int)
opt=parser.parse_args()
from_interval=[getattr(opt,key) is not None for key in ('from','interval')]
if opt.build is not None:
if any(from_interval):
sys.exit('error!')
elif not all(from_interval):
sys.exit('error!')
return opt