2014-07-10 10 views
10

Ich wollte verschiedene Subparser in einem Skript definieren, wobei beide Optionen von einem gemeinsamen Parent erben, aber mit unterschiedlichen Standardwerten. Es funktioniert jedoch nicht wie erwartet.argparse - Parser, Subparsers und Standardwerte kombinieren

Hier ist, was ich getan habe:

import argparse 

# this is the top level parser 
parser = argparse.ArgumentParser(description='bla bla') 

# this serves as a parent parser 
base_parser = argparse.ArgumentParser(add_help=False) 
base_parser.add_argument('-n', help='number', type=int) 


# subparsers 
subparsers = parser.add_subparsers() 
subparser1= subparsers.add_parser('a', help='subparser 1', 
            parents=[base_parser]) 
subparser1.set_defaults(n=50) 
subparser2 = subparsers.add_parser('b', help='subparser 2', 
            parents=[base_parser]) 
subparser2.set_defaults(n=20) 

args = parser.parse_args() 
print args 

Als ich das Skript von der Befehlszeile ausführen, das ist, was ich bekommen:

$ python subparse.py b 
Namespace(n=20) 

$ python subparse.py a 
Namespace(n=20) 

, Anscheinend ist der zweite set_defaults überschreibt die erste in der Elternteil. Da es in der argparse-Dokumentation (die ziemlich detailliert ist) nichts darüber gab, dachte ich, dies könnte ein Fehler sein.

Gibt es dafür eine einfache Lösung? Ich könnte die Variable args danach überprüfen und None Werte mit den beabsichtigten Standardeinstellungen für jeden Subparser ersetzen, aber das war es, was ich argarse für mich tun wollte.

Dies ist übrigens Python 2.7.

Antwort

7

set_defaults Schleifen durch die Aktionen des Parsers und setzt jedes default Attribut:

def set_defaults(self, **kwargs): 
     ... 
     for action in self._actions: 
      if action.dest in kwargs: 
       action.default = kwargs[action.dest] 

Ihr -n Argument (ein action Objekt) erstellt wurde, wenn Sie die base_parser definiert. Wenn jeder Subparser mit parents erstellt wird, wird diese Aktion zur Liste ._actions jedes Subparsers hinzugefügt. Es definiert keine neuen Aktionen; es kopiert nur Zeiger. Wenn Sie set_defaults unter subparser2 verwenden, ändern Sie die default für diese geteilte Aktion.

Diese Aktion ist wahrscheinlich der 2. Artikel in der subparser1._action Liste (h ist der erste).

subparser1._actions[1].dest # 'n' 
subparser1._actions[1] is subparser2._actions[1] # true 

Wenn die zweite Aussage True ist, bedeutet, dass die gleichen action in beiden Listen ist.

Wenn Sie für jeden Subparser einzeln -n definiert hätten, würden Sie dies nicht sehen. Sie würden verschiedene Aktionsobjekte haben.

Ich arbeite von meinem Wissen über den Code, nichts in der Dokumentation. Es wurde kürzlich in Cause Python's argparse to execute action for default darauf hingewiesen, dass die Dokumentation nichts über add_argument sagt, das ein Action Objekt zurückgibt. Diese Objekte sind ein wichtiger Teil der Code-Organisation, aber sie werden in der Dokumentation nicht besonders beachtet.


Kopieren geordneten Maßnahmen unter Berücksichtigung auch Probleme, wenn der ‚Entschlossenheit‘ Konflikt-Handler verwendet wird, und die Eltern wiederverwendet werden sollen.Dieses Problem wurde in

argparse conflict resolver for options in subcommands turns keyword argument into positional argument

und Python Bug Frage aufgeworfen:

http://bugs.python.org/issue22401

Eine mögliche Lösung, sowohl für dieses Problem, und dass zu ist (optional) eine Kopie der Aktion machen , anstatt die Referenz zu teilen. Auf diese Weise können die option_strings und defaults in den Kindern geändert werden, ohne die Eltern zu beeinflussen.

