问题
Most of my python codes I send a (sometimes long!) list of arguments to, which I know how to parse using getopt. All well and good so far...
However I find it tiresome to type out a long if-then-else structure to pass all the arguments to a list of variables (usually of the same name as the passed argument), so I wanted to write a function that simply dynamically accepts arguments and places them in dynamical variable names, which seemed easiest to do using a dictionary, so that the pre-defined dictionary provided the default possible option list as well as default argument values.
I did this in the following way:
import getopt, sys
def get_args(runpars):
"""routine to dynamically accept arguments"""
try:
opts, args = getopt.getopt(sys.argv[1:],"h",[key+"=" for key in runpars])
except getopt.GetoptError:
print ('Use these arguments ',runpars)
sys.exit(2)
for opt, arg in opts:
runpars[opt[2:]]=arg
return(runpars)
if __name__ == "__main__":
# default argument values:
runpars={"a":1,"animal":"cat","f":3.3}
runpars=get_args(runpars)
print(runpars)
This essentially works:
If I pass an argument not in the dictionary, it bombs as intended with the key list:
getopts_test.py --a=4000 --animal="dog" --c=2.1 --dfrfrfr=23
Use these arguments {'a': 1, 'animal': 'cat', 'f': 3.3}
If I pass some arguments it correctly overrides my options as desired
getopts_test.py --a=4000 --animal="dog"
{'a': '4000', 'animal': 'dog', 'f': 3.3}
BUT! as arguments are all passed as strings, my type has now changed to a string... In my old fashioned clunky if-then-else manually parsing, I of course would convert each argument manually with e.g.
param=int(arg)
etc etc, but now I can't do this. So my questions is, is there a way somehow of testing the original dictionary key type and using that to convert the argument from a string.
I essentially want to find some "as_type" method, like
runpars[opt[2:]]=arg.as_type(original key this opt matched)
回答1:
You found a really neat way of solving this using argparse
. You can take advantage of the Namespace object which can be passed to the parse_args() method to better handle the defaults. The idea is simple: instead of creating a new namespace object from the passed arguments and then augmenting it with the defaults, simply pre-create a namespace object from the defaults, and have argparse
update it with the passed arguments.
This will reduce your code a bit:
def get_args(defaultpars):
parser = argparse.ArgumentParser(description='Dynamic arguments')
namespace = argparse.Namespace(**defaultpars)
# add each key of the default dictionary as an argument expecting the same type
for key, val in defaultpars.items():
parser.add_argument('--'+key, type=type(val))
parser.parse_args(namespace=namespace)
return vars(namespace)
回答2:
Thanks to @Wolfgang Kuehn for his pointer to argparse. That has the useful "type" option in add_argument, which solved my problem!
So here is my new solution written from scratch using argparse:
import argparse
def get_args(defaultpars):
parser = argparse.ArgumentParser(description='Dynamic arguments')
# add each key of the default dictionary as an argument expecting the same type
for key,val in defaultpars.items():
parser.add_argument('--'+key,type=type(val))
newpars=vars(parser.parse_args())
# Missing arguments=None need to be overwritten with default value
for key,val in newpars.items():
if val==None:
newpars[key]=defaultpars[key]
return(newpars)
if __name__ == "__main__":
# default values:
runpars={"a":1,"animal":"cat","f":3.3}
runpars=get_args(runpars)
print(runpars)
giving the correct types in the resulting dictionary:
getopts_test.py --a=4000 --animal="dog"
{'a':4000, 'animal': 'dog', 'f': 3.3}
来源:https://stackoverflow.com/questions/64367210/how-to-perform-automatic-type-conversion-in-dynamic-argument-parsing