Запуск argparse с необязательными аргументами, для которых не требуется указывать выходное имя файла

#python #arguments #argparse #optional-arguments

Вопрос:

Я прочитал кучу вопросов, на которые уже даны ответы, но я не вижу, чтобы это было охвачено-по крайней мере, я не понял.

Я использую argparse, чтобы взять файл и преобразовать его в другой тип. Требуется ввести имя файла. Выходное имя файла НЕ требуется, так как необязательный аргумент должен обрабатывать это. Вот код до сих пор:

 import sys
import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--json', type=str, help='Converts to json format')
parser.add_argument('--bibtex', type=str, help='Converts to bibtex format')
parser.add_argument('--html', type=str, help='Converts to html format')
parser.add_argument('inputfilename', type=str, help='enter the original filename')
args = parser.parse_args()
filename=args.filename
if args.json:
    print('Converting to json ...')
    #conversion code here
elif args.bibtex:
    print('Converting to bibtex ...')
    #conversion code here
elif args.html:
    print('Converting to html ...')
    #conversion code here
else:
    print('No conversion type indicated')
 

Проблема в том, что всякий раз, когда я использую один из этих флагов. Если я это сделаю

 $ ./orsconvert.py --json inputfilename
 

Я получаю сообщение об ошибке, говорящее

 orsconvert.py: error: the following arguments are required: inputfilename
 

Это происходит потому, что он интерпретирует предоставленный inputfilename файл как имя выходного файла, подключенное к --json . Он пытается заставить меня фактически указать выходное имя файла после необязательного аргумента и перед входным именем файла, аналогично этому:

 $ ./orsconvert.py --json outputfilename inputfileneame
 

Однако я не хочу этого делать, если есть способ обойти это. Я хочу, чтобы он принимал --json в качестве указания, что делать, а затем использовал inputfilename в качестве входных данных и автоматически сохранял его в outputfilename соответствии с тем, что будет указано в коде.

Да, я рассматриваю возможность объединения 3 необязательных аргументов в группу для упрощения кода … но это все еще не объясняет, как заставить его не требовать другого аргумента после необязательного и до окончательного inputfilename требуемого аргумента.

Есть ли что-то еще, что я могу добавить в parser.add_argument('...') поле, что перестанет требовать от меня указывать outputfilename ? Я хотел бы изменить код как можно меньше, потому что я уже раздвигаю границы своего понимания кода.

Комментарии:

1. Я не уверен, что эта настройка надежна. Мне кажется, что на самом деле вам нужны 1-2 имени файлов и «режим» вместо 1 имени файла, «режим» и необязательное «имя файла режима». Это должен быть выбор/подраздел для режима, позиционное имя файла и nargs="?" имя файла.

2. Разве у вас не может быть необязательного имени файла вывода в качестве собственного аргумента и логических флагов для типа (игнорируя, что должно произойти, если указано более одного типа)?

Ответ №1:

Проблема в том, что вы определили --json , что требуется аргумент, и в качестве этого аргумента используется имя входного файла. Ваши три конвертера должны использовать store_true действие, а не store действие по умолчанию.

 parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--json', action='store_true', help='Converts to json format')
parser.add_argument('--bibtex', action='store_true', help='Converts to bibtex format')
parser.add_argument('--html', action='store_true', help='Converts to html format')
parser.add_argument('inputfilename', help='enter the original filename')
args = parser.parse_args()
 

С этим изменением args.json et все являются логическими значениями, которые вы ожидаете от них. store_true Действие заботится об определении типа bool и значения по умолчанию false .


Еще лучше, однако, был бы один обязательный позиционный аргумент, который должен принимать значение json , bibtex , или html . Затем parse_args он сам определит, был ли задан неправильный или отсутствующий тип преобразования.

 parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('inputfilename', help='enter the original filename')
parser.add_argument('conversion_type', choices=['json', 'bibtex', 'html'])
args = parser.parse_args()
filename = args.filename
conversion_type = args.conversion_type  # Guaranteed to be json, html, or bibtex
if conversion_type == "json":
    ...
elif conversion_type == "bibtex":
    ...
elif conversion_type == "html":
 

Если вам нужна опция со значением по умолчанию, пристегнитесь 🙂 Мы собираемся добавить 4 опции: одну, которая позволяет явно указать тип вывода, и три ярлыка для его настройки.

 p = argparse.ArgumentParser()
p.add_argument('inputfilename')
p.add_argument('--type', choices=['json', 'bibtex', 'html'], default='json')
p.add_argument('--json', action='store_const', const='json', dest='type')
p.add_argument('--html', action='store_const', const='html', dest='type')
p.add_argument('--bibtex', action='store_const', const='bibtex', dest='type')
 

