如何使用Python argparse模块添加可选位置参数组
实现方案
用Python argparse实现和GNU timeout完全一致的参数规则,核心靠3个关键配置:
- 给解析器设置
allow_interspersed_args=False,禁止选项穿插在位置参数之后,强制所有选项必须放在duration前面 - 命令部分用
nargs=argparse.REMAINDER收集duration之后的所有内容,不管是否带短横线,都作为待执行命令和它的参数,避免命令自身的参数被argparse误识别为timeout的选项 - 自定义参数类型校验函数,对齐原工具的时长、信号格式校验逻辑,非法输入抛出友好错误
完整可运行的参数解析实现代码如下:
import argparse import re import signal import subprocess import sys def duration_type(value: str) -> float: """校验时长格式,转换为秒数,支持s/m/h/d后缀,默认单位为秒""" match = re.fullmatch(r'(\d+\.?\d*)([smhd]?)', value.strip()) if not match: raise argparse.ArgumentTypeError(f"invalid time interval '{value}'") num, unit = match.groups() num = float(num) unit_multiplier = { '': 1, 's': 1, 'm': 60, 'h': 3600, 'd': 86400 }[unit] return num * unit_multiplier def signal_type(value: str) -> int: """校验信号参数,支持信号名(如HUP)或数字格式""" try: return int(value) except ValueError: sig_name = value.upper() if not sig_name.startswith('SIG'): sig_name = f'SIG{sig_name}' try: return getattr(signal, sig_name) except AttributeError: raise argparse.ArgumentTypeError(f"invalid signal '{value}'") def main(): parser = argparse.ArgumentParser( description='Start COMMAND, and kill it if still running after DURATION.', usage='timeout [OPTION] DURATION COMMAND [ARG]...\n or: timeout [OPTION]', add_help=True, allow_interspersed_args=False ) # 版本参数 parser.add_argument('--version', action='version', version='timeout 1.0 (Python implementation)') # 布尔开关选项 parser.add_argument('--preserve-status', action='store_true', help='exit with the same status as COMMAND, even when the command times out') parser.add_argument('--foreground', action='store_true', help='when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out') parser.add_argument('-v', '--verbose', action='store_true', help='diagnose to stderr any signal sent upon timeout') # 带参数选项 parser.add_argument('-k', '--kill-after', type=duration_type, metavar='DURATION', help='also send a KILL signal if COMMAND is still running this long after the initial signal was sent') parser.add_argument('-s', '--signal', type=signal_type, default=signal.SIGTERM, metavar='SIGNAL', help="specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") # 位置参数 parser.add_argument('duration', type=duration_type, help='timeout duration') parser.add_argument('command', nargs=argparse.REMAINDER, help='command to run and its arguments') args = parser.parse_args() # 校验命令参数非空 if not args.command: parser.error('the following arguments are required: COMMAND') # 以下为timeout核心运行逻辑示例,可按需扩展 # proc = subprocess.Popen(args.command) # try: # proc.wait(timeout=args.duration) # except subprocess.TimeoutExpired: # if args.verbose: # print(f"timeout: sending signal {args.signal} to command {args.command[0]}", file=sys.stderr) # proc.send_signal(args.signal) # try: # proc.wait(timeout=args.kill_after) # except subprocess.TimeoutExpired: # if args.verbose: # print(f"timeout: sending signal KILL to command {args.command[0]}", file=sys.stderr) # proc.kill() # sys.exit(proc.returncode if args.preserve_status else 124) if __name__ == '__main__': main()
行为验证
- 正常带选项调用:执行
python timeout.py -v -k 2s 10s ping baidu.com -c 3,可以正确解析verbose开关、kill-after=2秒、duration=10秒,command为['ping', 'baidu.com', '-c', '3'],-c 3不会被误识别为timeout自身的参数。 - 选项放在duration之后:执行
python timeout.py 5s -v ls,会把-v和ls都归为command内容,和原GNU timeout行为一致,执行时会因找不到-v命令报错。 - 缺少command参数:执行
python timeout.py 5s,会直接报错提示缺少COMMAND参数。 - 非法参数输入:执行
python timeout.py abc ls,会提示invalid time interval 'abc',和原工具报错逻辑对齐。
内容的提问来源于stack exchange,提问作者David SB
相关产品推荐
相关产品推荐

