Skip to content

Instantly share code, notes, and snippets.

@youheiakimoto
Created August 23, 2016 06:33
Show Gist options
  • Save youheiakimoto/c69f37afd144255a0f0325d3735b90c0 to your computer and use it in GitHub Desktop.
Save youheiakimoto/c69f37afd144255a0f0325d3735b90c0 to your computer and use it in GitHub Desktop.
from __future__ import division # use // for integer division
from __future__ import absolute_import # use from . import
from __future__ import print_function # use print("...") instead of print "..."
from __future__ import unicode_literals # all the strings are unicode
__author__ = 'Youhei Akimoto'
import sys
import pprint
# For Consistency in Python 2 and 3
if sys.version_info[0] >= 3:
basestring = str
def _myeval(s, g, l):
if isinstance(s, basestring):
return eval(s, g, l)
else:
return s
class OptionBaseClass(object):
def __init__(self, **kwargs):
"""Constructor
In the constructor of a derived class, one can simply call
OptionBaseClass.__init__(self, **kwargs) ## OptionBaseClass should read the super class
self.setattr_from_local(locals())
"""
for key in kwargs:
setattr(self, key, kwargs[key])
self.is_parsed = False
def __str__(self):
return type(self).__name__ + str(self.__dict__)
def setattr_from_local(self, locals_):
for key in locals_:
if key != 'self' and key != 'kwargs':
setattr(self, key, locals_[key])
def disp(self):
"""Display all the options"""
print(type(self).__name__)
pp = pprint.PrettyPrinter()
pp.pprint(self.__dict__)
def to_latex(self):
res = ''
res += '\\begin{longtable}{ll}\n'
res += '\\caption{The parameters of ' + type(self).__name__ + '.}\\\\\n'
res += '\hline\n'
res += 'Key & Value \\\\\n'
res += '\hline\n'
for key in sorted(list(self.__dict__)):
if key != 'is_parsed':
res += '\\detokenize{' + key + '} & \\detokenize{' + getattr(self, key) + '}\\\\\n'
res += '\hline\n'
res += '\\end{longtable}\n'
return res
def parse(self, env=None):
"""Parse the member variables that are string expressions
Parameters
----------
env : dict, default None
The dictionary of a namespace. A typical choice is globals().
The values in the instance are evaluated on the environment `env`
updated with globals() called inside the method.
Returns
-------
parsed : an instance of the same class as `self`
all the member variables are parsed.
Example
-------
Case 1.
>>> opts = OptionBaseClass(N='10', M='N', L='N')
>>> parsed = opts.parse()
>>> parsed.disp()
OptionBaseClass
{'L': 10, 'M': 10, 'N': 10, 'is_parsed': True}
Case 2.
repr(N) is evaluated before it is passed to the function
>>> N = 100
>>> opts = OptionBaseClass(N='10', L=repr(N))
>>> parsed = opts.parse()
>>> parsed.disp()
OptionBaseClass
{'L': 100, 'N': 10, 'is_parsed': True}
Case 4.
>>> N = 100
>>> opts = OptionBaseClass(M='N', L=repr(N))
>>> parsed = opts.parse(globals())
>>> parsed.disp()
OptionBaseClass
{'L': 100, 'M': 100, 'is_parsed': True}
In the following cases, one may obtain unexpected outputs.
Case A.
opts = OptionBaseClass(N='10', M='N')
The output of `parse` method is undefined.
If `M` is evaluated before `N`, the result will be M = '10' (string).
To prevent this unexpected behavior, consider to cast the variable like
opts = OptionBaseClass(N='10', M='int(N)')
Case B.
opts = OptionBaseClass(N='M', M='N')
Obviously, the variables can not be evaluated if there is a pair of variables that depend on each other.
Case C.
N = 100
mypow = pow
opts = OptionBaseClass(M='mypow(N, L)', L='2')
parsed = opts.parse(globals())
To refer to variables and objects defined in the caller, call `parse` with env=globals()
Case D.
Call `parse` with env=globals() if some modules are required to evaluate some variables
import numpy as np
opts = oi.OptionBaseClass(N='np.arange(5)', M='np.sqrt(N)')
parsed = opts.parse(globals())
"""
if self.is_parsed:
print("Already parsed. Returns itself.")
return self
parsed_dict = dict()
failure_count = 0
key_list = list(self.__dict__)
key_list.remove('is_parsed')
if env is None:
env = dict(globals())
else:
env = dict(env)
env.update(globals())
env.update(self.__dict__)
while key_list:
if failure_count >= len(key_list):
print("Some options couldn't be parsed: " + str(key_list))
print("Their values are as follows.")
for key in key_list:
print(key + ': ' + getattr(self, key))
print("\n" +
"To find out the reason, see the document of `OptionBaseClass.parse`, and\n" +
"A. type-cast the option variables (see Case A);\n" +
"B. remove a cycle of variables (see Case B);\n" +
"C. call `parse` with env=globals() to use global variables (see Case C);\n" +
"D. import modules and functions such as `numpy`, `exp`, `sqrt` (see Case D).\n")
print("Here are the parsed values:")
pp = pprint.PrettyPrinter()
pp.pprint(parsed_dict)
raise ValueError()
key = key_list.pop()
try:
val = _myeval(getattr(self, key), env, parsed_dict)
parsed_dict[key] = val
failure_count = 0
except:
key_list.insert(0, key)
failure_count += 1
parsed = self.create()
key_list = list(self.__dict__)
key_list.remove('is_parsed')
for key in key_list:
setattr(parsed, key, parsed_dict[key])
parsed.is_parsed = True
return parsed
@classmethod
def create(cls, **kwargs):
return cls(**kwargs)
if __name__ == "__main__":
import doctest
doctest.testmod()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment