как получить argparse для чтения аргументов из файла с опцией, а не префиксом

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

$ python myprogram.py --foo 1 -A somefile.txt --bar 2

когда argparse видит -A, он должен прекратить чтение из sys.argv или что я ему даю, и вызываю функцию, которую я пишу, которая будет читать какой-то файл.текст и возврат списка аргументы. Когда файл исчерпан, он должен возобновить синтаксический анализ sys.аргв или как его там. Важно, чтобы обработка аргументов в файле происходила по порядку (т. е.: -foo должен быть обработан, затем аргументы в файле, затем-bar, так что аргументы в файле могут переопределить --foo, и --bar может переопределить то, что в файле).

возможно ли такое? Могу ли я написать пользовательскую функцию, которая помещает новые аргументы в стек argparse или что-то в этом роде эффект?

3 ответов


вы можете решить эту проблему с помощью настраиваемого argparse.Action, который открывает файл, анализирует содержимое файла,а затем добавляет аргументы.

например, это было бы очень простое действие:

class LoadFromFile (argparse.Action):
    def __call__ (self, parser, namespace, values, option_string = None):
        with values as f:
            parser.parse_args(f.read().split(), namespace)

который вы можете использовать следующим образом:

parser = argparse.ArgumentParser()
# other arguments
parser.add_argument('--file', type=open, action=LoadFromFile)
args = parser.parse_args()

в результате пространство имен args будет также содержать любую конфигурацию, которая также была загружена из файла.

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

def __call__ (self, parser, namespace, values, option_string=None):
    with values as f:
        contents = f.read()

    data = parser.parse_args(contents.split(), namespace=namespace)
    for k, v in vars(data).items():
        if v and k != option_string.lstrip('-'):
            setattr(namespace, k, v)

конечно, вы также можете сделать чтение файла немного сложнее, например, сначала прочитать из JSON.


Вы сказали, что

мне нужно иметь возможность написать свою собственную функцию, чтобы прочитать этот файл и вернуть аргументы (это не в формате одного аргумента на строку) -

существует положение в существующем обработчике префикс-файла, чтобы изменить способ чтения файла. Файл считывается "частным" методом,parser._read_args_from_files, но он вызывает простой открытый метод, который преобразует строку в строки, по умолчанию один аргумент в строке действие:

def convert_arg_line_to_args(self, arg_line):
    return [arg_line]

оно было написано таким образом, чтобы вы могли легко настроить его. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

полезным переопределением этого метода является тот, который рассматривает каждое слово, разделенное пробелом, как аргумент:

def convert_arg_line_to_args(self, arg_line):
    for arg in arg_line.split():
        if not arg.strip():
            continue
        yield arg

на test_argparse.py unittesting file существует тестовый случай для этой альтернативы.


но если вы все еще хотите запустить это чтение с параметром аргумента вместо символа префикса, тогда подход пользовательского действия является хорошим.

вы могли бы написать свою собственную функцию, которая обрабатывает argv прежде чем он будет передан parser. Он может быть смоделирован на parser._read_args_from_files.

таким образом, вы можете написать такую функцию, как:

def read_my_file(argv):
    # if there is a '-A' string in argv, replace it, and the following filename
    # with the contents of the file (as strings)
    # you can adapt code from _read_args_from_files
    new_argv = []
    for a in argv:
        ....
        # details left to user
    return new_argv

вызываем парсер с:

parser.parse_args(read_my_file(sys.argv[1:]))

и да, это может быть заключено в ArgumentParser подкласс.


An Action, при вызове, получает parser и namespace среди его аргументов.

таким образом, вы можете поместить свой файл через Первый, чтобы обновить последний:

class ArgfileAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        extra_args = <parse_the_file>(values)
        #`namespace' is updated in-place when specified
        parser.parse_args(extra_args,namespace)