Я пишу программу, в которой я хотел бы иметь такие аргументы:
--[no-]foo Do (or do not) foo. Default is do.
Есть ли способ заставить argparse сделать это за меня?
Я использую Python 3.2
Я пишу программу, в которой я хотел бы иметь такие аргументы:
--[no-]foo Do (or do not) foo. Default is do.
Есть ли способ заставить argparse сделать это за меня?
Я использую Python 3.2
Я изменил решение @Omnifarious, чтобы сделать его более похожим на стандартные действия:
import argparse
class ActionNoYes(argparse.Action):
def __init__(self, option_strings, dest, default=None, required=False, help=None):
if default is None:
raise ValueError('You must provide a default with Yes/No action')
if len(option_strings)!=1:
raise ValueError('Only single argument is allowed with YesNo action')
opt = option_strings[0]
if not opt.startswith('--'):
raise ValueError('Yes/No arguments must be prefixed with --')
opt = opt[2:]
opts = ['--' + opt, '--no-' + opt]
super(ActionNoYes, self).__init__(opts, dest, nargs=0, const=None,
default=default, required=required, help=help)
def __call__(self, parser, namespace, values, option_strings=None):
if option_strings.startswith('--no-'):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
Вы можете добавить аргумент Да/Нет так же, как любой стандартный параметр. Вам просто нужно передать класс ActionNoYes
в аргументе action
:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action=ActionNoYes, default=False)
Теперь, когда вы это называете:
>> args = parser.parse_args(['--foo'])
Namespace(foo=True)
>> args = parser.parse_args(['--no-foo'])
Namespace(foo=False)
>> args = parser.parse_args([])
Namespace(foo=False)
Что ж, пока ни один из ответов не является вполне удовлетворительным по целому ряду причин. Итак, вот мой собственный ответ:
class ActionNoYes(argparse.Action):
def __init__(self, opt_name, dest, default=True, required=False, help=None):
super(ActionNoYes, self).__init__(['--' + opt_name, '--no-' + opt_name], dest, nargs=0, const=None, default=default, required=required, help=help)
def __call__(self, parser, namespace, values, option_string=None):
if option_string.starts_with('--no-'):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
И пример использования:
>>> p = argparse.ArgumentParser()
>>> p._add_action(ActionNoYes('foo', 'foo', help="Do (or do not) foo. (default do)"))
ActionNoYes(option_strings=['--foo', '--no-foo'], dest='foo', nargs=0, const=None, default=True, type=None, choices=None, help='Do (or do not) foo. (default do)', metavar=None)
>>> p.parse_args(['--no-foo', '--foo', '--no-foo'])
Namespace(foo=False)
>>> p.print_help()
usage: -c [-h] [--foo]
optional arguments:
-h, --help show this help message and exit
--foo, --no-foo Do (or do not) foo. (default do)
К сожалению, функция-член _add_action
не задокументирована, поэтому она не является «официальной» с точки зрения поддержки API. Кроме того, Action
в основном является классом-держателем. Он имеет очень мало поведения сам по себе. Было бы неплохо, если бы можно было использовать его для дополнительной настройки справочного сообщения. Например, говоря --[no-]foo
в начале. Но эта часть автоматически генерируется вещами вне класса Action
.
metavar
, чтобы получить --[no-]foo
.
- person Mad Physicist; 01.06.2016
startswith
вместо starts_with
?
- person sauerburger; 31.05.2017
ActionNoYes
? У меня был мгновенный мозговой сбой :D
- person corwin.amber; 29.01.2021
ActionYesNo
в своем коде, глядя на сообщение об ошибке, не понимая, почему он не может его найти ... возможно, это только я. Несмотря на это, ваш ответ и адаптация @btel были чрезвычайно полезны.
- person corwin.amber; 03.02.2021
Помогает ли add_mutually_exclusive_group()
из argparse
?
parser = argparse.ArgumentParser()
exclusive_grp = parser.add_mutually_exclusive_group()
exclusive_grp.add_argument('--foo', action='store_true', help='do foo')
exclusive_grp.add_argument('--no-foo', action='store_true', help='do not do foo')
args = parser.parse_args()
print 'Starting program', 'with' if args.foo else 'without', 'foo'
print 'Starting program', 'with' if args.no_foo else 'without', 'no_foo'
Вот как это выглядит при запуске:
./so.py --help
usage: so.py [-h] [--foo | --no-foo]
optional arguments:
-h, --help show this help message and exit
--foo do foo
--no-foo do not do foo
./so.py
Starting program without foo
Starting program without no_foo
./so.py --no-foo --foo
usage: so.py [-h] [--foo | --no-foo]
so.py: error: argument --foo: not allowed with argument --no-foo
Это отличается от следующего во взаимоисключающей группе, которая не разрешает опцию ни один в вашей программе (и я предполагаю, что вам нужны опции из-за синтаксиса --
). Это подразумевает и то, и другое:
parser.add_argument('--foo=', choices=('y', 'n'), default='y',
help="Do foo? (default y)")
Если это необходимо (необязательно), возможно, используйте add_subparsers()
— это то, что вам нужно.
Обновление 1
Логически другой, но, может быть, чище:
...
exclusive_grp.add_argument('--foo', action='store_true', dest='foo', help='do foo')
exclusive_grp.add_argument('--no-foo', action='store_false', dest='foo', help='do not do foo')
args = parser.parse_args()
print 'Starting program', 'with' if args.foo else 'without', 'foo'
И запустить его:
./so.py --foo
Starting program with foo
./so.py --no-foo
Starting program without foo
./so.py
Starting program without foo
action='store_false'
для --no-foo
и установить dest='foo'
для обоих, чтобы они отображались в одной переменной?
- person jterrace; 11.02.2012
Напишите свой собственный подкласс.
class MyArgParse(argparse.ArgumentParser):
def magical_add_paired_arguments( self, *args, **kw ):
self.add_argument( *args, **kw )
self.add_argument( '--no'+args[0][2:], *args[1:], **kw )
argparse
самому и посмотреть, как он работает внутри.
- person S.Lott; 11.02.2012
add_argument
можно рассматривать как функцию, которая создает некий объект-аргумент, представляющий все свойства аргумента... как его анализировать, в какую переменную его вставлять, значения по умолчанию, как генерировать справку и все такое прочее, и помещает его в красивый список внутри парсера. Но вы правы, я должен сам копаться во внутренностях и посмотреть, смогу ли я возиться с этим так, как я хочу. Если это не работает так, как я себе представляю, оно должно работать. Это намного более гибко.
- person Omnifarious; 11.02.2012
add_argument
, в список этих словарей.
- person Omnifarious; 11.02.2012
Ради интереса вот полная реализация ответа С.Лотта:
import argparse
class MyArgParse(argparse.ArgumentParser):
def magical_add_paired_arguments( self, *args, **kw ):
exclusive_grp = self.add_mutually_exclusive_group()
exclusive_grp.add_argument( *args, **kw )
new_action = 'store_false' if kw['action'] == 'store_true' else 'store_true'
del kw['action']
new_help = 'not({})'.format(kw['help'])
del kw['help']
exclusive_grp.add_argument( '--no-'+args[0][2:], *args[1:],
action=new_action,
help=new_help, **kw )
parser = MyArgParse()
parser.magical_add_paired_arguments('--foo', action='store_true',
dest='foo', help='do foo')
args = parser.parse_args()
print 'Starting program', 'with' if args.foo else 'without', 'foo'
Вот результат:
./so.py --help
usage: so.py [-h] [--foo | --no-foo]
optional arguments:
-h, --help show this help message and exit
--foo do foo
--no-foo not(do foo)
--foo
, так и --no-foo
в командной строке, и последний имеет приоритет. Во-вторых, справка излишне многословна, даже несмотря на то, что взаимоисключающая групповая вещь объединяет их. Я пошел своим путем и подробно описал свой подход в ответе на этот вопрос.
- person Omnifarious; 11.02.2012
Расширение ответа https://stackoverflow.com/a/9236426/1695680
import argparse
class ActionFlagWithNo(argparse.Action):
"""
Allows a 'no' prefix to disable store_true actions.
For example, --debug will have an additional --no-debug to explicitly disable it.
"""
def __init__(self, opt_name, dest=None, default=True, required=False, help=None):
super(ActionFlagWithNo, self).__init__(
[
'--' + opt_name[0],
'--no-' + opt_name[0],
] + opt_name[1:],
dest=(opt_name[0].replace('-', '_') if dest is None else dest),
nargs=0, const=None, default=default, required=required, help=help,
)
def __call__(self, parser, namespace, values, option_string=None):
if option_string.startswith('--no-'):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
class ActionFlagWithNoFormatter(argparse.HelpFormatter):
"""
This changes the --help output, what is originally this:
--file, --no-file, -f
Will be condensed like this:
--[no-]file, -f
"""
def _format_action_invocation(self, action):
if action.option_strings[1].startswith('--no-'):
return ', '.join(
[action.option_strings[0][:2] + '[no-]' + action.option_strings[0][2:]]
+ action.option_strings[2:]
)
return super(ActionFlagWithNoFormatter, self)._format_action_invocation(action)
def main(argp=None):
if argp is None:
argp = argparse.ArgumentParser(
formatter_class=ActionFlagWithNoFormatter,
)
argp._add_action(ActionFlagWithNo(['flaga', '-a'], default=False, help='...'))
argp._add_action(ActionFlagWithNo(['flabb', '-b'], default=False, help='...'))
argp = argp.parse_args()
Это дает вывод справки следующим образом:
usage: myscript.py [-h] [--flaga] [--flabb]
optional arguments:
-h, --help show this help message and exit
--[no-]flaga, -a ...
--[no-]flabb, -b ...
Gist-версия здесь, запросы на включение приветствуются :) а>
Прежде чем увидеть этот вопрос и ответы, я написал свою собственную функцию, чтобы справиться с этим:
def on_off(item):
return 'on' if item else 'off'
def argparse_add_toggle(parser, name, **kwargs):
"""Given a basename of an argument, add --name and --no-name to parser
All standard ArgumentParser.add_argument parameters are supported
and fed through to add_argument as is with the following exceptions:
name is used to generate both an on and an off
switch: --<name>/--no-<name>
help by default is a simple 'Switch on/off <name>' text for the
two options. If you provide it make sure it fits english
language wise into the template
'Switch on <help>. Default: <default>'
If you need more control, use help_on and help_off
help_on Literally used to provide the help text for --<name>
help_off Literally used to provide the help text for --no-<name>
"""
default = bool(kwargs.pop('default', 0))
dest = kwargs.pop('dest', name)
help = kwargs.pop('help', name)
help_on = kwargs.pop('help_on', 'Switch on {}. Default: {}'.format(help, on_off(defaults)))
help_off = kwargs.pop('help_off', 'Switch off {}.'.format(help))
parser.add_argument('--' + name, action='store_true', dest=dest, default=default, help=help_on)
parser.add_argument('--no-' + name, action='store_false', dest=dest, help=help_off)
Его можно использовать следующим образом:
defaults = {
'dry_run' : 0,
}
parser = argparse.ArgumentParser(description="Fancy Script",
formatter_class=argparse.RawDescriptionHelpFormatter)
argparse_add_toggle(parser, 'dry_run', default=defaults['dry_run'],
help_on='No modifications on the filesystem. No jobs started.',
help_off='Normal operation')
parser.set_defaults(**defaults)
args = parser.parse_args()
Вывод справки выглядит следующим образом:
--dry_run No modifications on the filesystem. No jobs started.
--no-dry_run Normal operation
Я предпочитаю подход к подклассу argparse.Action
, который предлагают другие ответы, вместо моей простой функции, потому что это делает код, использующий его, более чистым и легким для чтения.
Преимущество этого кода в том, что он имеет стандартную справку по умолчанию, а также help_on
и help_off
для перенастройки довольно глупых значений по умолчанию.
Может кто сможет интегрировать.
_add_action
API был задокументирован, аAction
был больше, чем просто контейнер атрибутов. - person Omnifarious   schedule 11.02.2012