Specifying default filenames with argparse, but not opening them on --help?

StackOverflow https://stackoverflow.com/questions/8236954

  •  07-03-2021
  •  | 
  •  

Pergunta

Let's say I have a script that does some work on a file. It takes this file's name on the command line, but if it's not provided, it defaults to a known filename (content.txt, say). With python's argparse, I use the following:

parser = argparse.ArgumentParser(description='my illustrative example')
parser.add_argument('--content', metavar='file', 
                     default='content.txt', type=argparse.FileType('r'),
                     help='file to process (defaults to content.txt)')
args = parser.parse_args()
# do some work on args.content, which is a file-like object

This works great. The only problem is that if I run python myscript --help, I get an ArgumentError if the file isn't there (which I guess makes sense), and the help text is not shown. I'd rather it not try to open the file if the user just wants --help. Is there any way to do this? I know I could make the argument a string and take care of opening the file myself later (and I've been doing that), but it would be convenient to have argparse take care of it.

Foi útil?

Solução

Looking at the argparse code, I see:

  • ArgumentParser.parse_args calls parse_known_args and makes sure that there isn't any pending argument to be parsed.
  • ArgumentParser.parse_known_args sets default values and calls ArgumentParser._parse_known_args

Hence, the workaround would be to use ArgumentParser._parse_known_args directly to detect -h and, after that, use ArgumentParser.parse_args as usual.

import sys, argparse
parser = argparse.ArgumentParser(description='my illustrative example', argument_default=argparse.SUPPRESS)
parser.add_argument('--content', metavar='file',
                     default='content.txt', type=argparse.FileType('r'),
                     help='file to process (defaults to content.txt)')
parser._parse_known_args(sys.argv[1:], argparse.Namespace())
args = parser.parse_args()

Note that ArgumentParser._parse_known_args needs a couple of parameters: the arguments from the command line and the namespace.

Of course, I wouldn't recommend this approach since it takes advantage of the internal argparse implementation and that might change in the future. However, I don't find it too messy, so you still might want to use it if you think maintenance risks pay off.

Outras dicas

You could subclass argparse.FileType:

import argparse
import warnings

class ForgivingFileType(argparse.FileType):
    def __call__(self, string):
        try:
            super(ForgivingFileType,self).__call__(string)
        except IOError as err:
            warnings.warn(err)

parser = argparse.ArgumentParser(description='my illustrative example')
parser.add_argument('--content', metavar='file', 
                     default='content.txt', type=ForgivingFileType('r'),
                     help='file to process (defaults to content.txt)')
args = parser.parse_args()

This works without having to touch private methods like ArgumentParser._parse_known_args.

Use stdin as default:

parser.add_argument('file', default='-', nargs='?', type=argparse.FileType('r'))

Perhaps you could define your own type or action in the add_argument call that checks if the file exists, and returns a file handle if it does and None (or something else) otherwise.

This would require you to write some code of yourself as well though, but if the default value can not always be used you probably have to do some checking sooner or later. Like Manny D argues you might want to reconsider your default value.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top