+0

Danke, vermutete ich so viel. Ich beschloss, die Standardwerte nach dem Parsen von Argumenten zu behandeln. – erickrf

5

Was

Das Problem hier passiert, ist, dass Parser Argumente Objekte sind, und wenn ein Parser von seinen Eltern erbt, fügt es einen Verweis auf die Klage der Eltern auf seine eigene Liste. Wenn Sie set_default aufrufen, wird der Standardwert für dieses Objekt festgelegt, das für alle Unterparser gemeinsam genutzt wird.

Sie können die subparsers untersuchen, dies zu sehen:

>>> a1 = [ action for action in subparser1._actions if action.dest=='n' ].pop() 
>>> a2 = [ action for action in subparser2._actions if action.dest=='n' ].pop() 
>>> a1 is a2 # same object in memory 
True 
>>> a1.default 
20 
>>> type(a1) 
<class 'argparse._StoreAction'> 

Erste Lösung: Explizit hinzufügen, um dieses Argument zu jeder subparser

Sie können dieses Problem beheben, indem Sie das Argument zu jeder subparser separate Zugabe statt Hinzufügen zu der Basisklasse.

subparser1= subparsers.add_parser('a', help='subparser 1', 
           parents=[base_parser]) 
subparser1.add_argument('-n', help='number', type=int, default=50) 
subparser2= subparsers.add_parser('b', help='subparser 2', 
           parents=[base_parser]) 
subparser2.add_argument('-n', help='number', type=int, default=20) 
... 

Zweite Lösung: mehrere Basisklassen

Wenn viele subparsers sind, die den gleichen Standardwert teilen, und Sie wollen, dies zu vermeiden, können Sie verschiedene Basisklassen für jeden Standard erstellen. Da es sich bei Eltern um eine Liste von Basisklassen handelt, können Sie die allgemeinen Teile immer noch in einer anderen Basisklasse gruppieren und die mehreren Basisklassen des Unterparsers übergeben, von denen er erbt. Dies ist wahrscheinlich unnötig kompliziert.

import argparse 

# this is the top level parser 
parser = argparse.ArgumentParser(description='bla bla') 

# this serves as a parent parser 
base_parser = argparse.ArgumentParser(add_help=False) 
# add common args 

# for group with 50 default 
base_parser_50 = argparse.ArgumentParser(add_help=False) 
base_parser_50.add_argument('-n', help='number', type=int, default=50) 

# for group with 50 default 
base_parser_20 = argparse.ArgumentParser(add_help=False) 
base_parser_20.add_argument('-n', help='number', type=int, default=20) 

# subparsers 
subparsers = parser.add_subparsers() 
subparser1= subparsers.add_parser('a', help='subparser 1', 
            parents=[base_parser, base_parser_50]) 

subparser2 = subparsers.add_parser('b', help='subparser 2', 
            parents=[base_parser, base_parser_20]) 

args = parser.parse_args() 
print args 

Erste Lösung mit geteiltem args

Sie auch ein Wörterbuch für die Argumente austauschen und Auspacken verwenden, um zu vermeiden, alle Argumente zu wiederholen:

import argparse 

# this is the top level parser 
parser = argparse.ArgumentParser(description='bla bla') 

n_args = '-n', 
n_kwargs = {'help': 'number', 'type': int} 

# subparsers 
subparsers = parser.add_subparsers() 
subparser1= subparsers.add_parser('a', help='subparser 1') 
subparser1.add_argument(*n_args, default=50, **n_kwargs) 

subparser2 = subparsers.add_parser('b', help='subparser 2') 
subparser2.add_argument(*n_args, default=20, **n_kwargs) 

args = parser.parse_args() 
print args 
+0

Danke für die gründliche Antwort. Da ich mehr als zwei Unterparser und viele gemeinsame Argumente (einschließlich mehr als ein Elternteil) habe, entschied ich mich, eine Funktion zum Setzen von Standardwerten zu erstellen, nachdem die Argumente analysiert wurden. – erickrf

Verwandte Themen