Argparse - Как указать подкоманду по умолчанию
Я использую пакет argparse Python 2.7 для написания некоторой логики разбора параметров для инструмента командной строки. Инструмент должен принимать один из следующих аргументов:
"ON": Включите функцию.
" ВЫКЛ ": выключите функцию.
[Нет аргументов]: Эхо текущее состояние функции.
Взгляд на документацию argparse заставил меня поверить, что я хотел бы определить две - возможно, три подкоманды, поскольку эти три состояния являются взаимоисключающими и представляют собой различные концептуальные действия. Это моя текущая попытка кода:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=print_state) # I think this line is wrong.
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=set_state, newstate='ON')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=set_state, newstate='OFF')
args = parser.parse_args()
if(args.func == set_state):
set_state(args.newstate)
elif(args.func == print_state):
print_state()
else:
args.func() # Catchall in case I add more functions later
У меня создалось впечатление, что если бы я предоставил 0 аргументов, основной синтаксический анализатор установил бы func=print_state
, и если бы я предоставил 1 аргумент, основной синтаксический анализатор использовал бы соответствующие подкоманды по умолчанию и вызывал func=set_state
. Вместо этого я получаю следующую ошибку с 0 аргументами:
usage: cvsSecure.py [-h] {ON,OFF} ...
cvsSecure.py: error: too few arguments
И если я предоставляю "ВЫКЛ" или "ON", вместо set_state
вызывается print_state
. Если я прокомментирую строку parser.set_defaults
, set_state
вызывается правильно.
Я программист на уровне подмастерья, но начинающий новичок на Python. Любые предложения о том, как я могу заставить это работать?
Изменить. Еще одна причина, по которой я рассматривал подкоманды, - это потенциальная четвертая функция, которую я рассматриваю в будущем:
"FORCE txtval": установите для состояния функции значение txtval
.
Ответы
Ответ 1
По умолчанию анализатор верхнего уровня переопределяет значения по умолчанию для вспомогательных парсеров, поэтому установка значения по умолчанию func
в подпараллеле игнорируется, но значение newstate
из параметров по-парсеру по умолчанию правильно.
Я не думаю, что вы хотите использовать подкоманды. Подкоманды используются, когда доступные параметры и позиционные аргументы изменяются в зависимости от выбранной подкоманды. Однако у вас нет других опций или позиционных аргументов.
Следующий код, похоже, выполняет то, что вам нужно:
import argparse
def print_state():
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
parser.add_argument('state', choices = ['ON', 'OFF'], nargs='?')
args = parser.parse_args()
if args.state is None:
print_state()
elif args.state in ('ON', 'OFF'):
set_state(args.state)
Обратите внимание на необязательные параметры parser.add_argument
. Параметр "Варианты" указывает допустимые параметры, а для параметра "nargs" - "?" указывает, что 1 аргумент должен потребляться, если он доступен, иначе никто не должен потребляться.
Изменить: Если вы хотите добавить команду FORCE с аргументом и иметь отдельный текст справки для команды ON и OFF, вам нужно использовать подкоманды. К сожалению, не существует способа указать подкоманду по умолчанию. Однако вы можете обойти эту проблему, проверив список пустых аргументов и предоставив свои собственные. Вот пример кода, иллюстрирующий, что я имею в виду:
import argparse
import sys
def print_state(ignored):
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
on = subparsers.add_parser('ON', help = 'On help here.')
on.set_defaults(func = set_state, newstate = 'ON')
off = subparsers.add_parser('OFF', help = 'Off help here.')
off.set_defaults(func = set_state, newstate = 'OFF')
prt = subparsers.add_parser('PRINT')
prt.set_defaults(func = print_state, newstate = 'N/A')
force = subparsers.add_parser('FORCE' , help = 'Force help here.')
force.add_argument('newstate', choices = [ 'ON', 'OFF' ])
force.set_defaults(func = set_state)
if (len(sys.argv) < 2):
args = parser.parse_args(['PRINT'])
else:
args = parser.parse_args(sys.argv[1:])
args.func(args.newstate)
Ответ 2
Есть две проблемы с вашим подходом.
Сначала вы, вероятно, уже заметили, что newstate
не является некоторым sub_value для дополнительного парсера, и его необходимо адресовать на верхнем уровне args
как args.newstate
. Это должно объяснить, что присвоение значения по умолчанию newstate
дважды приведет к тому, что первое значение будет перезаписано. Вызываете ли вы свою программу с помощью 'ON' или 'OFF' в качестве параметра, каждый раз set_state()
вызывается с помощью OFF
. Если вы просто хотите сделать python cvsSecure ON
и
python cvsSecure OFF
будет работать следующее:
from __future__ import print_function
import sys
import argparse
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
args = parser.parse_args()
args.func(args)
Вторая проблема заключается в том, что argparse
обрабатывает подпарамеры как однозначные аргументы, поэтому перед вызовом parser.parse_args()
нужно указать один. Вы можете автоматизировать вставку отсутствующего аргумента, добавив дополнительный подпараметр "PRINT" и автоматически вставляя
что использование set_default_subparser
добавлено в argparse.ArgumentParser()
(этот код является частью
пакета ruamel.std.argparse
from __future__ import print_function
import sys
import argparse
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def print_state(args):
print("print_state")
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_print = subparsers.add_parser('PRINT', help='default action')
parser_print.set_defaults(func=print_state)
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
parser.set_default_subparser('PRINT')
args = parser.parse_args()
args.func(args)
Вам не нужно обрабатывать в args
до do_on()
и т.д., но это пригодится, если вы начнете указывать параметры для разных подпараметров.