在 argparse 中使用 GNU 风格的长选项(不要将可选参数与位置混淆)
Using GNU-style long options in argparse (not confusing optional argument with positional)
例如,对于 GNU ls
,您可以使用 --color[=WHEN]
选项控制着色。现在在这种情况下,等号是至关重要的,因为 ls
必须区分 --color
的可选参数和位置参数(即要列出的文件)。即 ls --color
列出带有颜色的文件,这与 ls --color=always
相同,但 ls --color always
将列出文件 always
(并带有颜色)。
现在,据我所见,argparse
似乎也接受使用 --longopt <argument>
语法的长选项参数,这将导致无法使参数成为可选参数。也就是说,如果我尝试使用与 GNU ls
相同的行为来实现 myls
(这只是一个示例),我会 运行 遇到问题,因为现在 myls --color always
与 myls --color=always
(而不是不带参数的 --color
和 always
作为位置参数的要求)。
我知道我可以通过使用 myls --color -- always
来规避这个问题,但是如果没有这个变通办法,难道没有办法使它工作吗?那就是告诉 argparse
--color
的参数必须用 --color[=WHEN]
语法提供。
请注意,我不想依赖 --color
选项具有有限数量的有效参数这一事实。这是我尝试过但无法正常工作的示例:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--foo",
action="store",
nargs="?")
parser.add_argument("frob",
action="store",
nargs=argparse.REMAINDER)
print(parser.parse_args(["alpha", "beta"]))
print(parser.parse_args(["--foo", "alpha", "beta"]))
print(parser.parse_args(["--foo=bar", "alpha", "beta"]))
输出:
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo='alpha', frob=['beta'])
Namespace(foo='bar', frob=['alpha', 'beta'])
注意第二个 alpha
被解释为 --foo
的参数。我想要:
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo='bar', frob=['alpha', 'beta'])
也许 nargs
会有所帮助。
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--color', nargs='?', const='c', default='d')
>>> parser.parse_args(['XX', '--color', 'always'])
Namespace(bar='XX', color='always')
>>> parser.parse_args(['XX', '--color'])
Namespace(bar='XX', color='c')
>>> parser.parse_args([])
Namespace(bar='d', color='d')
使用 nargs,你会得到不同的 args,你会知道输入类型是什么。
顺便说一下,我认为 --color
选项可以使用 action='store_true'
。
parser.add_argument('--color', action='store_true')
显然这是不可能的。 GNU getopt() (man getopt
, man 3 getopt
) 支持此行为。 man getopt
说:
If the [long] option has an optional argument, it must be written directly after the long option name, separated by '=', if present
Python getopt
模块,但是,很明显它不支持这个:
Optional arguments [in long options] are not supported.
对于argparse
我没有在手册中找到任何具体参考,但如果它支持它我会感到惊讶。事实上,我很惊讶 GNU getopt 支持它并且 ls
按照你描述的方式工作。用户界面应该简单,而这种行为远非简单。
解决方法如下:
#!/usr/bin/python
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("files", nargs="*", help="List of files", type=str)
parser.add_argument('--color', dest='color', action='store_true')
parser.add_argument('--color=ALWAYS', dest='color_always', action='store_true')
args = parser.parse_args()
print args
结果:
[~]$ ./test.py xyz --color
Namespace(color=True, color_always=False, files=['xyz'])
[~]$ ./test.py xyz --color=ALWAYS
Namespace(color=False, color_always=True, files=['xyz'])
您可能已经尝试过 ?
可选后跟必需的位置:
p=argparse.ArgumentParser()
p.add_argument('--foo', nargs='?',default='one', const='two')
p.add_argument('bar')
失败
In [7]: p.parse_args('--foo 1'.split())
usage: ipython3 [-h] [--foo [FOO]] bar
ipython3: error: the following arguments are required: bar
--foo
消耗了 1
,没有为 bar
.
留下任何东西
http://bugs.python.org/issue9338讨论了这个问题。 nargs='?'
是贪婪的,消耗一个参数,即使下面的位置需要一个。但是建议的补丁很复杂,所以我不能快速将它应用到解析器并测试你的案例。
定义一个可以与 --foo==value
一起使用但不在 --foo value
中消耗 value
的操作的想法很有趣,但我不知道需要什么实行。当然它不适用于当前的解析器。我必须回顾一下它如何处理明确的 =
.
==============================
通过更改 parse_args
、
中的深层嵌套函数
def consume_optional(....):
....
# error if a double-dash option did not use the
# explicit argument
else:
msg = _('ignored explicit argument %r')
#raise ArgumentError(action, msg % explicit_arg)
# change for stack40989413
print('Warn ',msg)
stop = start_index + 1
args = [explicit_arg]
action.nargs=None
action_tuples.append((action, args, option_string))
break
并添加自定义操作 class:
class MyAction(myparse._StoreConstAction):
# requies change in consume_optional
def __call__(self, parser, namespace, values, option_string=None):
if values:
setattr(namespace, self.dest, values)
else:
setattr(namespace, self.dest, self.const)
我可以从以下位置获得所需的行为:
p = myparse.ArgumentParser()
p.add_argument('--foo', action=MyAction, const='C', default='D')
p.add_argument('bar')
基本上我正在修改 store_const
以保存 =explicit_arg
(如果存在)。
我不打算将此作为正式补丁提出,但如果有用,我欢迎提供反馈。使用风险自负。 :)
问题已解决!
这有点(很多)hacky,但是你开始吧。
解决方案围绕 class 展开,它继承了 _StoreConstAction 并稍作调整,但主要是在帮助格式化程序尝试获取其属性时欺骗它。
我在 windows 和 linux 下的 python3 中测试了这个。
import argparse
import inspect
class GnuStyleLongOption(argparse._StoreConstAction):
def __init__(self, **kw):
self._real_option_strings = kw['option_strings']
opts = []
for option_string in self._real_option_strings:
opts.append(option_string)
for choice in kw['choices']:
opts.append(f'{option_string}={choice}')
kw['option_strings'] = opts
self.choices = kw.pop('choices')
help_choices = [f"'{choice}'" for choice in self.choices]
kw['help'] += f"; {kw['metavar']} is {', or '.join([', '.join(help_choices[:-1]), help_choices[-1]])}"
super(GnuStyleLongOption, self).__init__(**kw)
def __getattribute__(self, attr):
caller_is_argparse_help = False
for frame in inspect.stack():
if frame.function == 'format_help' and frame.filename.endswith('argparse.py'):
caller_is_argparse_help = True
break
if caller_is_argparse_help:
if attr == 'option_strings':
return [f'{i}[=WHEN]' for i in self._real_option_strings]
if attr == 'nargs':
return 0
if attr == 'metavar':
return None
return super(GnuStyleLongOption, self).__getattribute__(attr)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const if '=' not in option_string else option_string[option_string.find('=') + 1:])
p = argparse.ArgumentParser()
p.add_argument('--color', '--colour', action=GnuStyleLongOption, choices=['always', 'never', 'auto'], const='always', default='auto', help='use markers to highlight whatever we want', metavar='WHEN')
p.add_argument('filenames', metavar='filename', nargs='*', help='file to process')
args = p.parse_args()
print(f'color = {args.color}, filenames = {args.filenames}')
结果:
~ $ ./gnu_argparse.py --help
usage: gnu_argparse.py [-h] [--color[=WHEN]] [filename [filename ...]]
positional arguments:
filename file to process
optional arguments:
-h, --help show this help message and exit
--color[=WHEN], --colour[=WHEN]
use markers to highlight whatever we want; WHEN is
'always', 'never', or 'auto'
~ $ ./gnu_argparse.py
color = auto, filenames = []
~ $ ./gnu_argparse.py file
color = auto, filenames = ['file']
~ $ ./gnu_argparse.py --color file
color = always, filenames = ['file']
~ $ ./gnu_argparse.py --color never file
color = always, filenames = ['never', 'file']
~ $ ./gnu_argparse.py --color=never file
color = never, filenames = ['file']
~ $ ./gnu_argparse.py --colour=always file
color = always, filenames = ['file']
例如,对于 GNU ls
,您可以使用 --color[=WHEN]
选项控制着色。现在在这种情况下,等号是至关重要的,因为 ls
必须区分 --color
的可选参数和位置参数(即要列出的文件)。即 ls --color
列出带有颜色的文件,这与 ls --color=always
相同,但 ls --color always
将列出文件 always
(并带有颜色)。
现在,据我所见,argparse
似乎也接受使用 --longopt <argument>
语法的长选项参数,这将导致无法使参数成为可选参数。也就是说,如果我尝试使用与 GNU ls
相同的行为来实现 myls
(这只是一个示例),我会 运行 遇到问题,因为现在 myls --color always
与 myls --color=always
(而不是不带参数的 --color
和 always
作为位置参数的要求)。
我知道我可以通过使用 myls --color -- always
来规避这个问题,但是如果没有这个变通办法,难道没有办法使它工作吗?那就是告诉 argparse
--color
的参数必须用 --color[=WHEN]
语法提供。
请注意,我不想依赖 --color
选项具有有限数量的有效参数这一事实。这是我尝试过但无法正常工作的示例:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--foo",
action="store",
nargs="?")
parser.add_argument("frob",
action="store",
nargs=argparse.REMAINDER)
print(parser.parse_args(["alpha", "beta"]))
print(parser.parse_args(["--foo", "alpha", "beta"]))
print(parser.parse_args(["--foo=bar", "alpha", "beta"]))
输出:
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo='alpha', frob=['beta'])
Namespace(foo='bar', frob=['alpha', 'beta'])
注意第二个 alpha
被解释为 --foo
的参数。我想要:
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo=None, frob=['alpha', 'beta'])
Namespace(foo='bar', frob=['alpha', 'beta'])
也许 nargs
会有所帮助。
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--color', nargs='?', const='c', default='d')
>>> parser.parse_args(['XX', '--color', 'always'])
Namespace(bar='XX', color='always')
>>> parser.parse_args(['XX', '--color'])
Namespace(bar='XX', color='c')
>>> parser.parse_args([])
Namespace(bar='d', color='d')
使用 nargs,你会得到不同的 args,你会知道输入类型是什么。
顺便说一下,我认为 --color
选项可以使用 action='store_true'
。
parser.add_argument('--color', action='store_true')
显然这是不可能的。 GNU getopt() (man getopt
, man 3 getopt
) 支持此行为。 man getopt
说:
If the [long] option has an optional argument, it must be written directly after the long option name, separated by '=', if present
Python getopt
模块,但是,很明显它不支持这个:
Optional arguments [in long options] are not supported.
对于argparse
我没有在手册中找到任何具体参考,但如果它支持它我会感到惊讶。事实上,我很惊讶 GNU getopt 支持它并且 ls
按照你描述的方式工作。用户界面应该简单,而这种行为远非简单。
解决方法如下:
#!/usr/bin/python
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("files", nargs="*", help="List of files", type=str)
parser.add_argument('--color', dest='color', action='store_true')
parser.add_argument('--color=ALWAYS', dest='color_always', action='store_true')
args = parser.parse_args()
print args
结果:
[~]$ ./test.py xyz --color
Namespace(color=True, color_always=False, files=['xyz'])
[~]$ ./test.py xyz --color=ALWAYS
Namespace(color=False, color_always=True, files=['xyz'])
您可能已经尝试过 ?
可选后跟必需的位置:
p=argparse.ArgumentParser()
p.add_argument('--foo', nargs='?',default='one', const='two')
p.add_argument('bar')
失败
In [7]: p.parse_args('--foo 1'.split())
usage: ipython3 [-h] [--foo [FOO]] bar
ipython3: error: the following arguments are required: bar
--foo
消耗了 1
,没有为 bar
.
http://bugs.python.org/issue9338讨论了这个问题。 nargs='?'
是贪婪的,消耗一个参数,即使下面的位置需要一个。但是建议的补丁很复杂,所以我不能快速将它应用到解析器并测试你的案例。
定义一个可以与 --foo==value
一起使用但不在 --foo value
中消耗 value
的操作的想法很有趣,但我不知道需要什么实行。当然它不适用于当前的解析器。我必须回顾一下它如何处理明确的 =
.
==============================
通过更改 parse_args
、
def consume_optional(....):
....
# error if a double-dash option did not use the
# explicit argument
else:
msg = _('ignored explicit argument %r')
#raise ArgumentError(action, msg % explicit_arg)
# change for stack40989413
print('Warn ',msg)
stop = start_index + 1
args = [explicit_arg]
action.nargs=None
action_tuples.append((action, args, option_string))
break
并添加自定义操作 class:
class MyAction(myparse._StoreConstAction):
# requies change in consume_optional
def __call__(self, parser, namespace, values, option_string=None):
if values:
setattr(namespace, self.dest, values)
else:
setattr(namespace, self.dest, self.const)
我可以从以下位置获得所需的行为:
p = myparse.ArgumentParser()
p.add_argument('--foo', action=MyAction, const='C', default='D')
p.add_argument('bar')
基本上我正在修改 store_const
以保存 =explicit_arg
(如果存在)。
我不打算将此作为正式补丁提出,但如果有用,我欢迎提供反馈。使用风险自负。 :)
问题已解决! 这有点(很多)hacky,但是你开始吧。
解决方案围绕 class 展开,它继承了 _StoreConstAction 并稍作调整,但主要是在帮助格式化程序尝试获取其属性时欺骗它。
我在 windows 和 linux 下的 python3 中测试了这个。
import argparse
import inspect
class GnuStyleLongOption(argparse._StoreConstAction):
def __init__(self, **kw):
self._real_option_strings = kw['option_strings']
opts = []
for option_string in self._real_option_strings:
opts.append(option_string)
for choice in kw['choices']:
opts.append(f'{option_string}={choice}')
kw['option_strings'] = opts
self.choices = kw.pop('choices')
help_choices = [f"'{choice}'" for choice in self.choices]
kw['help'] += f"; {kw['metavar']} is {', or '.join([', '.join(help_choices[:-1]), help_choices[-1]])}"
super(GnuStyleLongOption, self).__init__(**kw)
def __getattribute__(self, attr):
caller_is_argparse_help = False
for frame in inspect.stack():
if frame.function == 'format_help' and frame.filename.endswith('argparse.py'):
caller_is_argparse_help = True
break
if caller_is_argparse_help:
if attr == 'option_strings':
return [f'{i}[=WHEN]' for i in self._real_option_strings]
if attr == 'nargs':
return 0
if attr == 'metavar':
return None
return super(GnuStyleLongOption, self).__getattribute__(attr)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const if '=' not in option_string else option_string[option_string.find('=') + 1:])
p = argparse.ArgumentParser()
p.add_argument('--color', '--colour', action=GnuStyleLongOption, choices=['always', 'never', 'auto'], const='always', default='auto', help='use markers to highlight whatever we want', metavar='WHEN')
p.add_argument('filenames', metavar='filename', nargs='*', help='file to process')
args = p.parse_args()
print(f'color = {args.color}, filenames = {args.filenames}')
结果:
~ $ ./gnu_argparse.py --help
usage: gnu_argparse.py [-h] [--color[=WHEN]] [filename [filename ...]]
positional arguments:
filename file to process
optional arguments:
-h, --help show this help message and exit
--color[=WHEN], --colour[=WHEN]
use markers to highlight whatever we want; WHEN is
'always', 'never', or 'auto'
~ $ ./gnu_argparse.py
color = auto, filenames = []
~ $ ./gnu_argparse.py file
color = auto, filenames = ['file']
~ $ ./gnu_argparse.py --color file
color = always, filenames = ['file']
~ $ ./gnu_argparse.py --color never file
color = always, filenames = ['never', 'file']
~ $ ./gnu_argparse.py --color=never file
color = never, filenames = ['file']
~ $ ./gnu_argparse.py --colour=always file
color = always, filenames = ['file']