Reputation: 25
I wanted to make a generic module that can convert objects into a JSON format, save that to a text file, then load them back in.
below is an explanation of the pseudo code
When saving, it takes an object(copies it to not damage the original, created by a custom class method) and iterates over all the attributes.
If the attribute is a value in a provided list of keys to skip, it deletes the attribute
If the attribute is iterable, it iterates over it to see if it has other iterables or class objects
If a value from an iterable or attribute is a class object, it runs this save function on that object
finally it returns a dictionary with two keys, "id": "the name of the class object type" and "data": "a dictionary of all the attributes as keys to the corresponding data"
this is converted to string and saved to a file
When loading ## where the issue comes from ##
it takes the string and converts back to a dictionary. it takes the "id" and attempts to create an instance of that class by using globals()[dict["id"]]()
That is where the issues come from. Because I am importing this module into my main code elsewhere, when globals is called, it is getting the globals of the module, not the main program and as far as I can tell there is no way of sharing the globals with this imported module. Is there another way I cn do this? or some fix that doesnt require me to relocate all the code into my main code? I have tested it within the module itself and it works 100% but not when importing.
tl:dr
made a module that can save objects into JSON and can convert them back but requires access to the globals of the program that it is being ran in.
here is the code with functioning example at the bottom. It works when not imported and used that way.
import json
def testiter(object): ## tests if an object is iterable
if isinstance(object, list) or isinstance(object, tuple):
return True
else:
return False
def testclass(object): ## tests if an object is a class object
try:
object.__dict__
return True
except:
return False
class object_clone(): ## creates a clone of a object
def __init__(self, object, skip):
self.__class__.__name__ = object.__class__.__name__
for attr, value in dict(vars(object)).items():
if not(attr in skip):
if testiter(value):
setattr(self, attr, self.iterable_search(value, skip))
elif testclass(value):
setattr(self, attr, object_clone(value, skip))
else:
setattr(self, attr, value)
def iterable_search(self, lst, skip):
ret = []
for value in lst:
if testiter(value):
ret.append(self.iterable_search(value, skip))
elif testclass(value):
ret.append(object_clone(value, skip))
else:
ret.append(value)
return ret
class object_save(): ## saves an object
def save(self, object, skip, path):
self.skip=skip ## for skipping data that is unsaveable
open(path, 'w').write(json.dumps(self.semisave(object_clone(object, skip)))) ## clones the given object, writes it in a dict format, saves as json string, then writes to path
def semisave(self, object):
for attr, value in dict(vars(object)).items(): ## iterate over class object attributes
if attr in self.skip:
delattr(object, attr) ##remove unsavable
elif testiter(value):
setattr(object, attr, self.iterable_search(value)) ## Searches through iterables
elif testclass(value): ## runs this function on any class objects found
setattr(object, attr, self.semisave(value))
return {
'class_instance':object.__class__.__name__,
'data':json.loads(json.dumps(vars(object)))} ## json dict of object
def iterable_search(self, lst):
ret=[]
for value in lst:
if testiter(value):
ret.append(self.iterable_search(value)) ## Searches through nested iterables
elif testclass(value):
ret.append(self.semisave(value)) ## converts found class objects to dict
else:
ret.append(value) ## skips other data
return ret
class object_load():
def load(self, path):
json.loads(open(path, 'r').read()) ## loads saved string and converts to dict
return self.semiload(json.loads(open(path, 'r').read()))
def semiload(self, json):
try:
[print(key, name) for key, name in globals().items()] ##issue here##
ret = globals()[json['class_instance']]()
except:
return
for attr, value in json['data'].items():
if isinstance(value, dict) and 'class_instance' in value:
setattr(ret, attr, self.semiload(value))
elif testiter(value):
setattr(ret, attr, self.iterable_scan(value))
else:
setattr(ret, attr, value)
return ret
def iterable_scan(self, lst):
ret=[]
for value in lst:
if isinstance(value, dict) and 'class_instance' in value:
ret.append(self.semiload(value))
elif testiter(value):
ret.append(self.iterable_scan(value))
else:
ret.append(value)
return ret
##example
class foo():
def __init__(self):
self.a='test'
self.b=5
self.c=['test', 5, [bar()]]
def print_attr(self):
[print([attr, value]) for attr, value in vars(self).items()]
class bar():
def __init__(self):
self.c=5
self.e=[[6], 2]
object_save().save(foo(), ['b'], 'a')
object_load().load('a').print_attr()
Upvotes: 2
Views: 93
Reputation: 123413
The sample code below illustrates in a simple terms the principles of how to do what you want. I am providing it despite the fact that I agree with @Tim Roberts that doing stuff like this often ill-advised — because I also believe that there are exceptions to every "rule" and we're all consenting adults.
In fact, the reason I figured-out how to do this long ago was in precisely a situation that I felt doing so was justified.
That said, here's "Enough Rope to Shoot Yourself in the Foot" as the author of one of my favorite books on C/C++ programming used in the title of one of his books.
sample_vars.json
:
{
"a": "foobar",
"b": 42
}
main.py
:
from make_vars import create_vars
create_vars('sample_vars.json')
print(f'{a=}, {b=}') # -> a='foobar', b=42
make_vars.py
:
import json
import sys
def create_vars(json_filename):
namespace = sys._getframe(1).f_globals # Caller's globals
with open(json_filename) as json_file:
saved_vars = json.load(json_file)
namespace.update(saved_vars) # Create/update caller's globals.
Upvotes: 2