2017-01-02 3 views
1

Ich teste gerade eine argparse Verwendung, aber es funktioniert nicht wie erwartet. Ich habe ein paar subparsers und optionale Argumente, die folgende Art und Weise genannt werden:Optionale Argumente über alle Subparser

python3 myprogram.py positional argument --optional something 

# Outcome 
Namespace(optional='something') 

Das Programm funktioniert wie erwartet, wenn die optionalen die letzte ist, aber wenn es in irgendeiner anderen Ordnung ist, es ist verworfen.

python3 myprogram.py positional --optional argument 
python3 myprogram.py --optional positional argument 

# Outcome 
Namespace(optional=None) 

Durch an der argparse Dokumentation suchen war ich einen Weg zu finden, nicht in der Lage das optionale Argument global zu machen.

Ich erstelle die Positionsargumente für jede Position in einer for-Schleife, die nicht der beste Weg zu sein scheint. Andernfalls würden die optionalen Argumente nur zum letzten Subparser hinzugefügt.

import argparse 

class Parsing(object): 

    def __init__(self): 

     parser = argparse.ArgumentParser(prog='python3 myprogram.py', 
      formatter_class=argparse.RawDescriptionHelpFormatter, 
      description='some description') 

     self.subparser = parser.add_subparsers(title='Positional', help='help description') 

     for sub in self.Generate(): # Method with a bunch of subparsers 
      self.Subparser(sub) 

    def Subparser(self, parsers): 

     for each in sorted(parsers): 
      positional = subparser.add_parser(each) 
      self.Optional(positional) # Method with some optional arguments for each of the second subparsers 

     self.Optional(parser) # Adding the optional arguments to the first subparser 

    def Optional(self, parser): 

     # ... Optional arguments 

    def Generate(self): 

     # ... Subparsers 

Ich könnte einige Code in dem obigen Beispiel fehlt, versucht, das Beste zu vereinfachen ich konnte, und hoffen, dass es wahrnehmbar zu sein.

Frage: Gibt es eine Möglichkeit, die optionalen Argumente über alle Subparser zu machen?

Antwort

2

Ihre Beschreibung und Code ist schwer zu folgen, aber ich habe festgestellt, dass Ihr Problem liegt mit, wie Standardwerte behandelt werden, wenn die Haupt- und Subparser ein Argument teilen dest.

I kondensiert Code ein bisschen so konnte ich einen Testlauf machen:

import argparse 
class Parsing(object): 
    def __init__(self): 
     self.parser = argparse.ArgumentParser(prog='prog', 
      description='some description') 
     self.subparser = self.parser.add_subparsers(dest='cmd', title='Cmds', help='help description') 
     self.make_subparsers(['cmd1','cmd2']) 

    def make_subparsers(self, parsers): 
     for each in parsers: 
      subp = self.subparser.add_parser(each) 
      self.optional(subp, default='sub') 
     self.optional(self.parser, default='main') 

    def optional(self, parser, default=None): 
     parser.add_argument('--foo', default=default) 

args = Parsing().parser.parse_args() 
print(args) 

ich für 2 Läufe erhalten

1315:~/mypy$ python3.5 stack41431025.py cmd1 --foo 1 
Namespace(cmd='cmd1', foo='1') 

1316:~/mypy$ python3.5 stack41431025.py --foo 1 cmd1 
Namespace(cmd='cmd1', foo='sub') 

Im ersten foo durch die Saiten durch die analysiert gesetzt cmd1 Subparser.

In der zweiten Version erhält foo den Standardwert, der vom Subparser festgelegt wird. Der Hauptparser analysierte --foo, aber sein Wert wurde vom Subparser überschrieben.

Dies wurde in Bug/Issues diskutiert. http://bugs.python.org/issue9351 hat die Handhabung geändert, so dass der Subparser-Standard Vorrang vor Hauptparser-Werten hat. Ich denke, es gibt Probleme mit diesem Patch, aber es ist seit ein paar Jahren in Kraft.

Sie behalten mehr Kontrolle, wenn sie verschiedene dest erhalten.

def make_subparsers(self, parsers): 
    for each in parsers: 
     subp = self.subparser.add_parser(each) 
     self.optional(subp, default='sub') 
    self.optional(self.parser, default='main', dest='main_foo') 

def optional(self, parser, default=None, dest=None): 
    parser.add_argument('--foo', default=default, dest=dest) 

