I am trying to write a save/load command like the one in MATLAB (ability to save local variables to disk or load them into current context, or work space in MATLAB's terminology).
I wrote the following code, but it doesn't seem to work, as the variables in the outer scope are not replaced, probability because of a memory copy which takes place somewhere.
Here is the code:
import shelve
import logging
import inspect
logger = logging.getLogger()
def save_locals(filename, keys=None):
my_shelf = shelve.open(filename, 'n') # 'n' for new
caller_locals = inspect.stack()[1][0].f_locals
if keys is None:
keys = caller_locals.keys()
for key in keys:
try:
my_shelf[key] = caller_locals[key]
except TypeError:
#
# __builtins__, my_shelf, and imported modules can not be shelved.
#
print('ERROR shelving: {0}'.format(key))
my_shelf.close()
def load_locals(filename, keys=None):
my_shelf = shelve.open(filename)
caller_locals = inspect.stack()[1][0].f_locals
if keys is None:
keys = list(my_shelf.keys())
for key in keys:
try:
caller_locals[key] = my_shelf[key]
except ValueError:
print('cannot get variable %s'.format(key))
Here is the test which fails:
from unittest import TestCase
from .io import save_locals, load_locals
class TestIo(TestCase):
def test_save_load(self):
sanity = 'sanity'
an_int = 3
a_float = 3.14
a_list = [1, 2, 3]
a_dict = [{'a': 5, 'b': 3}]
save_locals('temp')
an_int = None
a_float = None
a_list = None
a_dict = None
load_locals('temp')
self.assertIn('an_int', locals())
self.assertIn('a_float', locals())
self.assertIn('a_list', locals())
self.assertIn('a_dict', locals())
self.assertEqual(an_int, 3)
self.assertEqual(a_float, 3.14)
self.assertEqual(a_list, [1, 2, 3])
self.assertEqual(a_dict, [{'a': 5, 'b': 3}])
When I break-point inside load_locals
I can see it changes the f_locals
dictionary but when the function returns they do not change.
No, you can't update local variables on the fly. The reason is because the local symbol table is saved as a C array for optimization and both
locals()
andframe.f_locals
end up returning a copy to that local symbol table. The official response is that modifying locals() has undefined behavior. This thread talks a bit about it.It ends up being extra weird because calling
locals()
orframe.f_locals
returns the same dictionary each time, which gets re-synced at different times. Here just callingframe.f_locals
resets the localoutput:
The behavior is going to depend on the Python implementation and probably other edge cases, but a few examples where (1) the variable is defined and is not updated; (2) the variable is not defined and is updated; (3) the variable is defined and subsequently deleted and is not updated.
output:
If you're running Python 2, you could use
exec
to execute a string into the local namespace, but it won't work in Python 3 and is in general probably a bad idea.and
In Python 2,
exec
usesPyFrame_LocalsToFast
to copy the variables back to the local scope, but can't in Python 3 becauseexec
is a function. Martijn Pieters has a good post about it.