Может ли Python argparse переставлять аргументы в порядке, как gnu getopt?

GNU getopt и инструменты командной строки, которые его используют, позволяют чередовать параметры и аргументы, известные как параметры перестановки (см. http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-Getopt). Модуль Perl Getopt :: Long также поддерживает это (с помощью qw (: config gnu_getopt)). argparse, похоже, не поддерживает (и даже не упоминает) параметры перестановки.

Есть много SO-вопросов, связанных с порядком arg / opt, но, похоже, ни один из них не отвечает на этот вопрос: можно ли настроить argparse для перестановки порядка аргументов, например getopt?

Вариант использования - это прототипная подпись командной строки, такая как сортировка GNU:

sort [opts] [files]

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

Например:

import argparse
p = argparse.ArgumentParser();
p.add_argument('files',nargs='*',default=['-']);
p.add_argument('-z',action='store_true')

p.parse_args(['-z','bar','foo']) # ok
p.parse_args(['bar','foo','-z']) # ok
p.parse_args(['bar','-z','foo']) # not okay
usage: ipython [-h] [-z] [files [files ...]]

Я пробовал:

  • p.parse_known_args - не жалуется, но на самом деле также не переставляет и не возражает против аргументов, которые выглядят как недопустимые параметры (например, --bogus или -b выше).
  • p.add_argument ('files', nargs = argparse.REMAINDER) - опция -z включается в файлы, если только перед позиционными аргументами
  • p.add_argument ('файлы', nargs = '*', действие = 'добавить');

Я хочу реализовать что-то близкое к прототипу сортировки GNU выше. Меня не интересует флаг, который можно указывать для каждого файла (например, -f file1 -f file2).


person Reece    schedule 02.03.2012    source источник


Ответы (2)


Вот быстрое решение, которое декодирует список аргументов по одной (параметры, позиционные аргументы) за раз.

import argparse

class ExtendAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        items = getattr(namespace, self.dest, None)
        if items is None:
            items = []
        items.extend(values)
        setattr(namespace, self.dest, items)

parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*', action=ExtendAction)
parser.add_argument('-z', action='store_true')
parser.add_argument('-v', action='count')
parser.add_argument('args_tail', nargs=argparse.REMAINDER)

def interleaved_parse(argv=None):
    opts = parser.parse_args(argv)
    optargs = opts.args_tail
    while optargs:
        opts = parser.parse_args(optargs, opts)
        optargs = opts.args_tail
    return opts

print(interleaved_parse('-z bar foo'.split()))
print(interleaved_parse('bar foo -z'.split()))
print(interleaved_parse('bar -z foo'.split()))
print(interleaved_parse('-v a -zv b -z c -vz d -v'.split()))

Вывод:

Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['a', 'b', 'c', 'd'], v=4, z=True)

Примечание. Не пытайтесь использовать это с другими аргументами, не являющимися флагами (кроме одного аргумента nargs='*' и аргумента args_tail). Синтаксический анализатор не будет знать о предыдущих вызовах parse_args, поэтому он сохранит неправильное значение для этих аргументов, не являющихся флагами. В качестве обходного пути вы можете проанализировать аргумент nargs='*' вручную после использования interleaved_parse.

person nneonneo    schedule 16.08.2012
comment
Py 3.7 добавляет заголовок parse_intermixed_args, stackoverflow.com/questions/50916124/ - person hpaulj; 01.08.2018

Я не видел ничего определенного в документации argparse о том, что он может или не может переставлять. Основываясь на ваших собственных наблюдениях, когда перестановка не удалась, и на следующих цитатах из документов, я собираюсь сделать вывод, что это невозможно.

  1. # P2 #
    # P3 #
  2. # P4 #
    # P5 #
  3. # P6 #
    # P7 #

Опять же, ничего окончательного, но, на мой взгляд, между getopt и argparse проводится очень резкое разделение с документацией, одобряющей / защищающей argparse.

Вот пример использования gnu_getop(), который соответствует вашему -z [file [file]] тесту:

>>> args = 'file1 -z file2'.split()
>>> args
['file1', '-z', 'file2']
>>> opts, args = getopt.gnu_getopt(args, 'z')
>>> opts
[('-z', '')]
>>> args
['file1', 'file2']

Изменить 1: измените себя с помощью argparse

Вдохновленный определением "перестановки" на странице "Использование Getopt", на которую вы ссылаетесь,

По умолчанию содержимое argv переставляется при его сканировании, так что в конечном итоге все не-параметры находятся в конце.

как насчет перестановки строки arg перед ее передачей parse_args()?

import argparse

p = argparse.ArgumentParser();
p.add_argument('files',nargs='*',default=['-']);
p.add_argument('-z',action='store_true')

Прокатывая собственное:

import re

def permute(s, opts_ptn='-[abc]'):
    """Returns a permuted form of arg string s using a regular expression."""
    opts = re.findall(opts_ptn, s)
    args = re.sub(opts_ptn, '', s)
    return '{} {}'.format(' '.join(opts), args).strip()

>>> p.parse_args(permute('bar -z foo', '-[z]').split())
Namespace(files=['bar', 'foo'], z=True)

Использование getopt:

import getopt

def permute(s, opts_ptn='abc'):
    """Returns a permuted form of arg string s using `gnu_getop()'."""
    opts, args = getopt.gnu_getopt(s.split(), opts_ptn)
    opts = ' '.join([''.join(x) for x in opts])
    args = ' '.join(args)
    return '{} {}'.format(opts, args).strip()

>>> p.parse_args(permute('bar -z foo', 'z').split())
Namespace(files=['bar', 'foo'], z=True)
person Zach Young    schedule 09.04.2012