2011-12-29 22 views
11

Pracuję z argparse i próbuję mieszać pod-komendy i argumenty pozycyjne, i pojawił się następujący problem.Python argparse argumenty pozycyjne i pod-komendy

Ten kod działa poprawnie:

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

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

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

Powyższy kod analizuje argumenty do Namespace(positional='positional'), jednak przy zmianie pozycyjnym argumentu mieć nargs = „?” takie jak:

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

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

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

It błędów za pomocą:

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

Dlaczego tak jest?

+0

Btw, wydaje się [znany bug ] (http://bugs.python.org/issue9340), który został naprawiony dla ostatnich wersji Pythona. –

Odpowiedz

9

Na początku myślałem, że tak samo jak jcollado, ale potem jest fakt, że jeśli kolejny (top level) pozycyjne argumenty mają określoną nargs (nargs = None, nargs = liczba całkowita), to działa zgodnie z oczekiwaniami. Nie powiedzie się, gdy nargs jest lub '*', a czasami, gdy jest to '+'. Więc poszedłem do kodu, aby dowiedzieć się, co się dzieje.

Sprowadza się to do sposobu podziału argumentów na zużyte. Aby dowiedzieć się, kto ma co, wywołanie parse_args podsumowuje argumenty w łańcuchu, takim jak 'AA', w twoim przypadku ('A' dla argumentów pozycyjnych, 'O' dla opcjonalnych), a kończy się tworzeniem wzorca regex do dopasowania do tego ciągu podsumowującego, w zależności od o działaniach dodanych do analizatora składni za pomocą metod.

W każdym przypadku, na przykład, ciąg argumentów kończy się na 'AA'. Jakie zmiany to dopasowywany wzorzec (można zobaczyć możliwe wzorce pod _get_nargs_pattern w argparse.py. Dla subpositional kończy się to '(-*A[-AO]*)', co oznacza, że ​​zezwala na jeden argument, po którym następuje dowolna liczba opcji lub argumentów:.Dla positional, zależy od wartości przekazanej nargs:

  • None =>'(-*A-*)'
  • 3 =>'(-*A-*A-*A-*)' (jeden '-*A' za oczekiwanego argument)
  • '?' =>'(-*A?-*)'
  • '*' =>'(-*[A-]*)'
  • '+' =>'(-*A[A-]*)'

Te wzory są dołączane, a dla nargs=None (Twój przykład pracy), możesz skończyć z '(-*A[-AO]*)(-*A-*)', który pasuje dwie grupy ['A', 'A']. W ten sposób subpositional będzie analizować tylko subpositional (co chcesz), podczas gdy positional dopasuje swoją akcję.

W przypadku nargs='?' użytkownik otrzymuje jednak '(-*A[-AO]*)(-*A?-*)'. Druga grupa składa się w całości z opcjonalnych wzorów, a * jest chciwy, co oznacza, że ​​pierwsza grupa globs wszystko w ciągu znaków, kończąc rozpoznawanie dwóch grup ['AA', '']. Oznacza to, że subpositional dostaje dwa argumenty i oczywiście kończy się zadławieniem.

Wystarczająco zabawne, wzorzec dla nargs='+' to '(-*A[-AO]*)(-*A[A-]*)', który działa , o ile tylko przekazuje jeden argument. Powiedz subpositional a, ponieważ potrzebujesz co najmniej jednego argumentu pozycyjnego w drugiej grupie. Ponownie, ponieważ pierwsza grupa jest chciwa, przekazując subpositional a b c d dostajesz ['AAAA', 'A'], co nie jest tym, czego potrzebujesz.

W skrócie: bałagan. Myślę, że to powinno być uznane za błąd, ale nie wiem, jaki wpływ będzie, jeśli wzory są włączone do nich non-chciwych ...

+0

Zauważ, że oczywiście dodanie akapitu po wszystkich najwyższych poziomach, zgodnie z sugestią jcollado i argalizacją dokumentacji, przełamie dwuznaczność i zadziała zgodnie z przeznaczeniem! –

5

Myślę, że problem polega na tym, że po wywołaniu add_subparsers do parsera oryginału dodawany jest nowy parametr, który przekazuje nazwę urządzenia.

Na przykład, z tym kodem:

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

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

parser.parse_args() 

uzyskać następujący ciąg help:

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

positional arguments: 
    {subpositional} 
    positional 

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

Zauważ, że subpositional wyświetlany przed positional. Powiedziałbym, że to, czego szukasz, to argument pozycyjny przed nazwą przedziału. Stąd zapewne co szukasz jest dodanie argumentu przed subparsers:

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

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

parser.parse_args() 

Ciąg pomoc uzyskane z tego kodu jest:

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

positional arguments: 
    positional 
    {subpositional} 

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

ten sposób można przekazać pierwsze argumenty do główny parser, następnie nazwa parsera i wreszcie argumenty do akapitu (jeśli istnieją).

+0

Niestety nie działa. Pomoc naprawdę wygląda tak, jak powinna, ale w praktyce - nie zmienia procesu analizy. – 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} ... 

powszechną praktyką jest, że argumenty przed poleceniem (po lewej side) należą do głównego programu, po (po prawej) - do polecenia. Dlatego positional powinien iść przed komendą subpositional. Przykładowe programy: git, twistd.

Dodatkowo argument z narg=? powinien prawdopodobnie być opcją (--opt=value), a nie argumentem pozycyjnym.

+0

Co się stanie, jeśli argument "parse" zawiera argumenty pozycyjne? Jak możemy pozwolić na 'print (parser.parse_args (['subpozycyjny', 'akapitowy']))' print: '# -> Namespace (pozycyjny = Brak)'? To powinno oznaczać, że wybieramy podpolecenie "podpoziomowe", a argument "pozycyjny" jest opcjonalny. czy to możliwe? – jmlopez

0

Wciąż jest bałagan w Pythonie 3.5.

Proponuję podklasa ArgumentParser zachować wszystkie pozostałe pozycyjnych argumentów i radzić sobie z nimi później:

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() 

Pozostałe pozycyjne argumenty są na liście options.remaining_positionals

Powiązane problemy