--json , --bibtex , и --html имеют тот же эффект, --type json что и, --type bibtex , и --type html , соответственно. Как и прежде, --type может принимать в качестве аргумента только эти три значения. Если их несколько, вступает в силу последний из них.

Комментарии:

1. Спасибо. Это помогает. Я не понимал, что, делая переменную необязательной с помощью — по умолчанию, она требует другого аргумента.

2. Только по умолчанию 🙂 Давайте сделаем шаг назад. Префикс имени аргумента с -- делает его опцией, а не позиционным аргументом. Однако у каждого параметра есть соответствующее действие , которое 'store' по умолчанию. Это действие магазина, которое заставляет параметр требовать аргумент, если используется параметр. 'store_const' , с другой стороны, не требует аргумента, поскольку вы заранее указываете значение для хранения в определении аргумента.

3. Итак, с практической точки зрения, почему я должен хотеть использовать «store_true», а не «store_const»? Мне нравится идея » выбор= […]», но мой руководитель хочет, чтобы я сохранил поля с дефисами «—json», «—bibtex», «—html», хотя требуется хотя бы одно.

4. store_true в основном это особый случай store_const . add_argument("...", action='store_true') это сокращение от add_argument("...", action='store_const', const=True, type=bool, default=False) . (Существует store_false также соответствующее действие.)

5. Возможно, вы захотите взглянуть на docs.python.org/3/library/argparse.html#mutual-exclusion кроме того, это может гарантировать, что будет выбран именно один из трех вариантов, как choices и для одного варианта или аргумента.

Ответ №2:

argparse уже поставляется со всем необходимым, чтобы на самом деле иметь нужный вам API, а не тот, который, по вашему мнению, вам нужен.

  • Тип должен быть одним из нескольких choices , а не тремя отдельными флагами.
  • Это inputfilename должно быть обязательно.
  • Это outputfilename должно быть необязательно.
 import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('format', choices=["json", "bibtex", "html"])
parser.add_argument('inputfilename', type=str)
parser.add_argument('outputfilename', type=str, nargs="?")
args = parser.parse_args()
print(args)
 

Это автоматически обеспечивает желаемое использование, не злоупотребляя опциями, но вместо этого получая обработку ошибок.

 $ ./converter json in.txt
Namespace(format='json', inputfilename='in.txt', outputfilename=None)
$ ./converter html in.txt out.html
Namespace(format='html', inputfilename='in.txt', outputfilename='out.html')
$ ./converter yaml in.txt         
usage: converter [-h] {json,bibtex,html} inputfilename [outputfilename]
converter: error: argument format: invalid choice: 'yaml' (choose from 'json', 'bibtex', 'html')
 

Комментарии:

1. Извините, я не хотел красть ваш ответ; Я думаю, он появился, когда я добавлял это же предложение.

2. @chepner, по крайней мере, мы не выбрали один и тот же порядок аргументов. 😉

3. Меня так и подмывает это украсть 🙂


Ответ №3:

Ваша проблема в том, что argparse ожидает ввода, тем более что у вас есть type=str

Попробуйте вместо этого заменить json arg на это

 parser.add_argument('--json', action="store_true", help='Converts to json format')
 

Ответ №4:

Таким образом, проблема выглядит так, как будто каждый argparse.arg требует указания аргумента, будь то что угодно. Так что, возможно, вы могли бы установить его в логическое значение. Но я думаю, что, возможно, этот подход мог бы быть лучше:

 import sys
import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--type', type=str, help='Converts to any of the following formats: 1 for json, 2 for bibtex and 3 for html')
parser.add_argument('--inputfilename', type=str, help='enter the original filename')
args = parser.parse_args()
filename=args.inputfilename
if args.type:
    conversion_type = int(args.type)
    if conversion_type == int(1):
        print('Converting to json ...')
        #conversion code here
    elif conversion_type == 2:
        print('Converting to bibtex ...')
        #conversion code here
    elif conversion_type == 3:
        print('Converting to html ...')
        #conversion code here
else:
    print('No conversion type indicated')
 

Просто добавьте аргумент для указания типа. Исходя из этого, конвертируйте или нет в соответствии с условиями.

Комментарии:

1. Я не думаю type и inputfilename должен быть необязательным . Argparse уже поддерживает выбор, поэтому наличие какой-либо непрозрачной схемы нумерации (которая плохо сочетается с типами) также не совсем подходит.

2. Наверное, я не знал об этом action=store_true варианте. Прошу прощения, если мой ответ ввел вас в заблуждение.

3. @MisterMiyagi, я думаю, это правда