I understand that and why using eval
is generally considered bad practice in most cases (see e.g. here). Related questions on config files and argparse don't use types. (1, 2)
I needed to parse a number of command line arguments and decided to store the arguments, defaults and help strings in a .json
file. I read the .json
and add every argument to an argparse.ArgumentParser
. To specify a type I need to pass a callable like float
and not the string "float"
.
To get from the string to the callable I thought of
- using eval
- using a dict to map from string to callable
- using a if/else or switch
and decided to use to use eval
to avoid hard coding all types.
I have no security concerns because the argument file is supplied by the user and the program that uses this code is targeted at scientists that will realistically want to change the file to add parameters or change defaults. (Also, it is a university project which will never be run except for grading. I handed in a version using eval
.)
Is there a smart solution avoiding hardcoding all types and avoiding eval, or did I find a place where eval
is a sensible choice? I was only allowed to use the standard library.
Minimal args.json
:
{
"dt": {
"type": "float",
"help": "Time step size",
"default": 0.4},
"T": {
"type": "int",
"help": "Time steps between outputs",
"default": 50},
"f": {
"type": "str",
"help": "Landscape file name",
"default": "small.dat"}
}
Runnable code, put args.json
above in same directory to run:
import json
import argparse
import pprint
def setup_parser(arguments, title):
parser = argparse.ArgumentParser(description=title,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
for key, val in arguments.items():
parser.add_argument('-%s' % key,
type=eval(val["type"]),
help=val["help"],
default=val["default"])
return parser
def read_params(parser):
parameters = vars(parser.parse_args())
return parameters
def get_parameters(title=None):
with open("args.json") as data_file:
data = json.load(data_file)
parser = setup_parser(data, title)
parameters = read_params(parser)
return parameters
if __name__ == "__main__":
params = get_parameters()
pprint.pprint(params)
type
parameter means. I discussed this in a recent SOargparse
answer.argparse
has aregistries
mechanism that implements a dictionary mapping between strings and functions for this parameter. \$\endgroup\$