需要通過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 參數檢查,沒有指定需要告警;增加默認值等。