【问题标题】:parsing mutually exclusive optional and positional arguments followed by pass-thru arguments解析互斥的可选参数和位置参数,后跟传递参数
【发布时间】:2019-09-22 23:18:49
【问题描述】:

我正在尝试模拟 Python 解释器的命令行行为,如帮助文本所示:

command [options] [-m mod | file] [arg] ...

即:

  • 任意数量的任意选项(形式为-[a-zA-Z],用作标志或带有单个参数)
  • 之一:
    • -m 模组
    • 文件
  • 零个或多个应按原样提供的参数

我尝试过使用内置的 argparse 模块,但没有成功。

import argparse

parser = argparse.ArgumentParser()

selector = parser.add_mutually_exclusive_group(required=True)
selector.add_argument('file', nargs='?', help='path to script')
selector.add_argument('-m', help='module name')

parser.add_argument('args', nargs=argparse.REMAINDER)

parser.parse_args(['-m', 'hello', '--', 'arg1'])

运行这个会产生

usage: test.py [-h] [-m M] [file] ...
test.py: error: argument file: not allowed with argument -m

这是有道理的,因为 argparse 似乎通常忽略选项的顺序 - 解析选项后剩余的任何位置参数都会按照指定从第一个到最后一个填充位置参数。

我已经尝试定义自定义argparse.Actions 来完成这项工作,但它最终看起来很hacky,因为与组中的一个参数对应的Action 类需要保存一个累积值以便以后包含在@ 987654327@.

我也尝试过对parser.parse_args 的输入进行预处理,但不喜欢这种方法,因为有关哪些选项具有值的信息(以将选项参数与file 参数区分开来)以及哪些选项是一组终端参数(应被视为传递参数[arg] ... 的开始)将在argparse.add_argument... 调用和预处理代码之间重复。

什么是好的方法(除了需要提供路径,例如-f)?

附加限制:

  • 我更喜欢使用argparse 或具有良好界面的东西,该界面将参数与帮助文本相关联并且加载时间不长(对我来说,argparse 导入只需 6 毫秒)
  • 我只需要兼容 Python 3.6 及以上版本即可。
  • 这并不理想,但我可以要求用户将-- 作为第一个arg,如果后续参数(将传递给模块或文件)以- 开头,或者可能是被误认为[options] 中的某些内容。

【问题讨论】:

  • 我认为如果首先定义了-m,那么mutual_exclusive 用法会正确显示。但这并不能解决file? 和用于args 的字符串之间的干扰。 file 获取第一个“位置”字符串,无论是否提供了 -m
  • 一般来说,两个变量编号为nargs 的位置不能很好地协同工作。第一个先被填满。第二个只得到“剩余”,并且可能对空列表“满意”。

标签: python argparse


【解决方案1】:

即使没有相互排斥的分组,fileargs 也不能很好地组合在一起:

In [2]: parser = argparse.ArgumentParser()                                           
In [3]: parser.add_argument('-m');                                                    
In [4]: parser.add_argument('file', nargs='?');                                      
In [6]: parser.add_argument('args', nargs=argparse.REMAINDER);   

好的:

In [7]: parser.parse_args('-m foo a b c '.split())                                   
Out[7]: Namespace(args=['b', 'c'], file='a', m='foo')

'--' 只是让我们使用 '-b' 作为纯字符串:

In [8]: parser.parse_args('-m foo a -- -b c '.split())                               
Out[8]: Namespace(args=['-b', 'c'], file='a', m='foo')

'a' 去 'file',休息到 'args' - 这是因为所有的 'contiguous' 位置都是一起评估的。对于remainder-m 标志将被忽略,并被视为纯字符串。

In [9]: parser.parse_args('a -m foo -- -b c '.split())                               
Out[9]: Namespace(args=['-m', 'foo', '--', '-b', 'c'], file='a', m=None)
In [10]: parser.parse_args('a -- -b c '.split())                                     
Out[10]: Namespace(args=['-b', 'c'], file='a', m=None)

参数分配甚至在调用 Action 之前发生,因此自定义 Action 类不会改变这种行为。

标记参数为您提供最佳控制 - 控制顺序和互斥性。

【讨论】:

  • 当我提到自定义操作类时 - 我的方法是让操作类检查是否已经满足互斥组之一,如果满足,则通过填充 type 保存参数值函数并将实际值保留在要拾取的一侧作为累积所有剩余值的参数-但这不是一个好方法,除非我对解析器本身进行子类化以保持某些状态,否则我不能多次调用解析器每次调用 parse_args 的一侧。
  • 互斥组的工作方式是在解析开始时创建一个action_conflicts 字典。然后在执行操作之前 (take_action),它会检查此 action 是否与该组中的某些内容冲突。作为测试的一部分,它维护了一个seen_non_default_actions,不幸的是,它在parse_args 函数调用之外是不可见的。
  • 如果为互斥组中除一个成员之外的所有成员返回 self.default,则他们通过了该检查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-03
  • 1970-01-01
  • 2019-02-16
  • 2013-02-18
  • 2020-11-10
  • 2018-02-18
相关资源
最近更新 更多