how to get argparse to read arguments from a file with an option rather than prefix

扶醉桌前 提交于 2019-11-29 06:48:53

问题


I would like to know how to use python's argparse module to read arguments both from the command line and possibly from text files. I know of argparse's fromfile_prefix_chars but that's not exactly what I want. I want the behavior, but I don't want the syntax. I want an interface that looks like this:

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

When argparse sees -A, it should stop reading from sys.argv or whatever I give it, and call a function I write that will read somefile.text and return a list of arguments. When the file is exhausted it should resume parsing sys.argv or whatever. It's important that the processing of the arguments in the file happen in order (ie: -foo should be processed, then the arguments in the file, then -bar, so that the arguments in the file may override --foo, and --bar might override what's in the file).

Is such a thing possible? Can I write a custom function that pushes new arguments onto argparse's stack, or something to that effect?


回答1:


You can solve this by using a custom argparse.Action that opens the file, parses the file contents and then adds the arguments then.

For example this would be a very simple 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)

Which you can the use like this:

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

The resulting namespace in args will then also contain any configuration that was also loaded from the file.

If you need a more sophisticated parsing, you can also parse the in-file configuration separately first and then selectively choose which values should be taken over. For example, it might make sense to disallow specifying another file in the config file:

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)

Of course, you could also make the file reading a bit more complicated, for example read from JSON first.




回答2:


You commented that

I need to be able to write my own function to read that file and return the arguments (it's not in a one-argument-per-line format) –

There is a provision in the existing prefix-file handler to change how the file is read. The file is read by a 'private' method, parser._read_args_from_files, but it calls a simple public method that converts a line to strings, default one-argument-per-line action:

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

It was written this way so you could easily customize it. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

A useful override of this method is one that treats each space-separated word as an argument:

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

In the test_argparse.py unittesting file there is a test case for this alternative.


But if you still want to trigger this read with an argument option, instead of a prefix character, then the custom Action approach is a good one.

You could though write your own function that processes argv before it is passed to the parser. It could be modeled on parser._read_args_from_files.

So you could write a function like:

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

Then invoke your parser with:

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

And yes, this could be wrapped in a ArgumentParser subclass.




回答3:


An Action, when called, gets parser and namespace among its arguments.

So you can put your file through the former to update the latter:

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)


来源:https://stackoverflow.com/questions/27433316/how-to-get-argparse-to-read-arguments-from-a-file-with-an-option-rather-than-pre

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!