如何序列化类实例及其定义?

11 投票
2 回答
6525 浏览
提问于 2025-04-16 21:43

这是一个我觉得很常见的问题,但我还没找到解决办法。我想要的其实很简单,看起来在技术上也可行:我有一个简单的Python类,我想把它和它的实例一起存储到一个文件里。虽然使用Pickle可以存储数据,但它并不能存储类的定义。有人可能会说,类的定义已经在我的.py文件里了,但我不想要一个单独的.py文件;我的目标是有一个自包含的单一文件,这样我只需一行代码就能把它放回我的命名空间里。

所以我知道可以用两个文件和两行代码来实现,但我想要的是一个文件和一行代码。这样做的原因是我经常会遇到这种情况;我在处理一些大数据集,用Python进行操作,然后需要把我处理过的数据写回到某个已有的目录结构里。我不想在这些数据目录里乱放一些命名不当的Python类的占位符,以便把我的代码和数据关联起来,更不想麻烦地去跟踪和整理这些在脚本中临时定义的小类。

所以,这种方便之处并不是代码的可读性,而是代码和数据之间轻松且不易混淆的关联。对我来说,这似乎是一个值得追求的目标,尽管我明白在大多数情况下这样做并不合适。

所以问题是:有没有什么包或者代码片段可以做到这一点,因为我似乎找不到任何相关的东西。

2 个回答

0

Pickle 不能把 Python 代码保存成文件,所以我觉得用 Pickle 完全不可能做到这一点。

>>> from pickle import *
>>> def A(object):
...     def __init__(self):
...             self.potato = "Hello"
...             print "Starting"
...                                                                                                                                                                  
>>> A.__code__                                                                                                                                                       
<code object A at 0xb76bc0b0, file "<stdin>", line 1>                                                                                                                
>>> dumps(A.__code__)                                                                                                                                                
Traceback (most recent call last):                                                                                                                                   
  File "<stdin>", line 1, in <module>                                                                                                                                
  File "/usr/lib/python2.6/pickle.py", line 1366, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/lib/python2.6/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/lib/python2.6/pickle.py", line 306, in save
    rv = reduce(self.proto)
  File "/usr/lib/python2.6/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle code objects
12

如果你使用 dill,它可以让你把 __main__ 当作一个 Python 模块来使用(大部分情况下)。这样,你就可以把互动定义的类等进行序列化。dill 还可以默认把类的定义作为 pickle 的一部分进行传输。

>>> class MyTest(object):
...   def foo(self, x):
...     return self.x * x
...   x = 4
... 
>>> f = MyTest() 
>>> import dill
>>>
>>> with open('test.pkl', 'wb') as s:
...   dill.dump(f, s)
... 
>>> 

然后关闭解释器,把文件 test.pkl 通过 TCP 发送出去。在你的远程机器上,现在你可以获取到类的实例。

Python 2.7.9 (default, Dec 11 2014, 01:21:43) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('test.pkl', 'rb') as s:
...   f = dill.load(s)
... 
>>> f
<__main__.MyTest object at 0x1069348d0>
>>> f.x
4
>>> f.foo(2)
8
>>>             

但是,如何获取类的定义呢?这并不是你想要的结果。不过,接下来会有更好的方法。

>>> class MyTest2(object):
...   def bar(self, x):
...     return x*x + self.x
...   x = 1
... 
>>> import dill
>>> with open('test2.pkl', 'wb') as s:
...   dill.dump(MyTest2, s)
... 
>>>

在发送文件之后……你可以获取到类的定义。

Python 2.7.9 (default, Dec 11 2014, 01:21:43) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('test2.pkl', 'rb') as s:
...   MyTest2 = dill.load(s)
... 
>>> print dill.source.getsource(MyTest2)
class MyTest2(object):
  def bar(self, x):
    return x*x + self.x
  x = 1

>>> f = MyTest2()
>>> f.x
1
>>> f.bar(4)
17

因为你想要一个一行代码的解决方案,我可以做得更好。我没有告诉你可以同时发送类和实例,也许这正是你想要的。

>>> import dill
>>> class Foo(object): 
...   def bar(self, x):
...     return x+self.x
...   x = 1
... 
>>> b = Foo()
>>> b.x = 5
>>> 
>>> with open('blah.pkl', 'wb') as s:
...   dill.dump((Foo, b), s)
... 
>>> 

不过,这仍然不是一行代码,但它是有效的。

Python 2.7.9 (default, Dec 11 2014, 01:21:43) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('blah.pkl', 'rb') as s:
...   Foo, b = dill.load(s)
... 
>>> b.x  
5
>>> Foo.bar(b, 2)
7

dill 中,有 dill.source,它有一些方法可以检测函数和类的依赖关系,并把它们和 pickle 一起传输(大部分情况下)。

>>> def foo(x):
...   return x*x
... 
>>> class Bar(object):
...   def zap(self, x):
...     return foo(x) * self.x
...   x = 3
... 
>>> print dill.source.importable(Bar.zap, source=True)
def foo(x):
  return x*x
def zap(self, x):
  return foo(x) * self.x

所以这并不是“完美的”(或者说不是你预期的)……但它确实可以序列化动态构建的方法及其依赖关系。你只是得不到类的其他部分——但在这种情况下,类的其他部分并不需要。不过,这似乎仍然不是你想要的结果。

如果你想获取所有内容,你可以直接序列化整个会话。 而且只需要一行代码(算上 import 的话是两行)。

>>> import dill
>>> def foo(x):
...   return x*x
... 
>>> class Blah(object):
...   def bar(self, x):
...     self.x = (lambda x:foo(x)+self.x)(x)
...   x = 2
... 
>>> b = Blah()
>>> b.x
2
>>> b.bar(3)
>>> b.x
11
>>> # the one line
>>> dill.dump_session('foo.pkl')
>>> 

然后在远程机器上……

Python 2.7.9 (default, Dec 11 2014, 01:21:43) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> # the one line
>>> dill.load_session('foo.pkl')
>>> b.x
11
>>> b.bar(2)
>>> b.x
15
>>> foo(3)
9

最后,如果你希望传输过程对你来说是“透明的”(而不是使用文件),你可以使用 pathos.ppppft,它们可以将对象发送到第二个 Python 服务器(在远程机器上)或 Python 进程。它们在后台使用 dill,只是把代码传输过去。

>>> class More(object):
...   def squared(self, x):
...     return x*x
... 
>>> import pathos
>>> 
>>> p = pathos.pp.ParallelPythonPool(servers=('localhost,1234',))
>>> 
>>> m = More()
>>> p.map(m.squared, range(5))
[0, 1, 4, 9, 16]

这里的 servers 参数是可选的,这里只是连接到本地机器的 1234 端口……但如果你使用远程机器的名称和端口(或者同时使用),你就可以“轻松地”发送到远程机器。

你可以在这里获取 dillpathosppfthttps://github.com/uqfoundation

撰写回答