序列化类定义

13 投票
3 回答
7708 浏览
提问于 2025-04-15 21:31

有没有办法把一个类的定义保存起来?

我想做的是把这个定义(可能是动态创建的)保存下来,然后通过TCP连接发送过去,这样在另一端就可以创建一个实例。

我知道这个类可能依赖一些东西,比如模块和全局变量。我希望在保存的过程中把这些也打包进去,不过我不太在意自动检测这些依赖,因为让用户自己指定这些依赖也是可以的。

3 个回答

4

文档对什么可以被“腌制”(pickled)和不能被腌制的内容解释得相当清楚,也说明了原因。

http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled

简单来说,如果你在反腌制(unpickled)时能通过名字导入这个类或模块,那它应该是可以工作的,前提是你在腌制和反腌制之间没有改变类的定义。在下面的类定义中,只有类名“Test”和方法名“mymethod”会被腌制。如果你腌制了这个类的定义,然后把定义改了,比如把属性(attr)改成了不同的值,或者mymethod做了完全不同的事情,那么反腌制时会使用新的定义。

class Test(object):
    attr = 5

    def mymethod(self, arg):
        return arg
6

可惜,不能直接这样做。你可以把 class 语句变成字符串形式或者字节码形式,然后在接收方用 exec 来“重新激活”它。

8

如果你使用 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

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

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

撰写回答