다음과 같은 인터페이스가있는 명령 줄 프로그램을 구현하고 있습니다.
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
나는 argparse 문서를 살펴 보았다 . in을 GLOBAL_OPTIONS
사용하여 선택적 인수로 구현할 수 있습니다 . 그리고 using 하위 명령add_argument
argparse
{command [COMMAND_OPTS]}
.
문서에서 하나의 하위 명령 만 가질 수있는 것 같습니다. 그러나 보시다시피 하나 이상의 하위 명령을 구현해야합니다. 사용하는 명령 줄 인수를 구문 분석하는 가장 좋은 방법은 무엇입니까 argparse
?
답변
나는 같은 질문을 내 놓았고 더 나은 답을 얻은 것 같습니다.
해결책은 단순히 다른 하위 구문 분석기와 하위 구문 분석기를 중첩하는 것이 아니라 다른 하위 구문 분석기 다음에 구문 분석기를 추가하여 하위 구문 분석기를 추가 할 수 있다는 것입니다.
코드는 방법을 알려줍니다.
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', '-u',
default=getpass.getuser(),
help='username')
parent_parser.add_argument('--debug', default=False, required=False,
action='store_true', dest="debug", help='debug flag')
main_parser = argparse.ArgumentParser()
service_subparsers = main_parser.add_subparsers(title="service",
dest="service_command")
service_parser = service_subparsers.add_parser("first", help="first",
parents=[parent_parser])
action_subparser = service_parser.add_subparsers(title="action",
dest="action_command")
action_parser = action_subparser.add_parser("second", help="second",
parents=[parent_parser])
args = main_parser.parse_args()
답변
@mgilson 은이 질문에 대한 좋은 대답 을 가지고 있습니다. 그러나 sys.argv를 나 자신이 분할하는 문제는 Argparse가 사용자를 위해 생성하는 모든 멋진 도움말 메시지를 잃어버린다는 것입니다. 그래서 나는 이것을 끝냈다.
import argparse
## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
namespaces = []
extra = namespace.extra
while extra:
n = parser.parse_args(extra)
extra = n.extra
namespaces.append(n)
return namespaces
argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a
## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')
## Do similar stuff for other sub-parsers
이제 첫 번째 구문 분석 후 모든 연결 명령이 extra
. 모든 연결 명령을 가져오고 별도의 네임 스페이스를 만드는 것이 비어 있지 않은 동안 다시 구문 분석합니다. 그리고 argparse가 생성하는 더 좋은 사용 문자열을 얻습니다.
답변
parse_known_args
네임 스페이스와 알 수없는 문자열 목록을 반환합니다. 이것은 extra
확인 된 답변 과 유사합니다 .
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
sub = parser.add_subparsers()
for i in range(1,4):
sp = sub.add_parser('cmd%i'%i)
sp.add_argument('--foo%i'%i) # optionals have to be distinct
rest = '--foo 0 cmd2 --foo2 2 cmd3 --foo3 3 cmd1 --foo1 1'.split() # or sys.argv
args = argparse.Namespace()
while rest:
args,rest = parser.parse_known_args(rest,namespace=args)
print args, rest
생성 :
Namespace(foo='0', foo2='2') ['cmd3', '--foo3', '3', 'cmd1', '--foo1', '1']
Namespace(foo='0', foo2='2', foo3='3') ['cmd1', '--foo1', '1']
Namespace(foo='0', foo1='1', foo2='2', foo3='3') []
대체 루프는 각 하위 파서에 자체 네임 스페이스를 제공합니다. 이렇게하면 위치 이름이 겹칠 수 있습니다.
argslist = []
while rest:
args,rest = parser.parse_known_args(rest)
argslist.append(args)
답변
언제든지 명령 줄을 직접 분할 ( sys.argv
명령 이름으로 분할 ) 한 다음 특정 명령에 해당하는 부분 만 전달할 parse_args
수 있습니다 Namespace
. 원하는 경우 네임 스페이스 키워드를 사용하여 동일한 것을 사용할 수도 있습니다 .
다음을 사용하면 명령 줄을 쉽게 그룹화 할 수 있습니다 itertools.groupby
.
import sys
import itertools
import argparse
mycommands=['cmd1','cmd2','cmd3']
def groupargs(arg,currentarg=[None]):
if(arg in mycommands):currentarg[0]=arg
return currentarg[0]
commandlines=[list(args) for cmd,args in intertools.groupby(sys.argv,groupargs)]
#setup parser here...
parser=argparse.ArgumentParser()
#...
namespace=argparse.Namespace()
for cmdline in commandlines:
parser.parse_args(cmdline,namespace=namespace)
#Now do something with namespace...
테스트되지 않은
답변
@mgilson의 답변을 개선하기 위해 argv를 부분으로 분할하고 명령 인수 값을 네임 스페이스 계층 구조에 넣는 작은 구문 분석 방법을 작성했습니다.
import sys
import argparse
def parse_args(parser, commands):
# Divide argv by commands
split_argv = [[]]
for c in sys.argv[1:]:
if c in commands.choices:
split_argv.append([c])
else:
split_argv[-1].append(c)
# Initialize namespace
args = argparse.Namespace()
for c in commands.choices:
setattr(args, c, None)
# Parse each command
parser.parse_args(split_argv[0], namespace=args) # Without command
for argv in split_argv[1:]: # Commands
n = argparse.Namespace()
setattr(args, argv[0], n)
parser.parse_args(argv, namespace=n)
return args
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(title='sub-commands')
cmd1_parser = commands.add_parser('cmd1')
cmd1_parser.add_argument('--foo')
cmd2_parser = commands.add_parser('cmd2')
cmd2_parser.add_argument('--foo')
cmd2_parser = commands.add_parser('cmd3')
cmd2_parser.add_argument('--foo')
args = parse_args(parser, commands)
print(args)
제대로 작동하여 멋진 argparse 도움말을 제공합니다.
대상 ./test.py --help
:
usage: test.py [-h] {cmd1,cmd2,cmd3} ...
optional arguments:
-h, --help show this help message and exit
sub-commands:
{cmd1,cmd2,cmd3}
대상 ./test.py cmd1 --help
:
usage: test.py cmd1 [-h] [--foo FOO]
optional arguments:
-h, --help show this help message and exit
--foo FOO
그리고 인수 값을 포함하는 네임 스페이스 계층을 생성합니다.
./test.py cmd1 --foo 3 cmd3 --foo 4
Namespace(cmd1=Namespace(foo='3'), cmd2=None, cmd3=Namespace(foo='4'))
답변
@Vikas가 제공하는 솔루션은 하위 명령 별 선택적 인수에 대해 실패하지만 접근 방식은 유효합니다. 다음은 개선 된 버전입니다.
import argparse
# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
# parse some argument lists
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
print(argv)
options, argv = parser.parse_known_args(argv)
print(options)
if not options.subparser_name:
break
이것은 사용하는 parse_known_args
대신 parse_args
. parse_args
현재 서브 파서에 알려지지 않은 인수가 발견되는 즉시 중단됩니다.parse_known_args
, 반환 된 튜플의 두 번째 값으로 반환됩니다. 이 접근 방식에서는 나머지 인수가 파서에 다시 공급됩니다. 따라서 각 명령에 대해 새 네임 스페이스가 생성됩니다.
이 기본 예에서 모든 전역 옵션은 첫 번째 옵션 네임 스페이스에만 추가되며 후속 네임 스페이스에는 추가되지 않습니다.
이 접근 방식은 대부분의 상황에서 잘 작동하지만 세 가지 중요한 제한 사항이 있습니다.
- 같은 다른 하위 명령에 대해 동일한 선택적 인수를 사용할 수 없습니다
myprog.py command_a --foo=bar command_b --foo=bar
. - 부속 명령 (
nargs='?'
또는nargs='+'
또는nargs='*'
) 과 함께 가변 길이 위치 인수를 사용할 수 없습니다 . - 알려진 인수는 새 명령에서 ‘중단’없이 구문 분석됩니다. 예
PROG --foo command_b command_a --baz Z 12
를 들어 위의 코드에서는에서--baz Z
가 아니라에서 소비command_b
됩니다command_a
.
이러한 제한은 argparse의 직접적인 제한입니다. 다음은 단일 하위 명령을 사용하는 경우에도 argparse의 제한 사항을 보여주는 간단한 예입니다.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
# create the parser for the "command_a" command
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
# create the parser for the "command_b" command
parser_b = subparsers.add_parser('command_b', help='command_b help')
options = parser.parse_args('command_a 42'.split())
print(options)
이것은 error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
원인은 내부 방법 argparse.ArgParser._parse_known_args()
이 너무 탐욕스럽고 그것이 command_a
선택적인 spam
인수 의 값 이라고 가정하기 때문 입니다 . 특히, 선택적 및 위치 인수를 ‘분할’할 때 _parse_known_args()
arugment의 이름 (예 : command_a
또는 command_b
)을 보지 않고 인수 목록에서 발생하는 위치 만 확인합니다. 또한 모든 하위 명령이 나머지 인수를 모두 사용한다고 가정합니다. 이 제한은 argparse
또한 다중 명령 하위 구문 분석기의 적절한 구현을 방해합니다. 이는 안타깝게도 적절한 구현을 위해서는 argparse.ArgParser._parse_known_args()
메서드를 완전히 다시 작성해야하며 이는 200 줄 이상의 코드가 필요함을 의미합니다 .
이러한 제한을 감안할 때 하위 명령 대신 단일 객관식 인수로 간단히 되 돌리는 옵션이 될 수 있습니다.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
choices=['command_a', 'command_b'])
options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
#options = parser.parse_args(['--help'])
사용 정보에 다른 명령을 나열하는 것도 가능합니다. 내 대답 https://stackoverflow.com/a/49999185/428542를 참조하십시오.