我试图使用shelve python模块来保存我的会话输出并稍后重新加载它,但我发现如果我已经定义了函数,那么我在重新加载阶段会出错。我这样做的方式有问题吗?我的代码基于How can I save all the variables in the current python session?的答案。
这里有一些简单的代码可以重现错误:
def test_fn(): #simple test function
return
import shelve
my_shelf = shelve.open('test_shelve','n')
for key in globals().keys():
try:
my_shelf[key] = globals()[key]
except: #__builtins__, my_shelf, and imported modules cannot be shelved.
pass
my_shelf.close()
然后,如果我退出,我可以做
ls -lh test_shelve*
-rw-r--r-- 1 user group 22K Aug 24 11:16 test_shelve.bak
-rw-r--r-- 1 user group 476K Aug 24 11:16 test_shelve.dat
-rw-r--r-- 1 user group 22K Aug 24 11:16 test_shelve.dir
通常,在新的IPython会话中,我希望能够执行以下操作:
import shelve
my_shelf = shelve.open('test_shelve')
for key in my_shelf:
globals()[key]=my_shelf[key]
这会导致key' test_fn'出现错误。以下是一些演示错误的代码:
print my_shelf['test_fn']
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-deb481380237> in <module>()
----> 1 print my_shelf['test_fn']
/home/user/anaconda2/envs/main/lib/python2.7/shelve.pyc in __getitem__(self, key)
120 except KeyError:
121 f = StringIO(self.dict[key])
--> 122 value = Unpickler(f).load()
123 if self.writeback:
124 self.cache[key] = value
AttributeError: 'module' object has no attribute 'test_fn'
当然,一个解决方案是在保存阶段排除函数,但从我读过的内容应该可以使用这种方法恢复它们,所以我想知道我是否做错了。
答案 0 :(得分:3)
您无法使用shelve
(或pickle
(shelve
使用的实际协议)来存储可执行代码,否。
存储的内容是函数的引用(只是可以再次导入函数的位置)。代码不是数据,只有您引用函数的事实才是数据。 Pickle希望能够在加载存储的信息时再次加载相同的模块并运行。
这同样适用于班级;如果你挑选一个类的引用,或者pickle一个类的实例,那么只存储再次导入该类的信息(重新创建引用或实例)。
所有这一切都已完成,因为已经具有该函数或类的持久且可加载的表示形式:定义它们的模块。无需存储另一份副本。
What can be pickled and unpickled? section中明确记录了这一点:
请注意,函数(内置和用户定义)由“完全限定”的名称引用而非值引用。这意味着只有函数名称被腌制,以及定义函数的模块的名称。函数的代码或其任何函数属性都不会被pickle。因此,定义模块必须可以在unpickling环境中导入,并且模块必须包含命名对象,否则将引发异常。
为您的具体示例详细介绍:Python执行的主要脚本称为__main__
模块,您搁置了__main__.test_fn
函数。然后存储的内容只是一个标记,表示您引用了全局和导入位置,因此存储了接近GLOBAL
和__main__
加test_fn
的内容。再次加载搁置数据时,在看到GLOBAL
标记后,pickle
模块会尝试从test_fn
模块加载名称__main__
。由于您的第二个脚本再次作为__main__
加载,但没有test_fn
全局,因此加载引用失败。
答案 1 :(得分:0)
您不能在架子中存储函数,但是可以存储字符串,因此将函数存储为字符串,然后使用exec()或eval()从架子上使用它们 要将函数更改为字符串,请使用inpect.getsource() 但是,直接运行解释器时不能使用inspect.getsource
存储函数时
#function
sq=lambda x:x*x
def sum(n,n2):
return n+n2
#---------------
import shelve
from inspect import getsource
with shelve.open('path to shelf') as mem:
sf=getsource(sum)
mem['sum function']='def {}'+sf[sf.index('('):]
mem['lambda function']=getsource(sq)
如果没有运行文件,getsource()将不起作用
将函数放入程序时:
import shelve
with shelve.open('path to shelf') as mem:
previous_lambda=eval(mem['lambda function'])
exec(mem['sum function'].format('prev_f'))#prev_f being the name of the function
#now you can use your functions
print(previous_lambda(4))
print(prev_f(2,3))
如果您需要知道一个函数是lambda还是普通函数(因为它们的存储方式不同):
if func.__name__=="<lambda>":
print('func is lambda')
elif callable(func):
print('func is an ordinary function')
else:
print('func is not a function')