CONTEXT
I'm trying to pass some parameters to a function by command line. These parameters can be read either from directly from the command line, from a configfile or combining both to make easier some pipelines. E.g.:
for i in { 1 .. 100 }
do
python myprogram.py -i data_${i}.nii.gz -o results_${i} -cf configfile.ini
done
MY PROBLEM
I have two functions read_configfile()
and read_commandline
within myprogram.py
that work correctly and provide a dict args
in exactly the same output format. If you print that args
variables returned by both funcitons, you get something like:
{'data_path': '~/problem/data_X/', 'results_path': '~/problem/results_X/', 'base_path': '~/problem/', 'model_name': 'subject_X', 'data_name': 'data.nii.gz', 'image_file': None, 'bvals_file': None, 'bvecs_file': None, 'brainmask_file': None, 'header_init': None, 'init_files': None, 'cov_file': 'Analytic', 'njobs': 14, 'slice_axis': None, 'slice_n': None, 'seed': None, 'get_cov_samples': False, 'num_sticks': 3, 'snr': 30, 'type_noise': 'Rician', 'framework': 'Matlab', 'optimizer': 'fmincon', 'cons_fit': True, 'ard_optimization': False, 'likelihood_type': 'Default', 'reparametrization': False, 'no_run_mcmc': False, 'mcmc_analysis': 'Hybrid1', 'burnin': 1000, 'jumps': 1250, 'thinning': 25, 'adaptive': True, 'period': 50, 'duration': 'whole-run', 'no_ard_mcmc': False, 'type_ard': 'Gaussian', 'ard_fudge': 1, 'bingham_flag': False, 'acg': False}
What I want is to map the content of the dict args
to create new variables (i.e. having variableX
instead of args.variableX
or args['variableX']
). The key
would be the name of the variable I want to create and the value
its respective value. E.g.:
print(data_path)
~/problem/data_X/
print(period)
50
I would like to do it as cleanest and scalable as possible, so I don't want to assign manually one by one.
I have searched and tried several options to do this mapping, like the ones listed here and here (e.g. locals().update(args)
or for i in list(args.keys()): exec(f'{i} = {args[i]}'
). However, all of them work partially. These mappings leave most of the variables undefined (those ones that are actually redefined or reused later in the code), probably for some kind of conflict with the namespace or something that I don't understand.
Is there any solution or I have to re-define them one by one?
EDIT
def myprogram():
[...]
var1, var2, var3, ...., var50 = read_params(logger, sys.argv[1:])
[other stuff]
def read_params(logger, argv):
# Only CONFIGFILE
if isinstance(argv, str): # It is a string, i.e. a path (chain of characters) = Only configfile provided
logger.info('Reading parameters from configfile....')
args = read_configfile(argv)
# COMMAND LINE PARAMS
elif isinstance(argv, list): # Params introduced by terminal
logger.info('Reading parameters from command-line....')
args = read_params(argv)
# One (automatic) option that should work
locals().update(args)
print('Variables in locals():')
print(locals()) # It seems to be updated correctly to locals(). See print shown below
# Re-definition one by one. It works but I want to avoid this
# model_name = args['model_name']
# base_path = args['base_path']
# data_path = args['data_path']
# [...] # Repeat for the rest of variables
# If I use these updated variables from locals().update(args) anywhere in the code, it raises an Error.
if data_path is None:
if (os.path.isdir(os.path.join(base_path, 'data/'))):
data_path = os.path.join(base_path, 'data/')
sys.path += [data_path]
logger.info(f'Data path set in from {data_path}')
else:
logger.error(f'Error! Data path {data_path} does not exist.')
sys.exit()
[other similar stuff]
return data_path, ...
def read_configfile(configfile):
# localconfig is a wrapper on top of configparser (so fully compatible) that makes easier to import the variables in correct data types using the same configfile
# https://pypi.org/project/localconfig/
from localconfig import config
config.read(configfile)
args = dict(list(config.args)) # returns a dict
return args
[...]
The print(locals())
and the error:
Variables in locals():
{'logger': <Logger run_myBedpostX (DEBUG)>, 'argv': '~/code/config/configfile_template.py', 'args': {'data_path': '~/code/data/', 'results_path': '~/code/results/', 'base_path': '~/code/', 'image_file': 'data.nii.gz', 'bvals_file': 'bvals', 'bvecs_file': 'bvecs', 'brainmask_file': 'nodif_brain_mask.nii.gz', 'model_name': 'model_1', 'header_init': 'pvmfit', 'init_files': '~/code/data/brain/PVMFIT/', 'cov_file': '~/code/results/3fib/covSPD.npy', 'njobs': 1, 'slice_axis': None, 'slice_n': None, 'seed': 1234, 'get_cov_samples': False, 'full_report': False, 'num_sticks': 3, 'snr': 30, 'type_noise': 'Rician', 'framework': 'None #Matlab', 'optimizer': 'None #', 'cons_fit': True, 'ard_optimization': False, 'likelihood_type': 'Default', 'reparametrization': False, 'run_mcmc': True, 'mcmc_analysis': 'Hybrid1', 'burnin': 1000, 'jumps': 1250, 'thinning': 25, 'adaptive': True, 'period': 50, 'duration': 'whole-run', 'ard_mcmc': True, 'type_ard': 'Gaussian', 'ard_fudge': 1, 'acg': False, 'bingham_flag': False}, 'base_path': '~/code/', 'image_file': 'data.nii.gz', 'bvals_file': 'bvals', 'bvecs_file': 'bvecs', 'brainmask_file': 'nodif_brain_mask.nii.gz', 'model_name': 'model_1', 'header_init': 'pvmfit', 'init_files': '~/code/data/brain/PVMFIT/', 'cov_file': '~/code/results/3fib/covSPD.npy', 'njobs': 1, 'slice_axis': None, 'slice_n': None, 'seed': 1234, 'get_cov_samples': False, 'full_report': False, 'num_sticks': 3, 'snr': 30, 'type_noise': 'Rician', 'framework': 'None #Matlab', 'optimizer': 'None #', 'cons_fit': True, 'ard_optimization': False, 'likelihood_type': 'Default', 'reparametrization': False, 'run_mcmc': True, 'mcmc_analysis': 'Hybrid1', 'burnin': 1000, 'jumps': 1250, 'thinning': 25, 'adaptive': True, 'period': 50, 'duration': 'whole-run', 'ard_mcmc': True, 'type_ard': 'Gaussian', 'ard_fudge': 1, 'acg': False, 'bingham_flag': False}
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1758, in <module>
main()
File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1752, in main
globals = debugger.run(setup['file'], None, None, is_module)
File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1147, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "~/code/main.py", line 214, in <module>
main(args)
File "~/code/main.py", line 72, in main
get_cov_samples, type_ard, ard_fudge, bingham_flag, acg = read_params(logger, args)
File "~/code/utils/read_params.py", line 98, in read_params
if data_path is None:
UnboundLocalError: local variable 'data_path' referenced before assignment
As you can see, all the items from args
appear also in locals()
as separate variables (after the args
dict). Actually, debugging it in Pycharm, I can call them from the Debug Console. However, it raises that error if you run the script (i.e., called from the function), as shown above.