web-dev-qa-db-ja.com

プレフィックスではなくオプションを使用してファイルから引数を読み取るためにargparseを取得する方法

Pythonのargparseモジュールを使用して、コマンドラインと場合によってはテキストファイルの両方から引数を読み取る方法を知りたいです。 argparseのfromfile_prefix_charsは知っていますが、それは私が望んでいることではありません。動作は必要ですが、構文は必要ありません。次のようなインターフェイスが必要です。

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

Argparseが-Aを検出すると、sys.argvまたは私が指定したものからの読み取りを停止し、somefile.textを読み取り、引数のリストを返す関数を呼び出す必要があります。ファイルが使い果たされると、sys.argvなどの解析を再開する必要があります。ファイル内の引数の処理が順番に行われることが重要です(つまり、-fooを処理し、次にファイル内の引数を処理し、次に-barを処理して、ファイル内の引数が--fooおよび-をオーバーライドできるようにする必要があります。バーはファイルの内容を上書きする場合があります)。

そのようなことは可能ですか?新しい引数をargparseのスタックにプッシュするカスタム関数、またはその効果をもたらす何かを作成できますか?

16
Bryan Oakley

カスタムを使用してこれを解決できます argparse.Action ファイルを開き、ファイルの内容を解析してから、引数を追加します。

たとえば、これは非常に単純なアクションになります。

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

あなたはこのように使うことができます:

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

結果として得られるargsの名前空間には、ファイルからロードされた構成も含まれます。

より高度な解析が必要な場合は、最初にファイル内の構成を個別に解析してから、引き継ぐ値を選択的に選択することもできます。たとえば、構成ファイルでanotherファイルを指定できないようにするのが理にかなっている場合があります。

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)

もちろん、ファイルの読み取りをもう少し複雑にすることもできます。たとえば、最初にJSONから読み取ることができます。

26
poke

あなたはそれをコメントしました

そのファイルを読み取り、引数を返すために独自の関数を記述できる必要があります(1行に1つの引数の形式ではありません)–

既存のプレフィックスファイルハンドラーには、ファイルの読み取り方法を変更するための規定があります。ファイルは「private」メソッドparser._read_args_from_filesによって読み取られますが、行を文字列に変換する単純なpublicメソッドを呼び出します。デフォルトでは、行ごとに1つの引数が実行されます。

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

簡単にカスタマイズできるようにこのように書かれています。 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

このメソッドの便利なオーバーライドは、スペースで区切られた各単語を引数として扱うメソッドです。

def convert_arg_line_to_args(self, arg_line):
    for arg in arg_line.split():
        if not arg.strip():
            continue
        yield arg

test_argparse.py unittestingファイルには、この代替のテストケースがあります。


ただし、プレフィックス文字の代わりに引数オプションを使用してこの読み取りをトリガーする場合は、カスタムアクションアプローチが適しています。

ただし、argvに渡される前にparserを処理する独自の関数を作成することもできます。 parser._read_args_from_filesでモデル化できます。

したがって、次のような関数を書くことができます。

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

次に、次のコマンドでパーサーを呼び出します。

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

はい、これはArgumentParserサブクラスでラップできます。

3
hpaulj

Actionが呼び出されると、引数の中にparsernamespaceが含まれます。

したがって、ファイルを前者に通して後者を更新できます。

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)
1
ivan_pozdeev