O argumento do Python pode permutar a ordem dos argumentos como gnu getopt?

O GNU getopt, e as ferramentas de linha de comando que o utilizam, permitem que opções e argumentos sejam intercalados, conhecidos como permuting options (consulte http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using -Getopt ). O módulo Getopt :: Long de Perl também suporta isto (com qw (: config gnu_getopt)). argparse parece não apoiar (ou mesmo mencionar) opções de permutação.

Existem muitas perguntas SO relacionadas à ordem arg / opt, mas nenhuma parece responder a esta pergunta: Pode-se fazer uma mudança para permutar ordem de argumento como getopt?

O caso de uso é uma assinatura de linha de comando prototípica como o tipo de sorting do GNU:

sort [opts] [files] 

em que 1) opções e arquivos são permutados, e 2) a lista de arquivos pode conter zero ou mais argumentos.

Por exemplo:

 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 ...]] 

Eu tentei:

  • p.parse_known_args – não se queixa, mas na verdade também não permuta e não hesita sobre argumentos que se parecem com opções inválidas (por exemplo, –bogus ou -b acima).
  • p.add_argument (‘files’, nargs = argparse.REMAINDER) – a opção -z é incluída nos arquivos, a menos que antes de argumentos posicionais
  • p.add_argument (‘files’, nargs = ‘*’, action = ‘append’);

Eu quero implementar algo próximo ao protótipo de sorting GNU acima. Eu não estou interessado em um sinalizador que pode ser especificado para cada arquivo (por exemplo, -f file1 -f file2).

Aqui está uma solução rápida que decodifica o par da lista de argumentos um (opções, argumentos posicionais) de cada vez.

 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('-va -zv b -zc -vz d -v'.split())) 

Saída:

 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) 

Nota: Não tente usar isso com outros argumentos que não sejam sinalizadores (além de um único nargs='*' e o argumento args_tail ). O analisador não saberá sobre invocações anteriores de parse_args portanto, armazenará o valor incorreto para esses argumentos sem sinalizador. Como solução alternativa, você pode analisar o nargs='*' manualmente depois de usar interleaved_parse .

Eu não vi nada definitivo na documentação do argass afirmando que pode ou não permutar. Com base em suas próprias observações, em que a permutação falhou e nas citações a seguir, concluo que não pode ser feito.

  1. Já existe um módulo explicitamente chamado ‘ getopt ‘:

    Nota O módulo getopt é um analisador para opções de linha de comandos cuja API foi projetada para ser familiar aos usuários da function C getopt() . Usuários que não estão familiarizados com a function C getopt() ou que gostariam de escrever menos código e obter melhor ajuda e mensagens de erro devem considerar o uso do módulo argparse .

  2. Mesmo o padrão para getopt não é permutado, existe um método mais explicitamente definido chamado gnu_getopt() :

    Esta function funciona como getopt() , exceto que o modo de varredura de estilo GNU é usado por padrão. Isso significa que argumentos opcionais e não opcionais podem ser misturados.

  3. Nos documentos getopt, a referência acima a argparse é ainda mais exagerada pela inclusão do seguinte:

    Note que uma interface de linha de comando equivalente poderia ser produzida com menos código e mais ajuda informativa e mensagens de erro usando o módulo argparse :

Mais uma vez, nada definitivo, mas, para mim, uma divisão muito acentuada está sendo traçada entre getopt e argparse com a documentação favorecendo / defendendo a argumentação.

Aqui está um exemplo usando gnu_getop() que satifica seu teste -z [file [file]] :

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

Edite 1: Vá Permute-se, com argparse

Inspirado na definição de “permute” na página “Usando o Getopt” à qual você está vinculado

O padrão é permutar o conteúdo de argv durante a varredura para que, eventualmente, todas as não opções estejam no final.

que tal permutar a string arg antes de passá-la para parse_args() ?

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

Rolando seu próprio:

 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) 

Aproveitando o 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)