可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
While searching for faster ways to parse command-line arguments in my scripts I came across the argh library.
I really like the features of argh but I’ve encountered one drawback that stops me from using it, and this has to do with the default help message that gets displayed if I’m invoking the —help option:
per default the function’s docstring is displayed on top of the arguments list. This is great, however the initial formatting is lost. See, for example, the following example script
import argh
def func(foo=1, bar=True):
"""Sample function.
Parameters:
foo: float
An example argument.
bar: bool
Another argument.
"""
print foo, bar
argh.dispatch_command(func, argv=['-h'])
which would result in the following output
usage: script.py [-h] [-f FOO] [-b]
Sample function. Parameters: foo: float An example argument. bar: bool Another
argument.
optional arguments:
-h, --help show this help message and exit
-f FOO, --foo FOO
-b, --bar
Is there an (easy) way to get an output like the following?
usage: script.py [-h] [-f FOO] [-b]
Sample function.
Parameters:
foo: float
An example argument.
bar: bool
Another argument.
optional arguments:
-h, --help show this help message and exit
-f FOO, --foo FOO
-b, --bar
I'd prefer to not use annotations to define the argument help messages since that would require me to alter both the function's docstring AND the help text each time there is something to change.
回答1:
I'm not familiar with argh
, but apparently it is a wrapper to argparse
. My guess is that it is taking your function __doc__
, and making it the description
of a parser, e.g.
parser = argparse.ArgumentParser(description=func.__doc__)
https://docs.python.org/2.7/library/argparse.html#argparse.RawDescriptionHelpFormatter
argparse
has a RawDescriptionHelpFormatter
that displays the description as is.
parser = argparse.ArgumentParser(description=func.__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
So the question is, is there a way of getting argh
to use this formatter?
This argparse
script produces the help that you want:
import argparse
def func(foo=1, bar=True):
"""Sample function.
Parameters:
foo: float
An example argument.
bar: bool
Another argument.
"""
print foo, bar
parser = argparse.ArgumentParser(prog='script.py',
description=func.__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-f', '--foo', type=float)
parser.add_argument('-b', '--bar', action='store_false')
parser.print_help()
In argh/dispatching.py
def dispatch_command(function, *args, **kwargs):
...
parser = argparse.ArgumentParser(formatter_class=PARSER_FORMATTER)
set_default_command(parser, function)
dispatch(parser, *args, **kwargs)
So you could either set:
PARSER_FORMATTER = argparse.RawDescriptionHelpFormatter
or write your own function:
def raw_dispatch_command(function, *args, **kwargs):
...
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
set_default_command(parser, function)
dispatch(parser, *args, **kwargs)
回答2:
With the help of @hpaulj I finally managed to obtain the desired behaviour. To facilitate this I defined a custom decorator similar to argh.arg, with the goal to not have to write @argh.arg(‘—param’, help=“%(default)s”)
for each parameter separately, but instead to only use one @arg_custom()
decorator on my function:
def arg_custom():
from argh.constants import ATTR_ARGS
from argh.assembling import _get_args_from_signature, _fix_compat_issue29
def wrapper(func):
declared_args = getattr(func, ATTR_ARGS, [])
for a in list(_get_args_from_signature(func)):
declared_args.insert(0, dict(option_strings=a['option_strings'], help="(default: %(default)s)"))
setattr(func, ATTR_ARGS, declared_args)
_fix_compat_issue29(func)
return func
return wrapper
The crucial point here is that a for
loop takes care that all arguments get a corresponding help=“%(default)s”
option.
Together with changing the corresponding lines in argh/constants.py
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
pass
PARSER_FORMATTER = CustomFormatter
we can now conveniently use
@arg_custom()
def func(foo=1, bar=True):
"""Sample function.
Parameters:
foo: float
An example argument.
bar: bool
Another argument.
"""
print foo, bar
argh.dispatch_command(func)
yielding finally
usage: script.py [-h] [-f FOO] [-b]
Sample function.
Parameters:
foo: float
An example argument.
bar: bool
Another argument.
optional arguments:
-h, --help show this help message and exit
-f FOO, --foo FOO (default: 1)
-b, --bar (default: True)
when executing the script with the -h
option.
回答3:
Regarding the issue of getting defaults in the help lines, this argparse
script combines the 2 formatter classes
import argparse
def func(foo=1, bar=True):
...
"""
print foo, bar
class MyFormatter(argparse.RawDescriptionHelpFormatter,
argparse.ArgumentDefaultsHelpFormatter):
pass
parser = argparse.ArgumentParser(prog='script.py',
description=func.__doc__,
formatter_class=MyFormatter)
parser.add_argument('-f', '--foo', type=float, default=1, help='test')
parser.add_argument('-b', '--bar', action='store_false', help='test')
parser.print_help()
producing
usage: script.py [-h] [-f FOO] [-b]
Sample function.
...
optional arguments:
-h, --help show this help message and exit
-f FOO, --foo FOO test (default: 1)
-b, --bar test (default: True)
To get the defaults in the help lines I have to include some text (here 'test') in the original help line.
In argh
, you might have to use annotations to give it help text.
If you are using annotations, you give it help lines with $(default)s
:
parser = argparse.ArgumentParser(prog='script.py',
description=func.__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-f', '--foo', type=float, default=1, help='default: %(default)s')
parser.add_argument('-b', '--bar', action='store_false', help='default: %(default)s')
回答4:
Thanks for your interest in the Argh library. The solutions discussed here will be incorporated in the next release (argh ≥ 0.25). See also the issue #64 (already fixed).