问题
I'm dealing with a (Python 3.x) script (written by someone else) where the input and output are currently specified with flagged optional arguments like so:
parser.add_argument('-i', '--input', nargs='?', type = argparse.FileType('r'),
default=sys.stdin, dest='inputfile')
parser.add_argument('-o', '--output-file', nargs='?', type=argparse.FileType('w'),
default=sys.stdout, dest='outputfile')
I'd like to upgrade this script so that the input and output file can be specified as positional arguments while maintaining the existing flag arguments for backwards compatibility. I'd also like to intelligently handle the potential conflicts which might come from mixing the flagged argument with the positional argument (i.e. if only one of -i
or -o
is given then a single positional argument is automatically passed to the other and two positional arguments raises a redundancy error while if both -i
and -o
are given, then any positional arguments raises the redundancy error).
Note: the script as currently written does not accept any positional arguments, though it does accept other flags, some with arguments some without, besides the ones related to the input and output file.
Is this possible with argparse (and if so, how) or do I have to rewrite the argument parsing using something else (and if so, what do you suggest)?
回答1:
Sticking with the FileType
will be awkward. That type
opens or creates the file. So potentially you'll have 4 open files when you only want 2. But if one of those files is stdin
or out
you don't want to close it. And you can't handle a positional
which could either be read or write depending what other arguments are given.
You could try defining 4 default string arguments, 2 flagged, and 2 nargs='?'
positional. Give them different dest
. Then you can apply your intelligence to the 4 possible values. The default default None
should be a clear enough indication that a value wasn't provided. Once you've decided on the two filenames, then you can open and use them. Newer Python recommends using with
contexts, though that can be awkward when a file is already open (e.g. sys.stdin
).
I don't think you should try to implement that logic within argparse
. Do it after parsing.
回答2:
For those interested in a more explicit answer, here is how I eventually implemented @hpaulj's suggestion:
First I defined an argument group for the input and output file arguments:
files = parser.add_argument_group('file arguments:',description='These arguments can also be provided as postional arguments, in which case the input file comes first.')
files.add_argument('-i', '--input', nargs='?',
help='Source of the words to be syllabified. If None or -, then input will be read from stdin.',
dest='inputfile')
files.add_argument('-o', '--output-file', nargs='?',
help='Destination of the syllabified words. If None or -, then ouput will be written to stdout.',
dest='outputfile')
files.add_argument('fileone',nargs='?',
help=argparse.SUPPRESS)
files.add_argument('filetwo',nargs='?',
help=argparse.SUPPRESS)
This allowed me to keep the arguments together, separate from the other arguments for the program, and provided a bit more control over how the help text would appear so that it would make the most sense.
Then, after parsing the arguments (args = parser.parse_args()
), I added the following logic to figure out what the correct input and output were and to open files or stdin and stdout as appropriate:
if (args.inputfile == None or args.inputfile == '-'):
if (args.outputfile == None or args.outputfile == '-'):
if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
input = sys.stdin
output = sys.stdout
elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
try:
input = open(args.fileone,'r')
output = sys.stdout
except:
input = sys.stdin
output = open(args.fileone,'w')
else:
input = open(args.fileone,'r')
output = open(args.filetwo,'w')
else:
if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
input = sys.stdin
output = open(args.outputfile,'w')
elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
input = open(args.fileone,'r')
output = open(args.outputfile,'w')
else:
print("Error: too many files")
print("Both -o and positional output file given")
sys.exit(1)
else:
if (args.outputfile == None or args.outputfile == '-'):
if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
input = open(args.inputfile,'r')
output = sys.stdout
elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
input = open(args.inputfile,'r')
output = open(args.fileone,'w')
else:
print("Error: too many files")
print("Both -i and positional input file give")
sys.exit(1)
else:
if (args.fileone == None or args.fileone == '-') and (args.filetwo == None or args.filetwo == '-'):
input = open(args.inputfile,'r')
output = open(args.outputfile,'w')
elif args.fileone != None and (args.filetwo == None or args.filetwo == '-'):
print("Error: too many files")
print("Both -i and -o given with a positional file")
sys.exit(1)
else:
print("Error: too many files")
print("Both -i and -o given with positional files")
sys.exit(1)
As you can see I decided to accept both the default None
and -
as possibilities for referring to stdin/stdout. This duplicated the behavior of the FileType
argument which also accepted -
in this fashion.
The one ambiguity that remains is that "None"
(i.e. a string of the word "None") is not the same as None
(i.e. the NoneType class) and the help messages might be interpreted to imply that -i None
should refer to stdin. However, I figured that the difference here should be obvious enough to most python users that I wasn't going to complicate the logic any further to account for this possibility.
来源:https://stackoverflow.com/questions/49714105/how-to-add-positional-options-for-existing-arguments-in-argparse