2011-12-29 4 views
11

Sto lavorando con argparse e sto provando a mescolare sottocomandi e argomenti posizionali, e il problema seguente è venuto fuori.Argomenti e sottocomandi Python argparse

Questo codice funziona benissimo:

import argparse 
parser = argparse.ArgumentParser() 
subparsers = parser.add_subparsers() 

parser.add_argument('positional') 
subparsers.add_parser('subpositional') 

parser.parse_args('subpositional positional'.split()) 

Il codice sopra analizza i args in Namespace(positional='positional'), tuttavia quando ho modificare l'argomento posizionale di avere nargs = '?' come ad esempio:

import argparse 
parser = argparse.ArgumentParser() 
subparsers = parser.add_subparsers() 

parser.add_argument('positional', nargs='?') 
subparsers.add_parser('subpositional') 

parser.parse_args('subpositional positional'.split()) 

errori fuori con:

usage: [-h] {subpositional} ... [positional] 
: error: unrecognized arguments: positional 

perché è questo?

+0

Btw, sembra un [noto bug ] (http://bugs.python.org/issue9340), che è stato corretto per le recenti versioni di Python. –

risposta

9

In un primo momento ho pensato lo stesso di jcollado, ma poi c'è il fatto che, se le successive (livello superiore) argomenti posizionali hanno una specifica nargs (nargs = None, nargs = intero), quindi funziona come previsto. Fallisce quando nargs è '?' o '*' e talvolta quando è '+'. Così, sono andato giù al codice, per capire cosa sta succedendo.

Si riduce al modo in cui gli argomenti sono suddivisi per essere consumati. Per capire chi ottiene cosa, la chiamata a parse_args riepiloga gli argomenti in una stringa come 'AA', nel tuo caso ('A' per argomenti posizionali, 'O' per facoltativo) e finisce per produrre un modello di espressioni regolari da abbinare a quella stringa di riepilogo, in base sulle azioni che hai aggiunto al parser tramite i metodi .add_argument e .add_subparsers.

In ogni caso, per l'esempio, la stringa argomento termina con 'AA'. Ciò che cambia è il modello da abbinare (è possibile vedere i possibili modelli sotto _get_nargs_pattern in argparse.py. Per subpositional si finisce per essere '(-*A[-AO]*)', il che significa permettono un argomento seguito da un qualsiasi numero di opzioni o argomenti.Per positional, dipende dal valore trasmesso a nargs:

  • None =>'(-*A-*)'
  • 3 =>'(-*A-*A-*A-*)' (uno '-*A' per argomento expected)
  • '?' =>'(-*A?-*)'
  • '*' =>'(-*[A-]*)'
  • '+' =>'(-*A[A-]*)'

quei modelli vengono aggiunti e, per nargs=None (il vostro esempio di lavoro), si finisce con '(-*A[-AO]*)(-*A-*)', che corrisponde a due gruppi ['A', 'A']. In questo modo, subpositional analizzerà solo subpositional (cosa volevi), mentre positional corrisponderà all'azione.

Per nargs='?', si finisce con '(-*A[-AO]*)(-*A?-*)'. Il secondo gruppo è interamente composto da modelli opzionali e * è avido, ovvero il primo gruppo globifica tutto nella stringa, fino a riconoscere i due gruppi ['AA', '']. Ciò significa che subpositional ottiene due argomenti e finisce per soffocare, ovviamente.

Abbastanza divertente, il modello per nargs='+' è '(-*A[-AO]*)(-*A[A-]*)', che funziona finché si passa solo un argomento. Dì subpositional a, poiché hai bisogno di almeno un argomento posizionale nel secondo gruppo. Ancora una volta, dato che il primo gruppo è avido, passare subpositional a b c d ti dà ['AAAA', 'A'], che non è quello che volevi.

In breve: un casino. Credo che questo dovrebbe essere considerato un errore, ma non sono sicuro che l'impatto sarebbe se i modelli si trasformano in quelli non-avidi ...

+0

Si noti che, naturalmente, aggiungendo i subparser dopo tutto il livello superiore, come suggerito dalla documentazione di jcollado e argparses, si rompono le ambiguità e si lavora come previsto! –

5

Penso che il problema è che quando si chiama add_subparsers, un nuovo parametro viene aggiunto al parser originale per passare il nome del subparser.

Ad esempio, con questo codice:

import argparse 
parser = argparse.ArgumentParser() 
subparsers = parser.add_subparsers() 

parser.add_argument('positional')            
subparsers.add_parser('subpositional')            

parser.parse_args() 

Si ottiene la seguente stringa di aiuto:

usage: test.py [-h] {subpositional} ... positional 

positional arguments: 
    {subpositional} 
    positional 

optional arguments: 
    -h, --help  show this help message and exit 

noti che subpositional viene visualizzato prima positional. Direi che quello che stai cercando è avere l'argomento posizionale prima del nome del subparser. Quindi, probabilmente quello che stai cercando è aggiunta l'argomento prima che i subparsers:

import argparse 
parser = argparse.ArgumentParser() 
parser.add_argument('positional') 

subparsers = parser.add_subparsers() 
subparsers.add_parser('subpositional') 

parser.parse_args() 

La stringa aiuto ottenuto con questo codice è:

usage: test.py [-h] positional {subpositional} ... 

positional arguments: 
    positional 
    {subpositional} 

optional arguments: 
    -h, --help  show this help message and exit 

In questo modo, si passa prima gli argomenti al parser principale, quindi il nome del subparser e infine gli argomenti del subparser (se presente).

+0

Purtroppo non sembra funzionare. Aiuta veramente come dovrebbe, ma in pratica non cambia il processo di analisi. – kcpr

6
import argparse 
parser = argparse.ArgumentParser() 
parser.add_argument('positional', nargs='?') 

subparsers = parser.add_subparsers() 
subparsers.add_parser('subpositional') 

print(parser.parse_args(['positional', 'subpositional'])) 
# -> Namespace(positional='positional') 
print(parser.parse_args(['subpositional'])) 
# -> Namespace(positional=None) 
parser.print_usage() 
# -> usage: bpython [-h] [positional] {subpositional} ... 

La pratica comune è che gli argomenti prima che il comando (a sinistra lato) appartengono al programma principale, dopo (a destra) - al comando. Pertanto positional dovrebbe andare prima del comando subpositional. Programmi di esempio: git, twistd.

Inoltre un argomento con narg=? dovrebbe probabilmente essere un'opzione (--opt=value) e non un argomento posizionale.

+0

Cosa succede se il subparser ha argomenti posizionali? Come possiamo lasciare 'print (parser.parse_args (['subpositional', 'subparserarg']))' print: '# -> Namespace (posizionale = None)'? Questo avrebbe dovuto dire che stiamo selezionando il sottocomando "subposizionale" con l'argomento "posizionale" essendo facoltativo. È possibile? – jmlopez

0

È ancora un disastro in Python 3.5.

suggerisco di sottoclasse ArgumentParser per mantenere tutti gli argomenti posizionali rimanenti, e di trattare con loro più tardi:

import argparse 

class myArgumentParser(argparse.ArgumentParser): 
    def parse_args(self, args=None, namespace=None): 
     args, argv = self.parse_known_args(args, namespace) 
     args.remaining_positionnals = argv 
     return args 

parser = myArgumentParser() 

options = parser.parse_args() 

Gli argomenti posizionali rimanenti sono nella lista options.remaining_positionals