0

我正在尝试编写一个类似于 MATLAB 中的保存/加载命令(能够将局部变量保存到磁盘或将它们加载到当前上下文或 MATLAB 术语中的工作空间)。

我写了下面的代码,但它似乎不起作用,因为外部范围内的变量没有被替换,可能是因为内存副本发生在某处。

这是代码:

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))

这是失败的测试:

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}])

当我在里面断点时,load_locals我可以看到它改变了f_locals字典,但是当函数返回时它们不会改变。

4

1 回答 1

1

不,您不能即时更新局部变量。原因是本地符号表被保存为 C 数组以进行优化,locals()并且frame.f_locals最终将副本返回到该本地符号表。官方的回应是修改 locals() 有未定义的行为。 这个线程有点谈论它。

它最终变得更加奇怪,因为每次调用locals()或返回相同的字典,它会在不同的时间重新同步。frame.f_locals这里只是调用frame.f_locals重置本地

def test_locals():
    frame = inspect.stack()[1][0]
    caller_locals = frame.f_locals
    caller_locals['an_int'] = 5
    print(caller_locals)
    _ = frame.f_locals
    print(caller_locals)


def call_test_locals():
    an_int = 3
    test_locals()


call_test_locals()

输出:

{'an_int': 5}
{'an_int': 3}

行为将取决于 Python 实现和可能的其他边缘情况,但有几个示例: (1) 变量已定义且未更新;(2) 变量未定义并被更新;(3) 变量被定义并随后被删除并且不被更新。

def test_locals():
    frame = inspect.stack()[1][0]
    caller_locals = frame.f_locals
    caller_locals['an_int'] = 5


def call_test_locals1():
    an_int = 3
    print('calling', locals())
    test_locals()
    print('done', locals())


def call_test_locals2():
    print('calling', locals())
    test_locals()
    print('done', locals())


def call_test_locals3():
    an_int = 3
    del an_int
    print('calling', locals())
    test_locals()
    print('done', locals())


print('\n1:')
call_test_locals1()
print('\n2:')
call_test_locals2()
print('\n3:')
call_test_locals3()

输出:

1:
calling {'an_int': 3}
done {'an_int': 3}

2:
calling {}
done {'an_int': 5}

3:
calling {}
done {}

如果您正在运行 Python 2,则可以使用exec将字符串执行到本地命名空间中,但它在 Python 3 中不起作用,并且通常可能是个坏主意。

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_string(filename, keys=None):
    my_shelf = shelve.open(filename)
    if keys is None:
        keys = list(my_shelf.keys())
    return ';'.join('{}={!r}'.format(key, my_shelf[key]) for key in keys)

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
        exec load_locals_string('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}])

在 Python 2 中,exec用于PyFrame_LocalsToFast将变量复制回本地范围,但在 Python 3 中不能,因为exec它是一个函数。 Martijn Pieters有一篇关于它的好帖子。

于 2017-01-08T20:47:56.850 回答