2016-05-10 2 views
1

Я пытаюсь заставить мой скрипт работать, но argparse переписывает мои позиционные аргументы из родительского синтаксического анализатора. Как я могу получить argparse, чтобы почтить его значение? Он сохраняет значения из необязательных аргументов.Как я могу заставить Python ArgParse прекратить заменять позиционные аргументы в детском парсере

Вот очень упрощенная версия того, что мне нужно. Если вы запустите это, вы увидите, что аргументы перезаписаны.

testargs.py

#! /usr/bin/env python3 

import argparse 
import sys 

def main(): 
    preparser = argparse.ArgumentParser(add_help=False) 
    preparser.add_argument('first', 
         nargs='?') 

    preparser.add_argument('outfile', 
         nargs='?', 
         type=argparse.FileType('w', encoding='utf-8'), 
         default=sys.stdout, 
         help='Output file') 

    preparser.add_argument(
     '--do-something','-d', 
     action='store_true') 
    # Parse args with preparser, and find config file 
    args, remaining_argv = preparser.parse_known_args() 
    print(args) 

    parser = argparse.ArgumentParser(
     parents=[preparser], 
     description=__doc__) 

    parser.add_argument(
     '--clear-screen', '-c', 
     action='store_true') 
    args = parser.parse_args(args=remaining_argv,namespace=args) 
    print(args) 

if __name__ == '__main__': 
    main() 

И называть его testargs.py something /tmp/test.txt -d -c

Вы увидите, что он держит -d но падает как позиционные аргументы и возвращает их по умолчанию.

EDIT: см. Дополнительные комментарии в принятом ответе на некоторые оговорки.

+0

Почему вы не можете просто разобрать все арг сразу?Зачем вам сначала нужны подготовители? –

+0

просто разбор исходных параметров с помощью 'parser.parse_args()' будет работать, не так ли? –

+0

rerunning 'parser.parse_args()' ничего не делает. – MaDhAt2r

ответ

0

При указании parents=[preparser] это означает, что parser является продолжением preparser, и будет анализировать все аргументы Релевент к preparser, которые он никогда не дано.

Позволяет сказать preparser имеет только один позиционный аргумент first и parser имеет только один позиционный аргумент second, когда вы делаете парсер ребенок preparser ожидает как аргументы:

import argparse 

parser1 = argparse.ArgumentParser(add_help=False) 
parser1.add_argument("first") 

parser2 = argparse.ArgumentParser(parents=[parser1]) 
parser2.add_argument("second") 
args2 = parser2.parse_args(["arg1","arg2"]) 
assert args2.first == "arg1" and args2.second == "arg2" 

Однако прохождение только оставшиеся аргументы, оставшиеся от parser1, будут только ['second'], которые являются неправильными аргументами для parser2:

parser1 = argparse.ArgumentParser(add_help=False) 
parser1.add_argument("first") 
args1, remaining_args = parser1.parse_known_args(["arg1","arg2"]) 

parser2 = argparse.ArgumentParser(parents=[parser1]) 
parser2.add_argument("second") 

>>> args1 
Namespace(first='arg1') 
>>> remaining_args 
['arg2'] 
>>> parser2.parse_args(remaining_args) 
usage: test.py [-h] first second 
test.py: error: the following arguments are required: second 

Чтобы обрабатывать только аргументы, которые не были обработаны с помощью первого прохода, не указать его в качестве родителя второго синтаксического анализатора:

parser1 = argparse.ArgumentParser(add_help=False) 
parser1.add_argument("first") 
args1, remaining_args = parser1.parse_known_args(["arg1","arg2"]) 

parser2 = argparse.ArgumentParser() #parents=[parser1]) #NO PARENT! 
parser2.add_argument("second") 
args2 = parser2.parse_args(remaining_args,args1) 

assert args2.first == "arg1" and args2.second == "arg2" 
+0

Это правильный ответ. Я удалил родительскую ссылку и передал ее в пространстве имен. Теперь он работает так, как я его намеревался. Спасибо за Ваш ответ! – MaDhAt2r

+0

