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

  • Ввод аргументов в командной строке.
  • Сохранение списка аргументов в файле и передача имени этого файла программе через командную строку.

Для этого я передаю аргумент fromfile_prefix_chars конструктору ArgumentParser.

Script.py

from argparse import ArgumentParser
parser = ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)

Args.txt

--foo
2
--bar
2

Примеры использования

$ python script.py --foo 3
Namespace(bar=1, filename=None, foo='3')
$ python script.py @args.txt --foo 3
Namespace(bar='2', filename=None, foo='3')

Я ожидал, что args.filename сохранит имя файла, но, что удивительно, вместо него будет значение None. Мне известно, что я мог бы получить имя файла от sys.argv путем небольшой обработки. Есть ли более чистый способ (в идеале подход argparse), чтобы получить имя файла аргументов?

6
Tonechas 28 Июн 2019 в 20:32

3 ответа

Лучший ответ

Из моего тестирования использование fromfile_prefix_chars означает, что argparse фактически не передаст аргумент вашей программе. Вместо этого argparse видит @args.txt, перехватывает его, читает из него и передает аргументы без @args.txt в вашу программу. Вероятно, это потому, что большинству людей на самом деле не нужно имя файла, просто нужны аргументы внутри, поэтому argparse избавляет вас от необходимости создавать другой аргумент для хранения того, что вам не нужно.

К сожалению, все аргументы хранятся как локальные переменные в argparse.py, поэтому мы не можем получить к ним доступ. Я полагаю, что вы могли бы переопределить некоторые функции argparse. Имейте в виду, что это ужасное, отвратительное, хакерское решение, и я чувствую, что разбор sys.argv на 100% лучше.

from argparse import ArgumentParser

# Most of the following is copied from argparse.py
def customReadArgs(self, arg_strings):
    # expand arguments referencing files
    new_arg_strings = []
    for arg_string in arg_strings:

        # for regular arguments, just add them back into the list
        if not arg_string or arg_string[0] not in self.fromfile_prefix_chars:
            new_arg_strings.append(arg_string)

        # replace arguments referencing files with the file content
        else:
            try:
                fn = arg_string[1:]
                with open(fn) as args_file:

                    # What was changed: before was []
                    arg_strings = [fn]

                    for arg_line in args_file.read().splitlines():
                        for arg in self.convert_arg_line_to_args(arg_line):
                            arg_strings.append(arg)
                    arg_strings = self._read_args_from_files(arg_strings)
                    new_arg_strings.extend(arg_strings)
            except OSError:
                err = _sys.exc_info()[1]
                self.error(str(err))

    # return the modified argument list
    return new_arg_strings
ArgumentParser._read_args_from_files = customReadArgs
parser = ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)
1
101arrowz 28 Июн 2019 в 18:35

Просто для протокола, вот быстрое и грязное решение, которое я придумала. В основном он состоит в создании копии parser и установке ее атрибута from_file_prefix_chars на None:

import copy

parser_dupe = copy.copy(parser)
parser_dupe.fromfile_prefix_chars = None
args_raw = parser_dupe.parse_args()
if args_raw.filename:
    args.filename = args_raw.filename[1:]
0
Tonechas 1 Июл 2019 в 13:55

Ваш script.py плюс файл. Я добавил имя файла в сам файл.

Args.txt

args.txt
--foo
2
--bar
2

Тестирование:

1803:~/mypy$ python3 stack56811067.py --foo 3
Namespace(bar=1, filename=None, foo='3')
1553:~/mypy$ python3 stack56811067.py @args.txt --foo 3
Namespace(bar='2', filename='args.txt', foo='3')
1
hpaulj 28 Июн 2019 в 22:55