需要通过Python程序运行其它应用程序,程序格式为:
我的程序 <我的程序参数> 应用程序 <应用程序参数>
由于应用程序不固定,应用程序的参数也不固定,我的程序不需要对应用程序参数进行解析,仅需要解析自己的参数。
看上去需求很简单,但试用了几个参数解析模块都不能满足此场景。比如流行的 argparse 库就无法区分我的程序参数和应用程序参数,而且如果我的程序参数和应用程序参数同名,它会认为都是我的程序参数。
因此只能自己编写自己的参数解析。
先梳理功能需求:
- 参数名称均以符号 ’-‘ 或 ’--‘ 开始,名称大小写敏感。
- 参数名称后可以跟参数值,也可以不跟参数值。
- 参数可重复,但只取最后出现的参数值。
- 不以符号 ’-‘ 开始的参数被认为是应用程序名称,其后的参数不再解析。
根据以上功能需求,编写了一个简单的工具类:
class ArgException(Exception):passclass ArgParser():def __init__(self):self.keys = ['arg', 'required', 'single', 'dest', 'help']self.args = []self.va_list = []self.usage = ''def add_arg(self, *arglist, **kargs):for k in kargs.keys():if k not in self.keys:e = f'{k} 不是合法属性。合法属性为:{" ".join(self.keys)}。'raise ArgException(e)self.args.append({'arg':arglist, **kargs})self.va_list += arglistdef find_arg(self, arg):for k in self.args:if arg in k['arg']:return kdef parse_arg(self):params = {}cmd = []emsg = ''i = 1while i < len(sys.argv):a = sys.argv[i]if a.startswith('-'):if a not in self.va_list:emsg += f"非法参数选项 {a} ,请指定合法参数选项。指定参数 -h 查看合法参数列表。\n"breakp = self.find_arg(a)if p['single']:params[p['dest']] = Truei += 1else:v = sys.argv[i+1]if v.startswith('-'):emsg += f"参数值缺失,请在参数选项 {a} 后指定参数值。\n"i += 1continueparams[p['dest']] = sys.argv[i+1]i += 2continueelse:cmd = sys.argv[i:]breakif len(emsg):raise ArgException(emsg)return params, cmddef set_usage(self, usage):self.usage += usageself.usage += '\n参数选项:\n'for k in self.args:self.usage += f"{' | '.join(k['arg']):20} {' ' if k['single'] else k['dest']:10} : {k['help']}\n"def print_usage(self):print(self.usage)
使用方法如下:
parser = ArgParser()parser.add_arg('-P', '--project', required=True, single=False, dest='project', help='指定项目名称')
首先生成类的实例,然后调用 add_arg 增加参数定义:
- 前面没有名称的为参数的合法名称,比如’-P', '--project' 都表示同一个参数;
- required 表示此参数是不是必要,True 为必要。如果没有指定会报错;
- single 表示此参数是否需要指定参数值,True 不需要指定,反之需要参数值
- dest 表示参数名称
- help 表示参数的说明
定义完参数后,可以加上参数说明
usage = '''
psub -P <project name> -q <queue_name> [-J <job name>] [-n <resource requirement>] [ -I ] [ -t <dispatch time window>] [-p <priority>] [-m <workstation>] [-W <run time limit>] [-U <resource reservation id>] cmd ...
'''parser.set_usage(usage)
第一行指定参数的使用说明。
第二行 set_usage 会从上面的参数定义中,抽取参数说明。
通过调用 print_uage 打印说明
parser.print_usage()
下面是使用示例
if __name__ == "__main__":parser = ArgParser()parser.add_arg('-P', '--project', required=True, single=False, dest='project', help='指定项目名称')parser.add_arg('-q', '--queue', required=False, single=False, dest='queue', help='指定队列名称')parser.add_arg('-J', '--jobname', required=False, single=False, dest='jobname', help='指定任务名称')parser.add_arg('-n', '--resource', required=False, single=False, dest='resource', help='指定资源需求')parser.add_arg('-I', '--interactive', required=False, single=True, dest='interact', help='要执行的任务为命令行交互式应用')parser.add_arg('-t', '--timewindow', required=False, single=False, dest='window', help='任务派发时间窗口;格式 HH:MM,HH:MM,不能跨天')parser.add_arg('-p', '--priority', required=False, single=False, dest='priority', help='指定任务优先级')parser.add_arg('-m', '--machine', required=False, single=False, dest='machine', help='指定任务运行的主机列表,格式 “machine1,machine2,machine3”')parser.add_arg('-W', '--runlimit', required=False, single=False, dest='runlimit', help='指定任务最长运行时间,默认以分钟为单位')parser.add_arg('-b', '--bookid', required=False, single=False, dest='bookid', help='指定任务使用的资源预订订单号')parser.add_arg('-h', '--help', required=False, single=True, dest='help', help='打印帮助信息')usage = '''
psub -P <project name> -q <queue_name> [-J <job name>] [-n <resource requirement>] [ -I ] [ -t <dispatch time window>] [-p <priority>] [-m <workstation>] [-W <run time limit>] [-U <resource reservation id>] cmd ...
'''parser.set_usage(usage)params, cmd = parser.parse_arg()if 'help' in params.keys():parser.print_usage()sys.exit(0)print(f"params: {params}")print(f"cmd: {cmd}")
运行效果如下
$ python pxee.py -hpxee -P <project name> -q <queue_name> [-J <job name>] [-n <resource requirement>] [ -I ] [ -t <dispatch time window>] [-p <priority>] [-m <workstation>] [-W <run time limit>] [-U <resource reservation id>] cmd ...参数选项:
-P | --project project : 指定项目名称
-q | --queue queue : 指定队列名称
-J | --jobname jobname : 指定任务名称
-n | --resource resource : 指定资源需求
-I | --interactive : 要执行的任务为命令行交互式应用
-t | --timewindow window : 任务派发时间窗口;格式 HH:MM,HH:MM,不能跨天
-p | --priority priority : 指定任务优先级
-m | --machine machine : 指定任务运行的主机列表,格式 “machine1,machine2,machine3”
-W | --runlimit runlimit : 指定任务最长运行时间,默认以分钟为单位
-b | --bookid bookid : 指定任务使用的资源预订订单号
-h | --help : 打印帮助信息
正常解析
$ python pxee.py -P prj -q adam -J myjob -I verdi -P xx -q asf -I -x -o /tmp/asfdsf
params: {'project': 'prj', 'queue': 'adam', 'jobname': 'myjob', 'interact': True}
cmd: ['verdi', '-P', 'xx', '-q', 'asf', '-I', '-x', '-o', '/tmp/asfdsf']
异常情况
$ python pxee.py -Pxe prj -q adam -J myjob -I verdi -P xx -q asf -I -x -o /tmp/asfdsf
Traceback (most recent call last):File "/Users/bytedance/scheduler_investigation/pxee.py", line 106, in <module>params, cmd = parser.parse_arg()File "/Users/bytedance/scheduler_investigation/pxee.py", line 72, in parse_argraise ArgException(emsg)
__main__.ArgException: 非法参数选项 -Pxe ,请指定合法参数选项。指定参数 -h 查看合法参数列表。
以上仅是初步原型,还需要进一步完善,比如对 required 参数检查,没有指定需要告警;增加默认值等。