Просто примечание всем, кого интересует. Когда я делаю это так, я теряю помощь от первого парсера, а это значит, что мои позитивные аргументы не отображались в результатах. Таким образом, это действительно делает его частичным решением. – MaDhAt2r

0

2-х позиционные nargs='?'. Такой позиционный элемент всегда «виден», так как пустой список соответствует этому nargs.

Первый раз через 'text.txt' совпадает с first и помещается в пространство имен. Второй раз, когда нет соответствующей строки, поэтому используется значение по умолчанию - так же, как если бы вы не дали эту строку в первый раз.

Если изменить first иметь по умолчанию nargs, я получаю

error: the following arguments are required: first 

из 2-го синтаксического анализа. Несмотря на то, что в пространстве имен есть значение, он по-прежнему пытается получить значение от argv. (это по умолчанию, но не совсем).

По умолчанию для позиционеров с nargs='?' (или *) сложны. Они являются необязательными, но не совсем так, как optionals. Позиционные действия по-прежнему вызываются, но с пустым списком значений.

Я не думаю, что функция parents делает что-нибудь для вас. preparser уже обрабатывает набор аргументов; нет необходимости обрабатывать их снова в parser, тем более, что все соответствующие строки аргументов были удалены.

Другой вариант - оставить родителей, но использовать по умолчанию sys.argv[1:] во втором парсере. (Но остерегайтесь побочных эффектов, как открытие файлов)

args = parser.parse_args(namespace=args) 

Третий вариант разобрать аргументы самостоятельно и объединить их со словарем update.

adict = vars(preparse_args) 
adict.update(vars(parser_args)) 
# taking some care in who overrides who 

Для получения более подробной информации смотрите в файле argparse.py на ArgumentParser._get_values, в частности not arg_strings случаев.

Примечание относительно FileType. Этот тип прекрасно работает для небольших скриптов, где вы будете использовать файлы сразу и выйти. Это не так хорошо в больших программах, где вы можете закрыть файл после использования (закройте stdout ???) или используйте файлы в контексте with.


редактировать - примечание на parents

add_argument создает Action объект, и добавляет его в список парсера действий. parse_args в основном соответствует входным строкам с этими действиями.

parents просто копирует эти объекты Action (по ссылке) от родителя к дочернему. Для детского анализатора это похоже на то, что действия были созданы с add_argument напрямую.

parents наиболее полезен, когда вы импортируете парсер и не имеете прямого доступа к его определению. Если вы определяете как родительский, так и дочерний, то parents просто сохранит вам текст ввода/вырезания-n-paste.

Этот и другие вопросы SO (в основном вызванные копией по ссылке) показывают, что разработчики не собирались использовать как родительский, так и дочерний сеансы для синтаксического анализа. Это можно сделать, но есть глюки, которые они не рассматривали.

===================

Я могу представить себе определение пользовательских Action класса, который будет «вести себя» в такой ситуации. Например, можно проверить пространство имен для некоторого значения по умолчанию, прежде чем добавлять его собственное (возможно, значение по умолчанию).

Рассмотрим, например, если я изменил action из first в 'Append':

preparser.add_argument('first', action='append', nargs='?') 

Результат является:

1840:~/mypy$ python3 stack37147683.py /tmp/test.txt -d -c 
Namespace(do_something=True, first=['/tmp/test.txt'], outfile=<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>) 

Namespace(clear_screen=True, do_something=True, first=['/tmp/test.txt', None], outfile=<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>) 

С первого синтаксического анализатора, first=['/tmp/test.txt']; от второго, first=['/tmp/test.txt', None].

Из-за append элемент из первого сохраняется, а второй по умолчанию добавлен новый параметр.

+0

«Я не думаю, что функция« родители »делает что-то для вас ... соответствующие строки аргументов были удалены». Если функция 'parents' не использовалась, она правильно только анализирует аргументы, которые еще не были обработаны в предыдущем проходе. Если мой ответ не ушел, я думаю, что использование функции «родители» - это корень проблемы. –

+0

ОП будет иметь эту проблему, независимо от того, как «первый» был добавлен во второй парсер. – hpaulj