2014-12-11 9 views
11

Ich würde gerne wissen, wie Python Argparse-Modul zum Lesen von Argumenten sowohl aus der Befehlszeile und möglicherweise aus Textdateien verwenden. Ich kenne Argarses fromfile_prefix_chars, aber das ist nicht genau das, was ich will. Ich möchte das Verhalten, aber ich will nicht die Syntax. Ich möchte eine Schnittstelle, die wie folgt aussieht:wie argarse zu lesen Argumente aus einer Datei mit einer Option anstelle von Präfix

$ python myprogram.py --foo 1 -A somefile.txt --bar 2 

Wenn argparse -A sieht, sollte es aufhören von sys.argv lesen oder was auch immer ich es geben, und rufen Sie eine Funktion, die ich schreiben, dass liest somefile.text und Rückkehr eine Liste von Argumenten. Wenn die Datei erschöpft ist, sollte sie mit der Analyse von sys.argv oder was auch immer weitermachen. Es ist wichtig, dass die Verarbeitung der Argumente in der Datei der Reihe nach erfolgt (dh: -foo sollte verarbeitet werden, dann die Argumente in der Datei, dann -bar, damit die Argumente in der Datei überschrieben werden können --foo, und - Bar könnte überschreiben, was in der Datei ist).

Ist so etwas möglich? Kann ich eine benutzerdefinierte Funktion schreiben, die neue Argumente in den Stapel von argparse schiebt?

+0

Was würde in Ihrer Version die spezielle Behandlung eines Arguments (anstelle eines Präfixzeichens) auslösen? – martineau

+0

@martineau: die "-A" -Option sagt "das nächste Argument ist eine Datei, die gelesen werden soll". Ich muss in der Lage sein, meine eigene Funktion zu schreiben, diese Datei zu lesen und die Argumente zurückzugeben (es ist nicht in einem Ein-Argument-pro-Zeile-Format) –

+0

Blick auf die [Quelle von _parse_known_args] (https: //hg.python. org/cpython/file/3.4/Lib/argparse.py # l1769), es sieht so aus, als würde argparse alle Argumente von vorn kennen. Wenn Sie @from_prefix_chars gesetzt haben, dann sieht es sich die Argumente an und erstellt eine vollständig neue Liste zum Parsen (_read_args_from_files). Ich denke, Ihre beste Option ist, sys.argv im Voraus zu parsen, um die Liste der Argumente zu erhalten. – mgilson

Antwort

19

Sie dies mithilfe einer benutzerdefinierten lösen können argparse.Action, der die Datei öffnet, analysiert den Inhalt der Datei und fügt dann die Argumente dann.

Für dieses Beispiel eine sehr einfache Maßnahme wäre:

class LoadFromFile (argparse.Action): 
    def __call__ (self, parser, namespace, values, option_string = None): 
     with values as f: 
      parser.parse_args(f.read().split(), namespace) 

, die Sie die Verwendung wie diese kann:

parser = argparse.ArgumentParser() 
# other arguments 
parser.add_argument('--file', type=open, action=LoadFromFile) 
args = parser.parse_args() 

Der resultierende Namespace in args wird dann jede Konfiguration enthält auch die auch war aus der Datei geladen.

Wenn Sie eine komplexere Analyse benötigen, können Sie die In-File-Konfiguration auch zuerst separat analysieren und dann selektiv auswählen, welche Werte übernommen werden sollen. Zum Beispiel könnte es sinnvoll sein andere Datei in der Konfigurationsdatei angeben, um nicht zuzulassen:

def __call__ (self, parser, namespace, values, option_string=None): 
    with values as f: 
     contents = f.read() 

    data = parser.parse_args(contents.split(), namespace=namespace) 
    for k, v in vars(data).items(): 
     if v and k != option_string.lstrip('-'): 
      setattr(namespace, k, v) 

Natürlich können Sie die Datei auch machen könnten zunächst etwas komplizierter, zum Beispiel liest von JSON zu lesen.

+0

Ah-ha! Der Schlüssel besteht darin, zu erkennen, dass der Parser an die benutzerdefinierte Aktion übergeben wird, und Sie können diesen Parser verwenden, um den Inhalt der Datei zu verarbeiten. Vielen Dank. –

+0

Wenn der Parser in der globalen Umgebung jedoch einen anderen Namen hat, wäre er auch hier verfügbar. Also könnte jeder Parser hier verwendet werden. Es gibt nichts besonderes an dem Parser, der als Parameter übergeben wurde. – hpaulj

+1

Leider schlägt dies fehl, wenn einer Ihrer Parameter auf Required gesetzt ist. –

1

Ein Action, wenn er aufgerufen wird, bekommt parser und namespace zu seinen Argumenten.

So können Sie Ihre Datei durch das ehemalige setzen diese zu aktualisieren:

class ArgfileAction(argparse.Action): 
    def __call__(self, parser, namespace, values, option_string=None): 
     extra_args = <parse_the_file>(values) 
     #`namespace' is updated in-place when specified 
     parser.parse_args(extra_args,namespace) 
+3

Ich glaube, dass OP bereits über dieses Feature weiß, wie durch die Aussage "Ich kenne argparse's fromfile_prefix_chars, aber das ist nicht genau das, was ich will" – mgilson

2

Sie kommentierte, dass

Ich brauche meine eigene Funktion zu schreiben, in der Lage sein, diese Datei zu lesen und die Argumente zurückkehren (es ist nicht in einem Ein-Argument-per-line-Format) -

Im vorhandenen Präfix-Dateihandler gibt es eine Bestimmung, um zu ändern, wie die Datei gelesen wird. Die Datei wird von einer ‚privaten‘ Methode lesen, parser._read_args_from_files, aber es ruft eine einfache öffentliche Methode, die eine Linie in Strings konvertiert, Standard einargumentigen-per-line Aktion:

def convert_arg_line_to_args(self, arg_line): 
    return [arg_line] 

es auf diese Weise geschrieben wurde so Sie könnten es leicht anpassen. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

Eine nützliche Überschreibung dieser Methode ist eine, die jede durch Leerzeichen getrenntes Wort als Argument behandelt:

In test_argparse.py Unittesting Datei gibt es einen Testfall für diese Alternative.


Aber wenn Sie immer noch diese Lese mit einem Argument Option auslösen soll, anstelle eines Präfixzeichen, dann die benutzerdefinierte Aktion Ansatz ist ein guter.

Sie könnten jedoch Ihre eigene Funktion schreiben, die argv verarbeitet, bevor es an die parser übergeben wird. Es könnte auf parser._read_args_from_files modelliert werden.

So könnten Sie eine Funktion wie schreiben:

def read_my_file(argv): 
    # if there is a '-A' string in argv, replace it, and the following filename 
    # with the contents of the file (as strings) 
    # you can adapt code from _read_args_from_files 
    new_argv = [] 
    for a in argv: 
     .... 
     # details left to user 
    return new_argv 

Dann wird Ihr Parser aufrufen mit:

parser.parse_args(read_my_file(sys.argv[1:])) 

Und ja, diese in einer ArgumentParser Unterklasse eingewickelt werden.