1325:~/mypy$ python3.5 stack41431025.py --foo 1 cmd1 
Namespace(cmd='cmd1', foo='sub', main_foo='1') 
1325:~/mypy$ python3.5 stack41431025.py cmd1 
Namespace(cmd='cmd1', foo='sub', main_foo='main') 
1325:~/mypy$ python3.5 stack41431025.py --foo 1 cmd1 --foo 2 
Namespace(cmd='cmd1', foo='2', main_foo='1') 

====================

(frühe Antwort)

Ich werde versuchen, die möglichen Kombinationen von Argumenten zu skizzieren

parser = argparse.ArgumentParser() 
parser.add_argument('mainpos', help='positional for main') 
parser.add_argument('--mainopt', help='optional defined for main') 
sp = parser.add_subparser(dest='cmd') 
p1 = sp.add_parser('cmd1') 
p1.add_argument('subpos', help='postional for sub') 
p1.add_argument('--subopt', help='optional defined for sub') 

Verbund usage würde wie folgt aussehen:

python prog.py foo [--mainopt bar] cmd1 sfoo [--subopt baz] 

Die entsprechenden positionals müssen in der richtigen Reihenfolge angegeben werden. Der Subparser cmd ist effektiv eine Position für die main.

Das für main definierte Optional muss vor dem Subparser-Namen stehen. Das optional für den Subparser definierte muss danach auftreten. Sie könnten die gleichen flag oder dest haben, aber sie müssen separat definiert werden. Und wenn sie das gleiche haben dest, könnte es einen Konflikt über Werte, insbesondere Standardwerte geben.

parser.parse_args() beginnt mit den Eingabezeichenfolgen mit seinen Argumenten übereinzustimmen. Wenn es --mainopt sieht, analysiert dieses optionale Argument. Ansonsten erwartet es zwei Optionen. Der zweite muss einer der Subparser-Namen sein.

Sobald es einen Subparser-Namen erhält, übergibt es die verbleibenden Zeichenfolgen an diesen Subparser. Der Subparser behandelt den Rest und legt die Werte im Hauptnamespace ab. Und das erste, was der Subparser tut, sind die Standardeinstellungen. Ob diese Aktion die vom Hauptparser gesetzten Werte überschreibt oder nicht, hängt davon ab, wie der namespace zwischen den beiden übergeben wird.

================

Parsing wird durch die Reihenfolge der Argumente in der Befehlszeile angesteuert. Es versucht, markierte Argumente in beliebiger Reihenfolge zuzulassen. Sobald Parsing jedoch an den Subparser übergeben wird, erhält der Hauptparser beim Parsing keinen weiteren Versuch. Es macht nur ein paar Aufräumarbeiten.

Aber wenn ich parse_known_args verwende, kann ich die Zeichenfolgen sammeln, die keiner der Parsers behandelt hat, und einen weiteren Stich machen, um sie zu analysieren.

parser1 = argparse.ArgumentParser() 
parser1.add_argument('--foo') 
sp = parser1.add_subparsers(dest='cmd') 
sp1 = sp.add_parser('cmd1') 
args, extra = parser1.parse_known_args() 

parser2 = argparse.ArgumentParser() 
parser2.add_argument('--foo') 
if extra: 
    args = parser2.parse_args(extra) 
print(args) 

läuft

1815:~/mypy$ python stack41431025.py --foo 1 cmd1 
Namespace(cmd='cmd1', foo='1') 

1815:~/mypy$ python stack41431025.py cmd1 --foo 2 
Namespace(foo='2') 

1815:~/mypy$ python stack41431025.py --foo 1 cmd1 --foo 3 
Namespace(foo='3') 

Ich habe nicht diese Idee in etwas komplexer, getestet, so könnte es einige Interaktionen, die ich habe nicht gedacht. Aber das ist der nächste, dem ich zu einem markierten Argument kommen kann, das überall auftreten kann und keinen widersprüchlichen default Problemen unterliegt.

+0

Vielen Dank für Ihre ausführliche Antwort Hpaulj, lassen Sie mich testen, um meinen Code zu passen – Flippym

+0

Wenn ich richtig verstanden habe, 'argparse' unterstützt keine globalen Argumente, würde ich sie mit' dest' kontrollieren, in jedem Subparser, richtig ? – Flippym

+0

Ich habe ein Beispiel mit 'Parse_known_args' und einem zweiten Parsing-Lauf hinzugefügt. – hpaulj

Verwandte Themen