Created
August 23, 2016 06:33
-
-
Save youheiakimoto/c69f37afd144255a0f0325d3735b90c0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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