动态类定义的序列化

4 投票
3 回答
2235 浏览
提问于 2025-04-18 10:45

我正在尝试把一个动态生成的类保存成一个工厂,用来创建另一种类。就像下面这样:

import sys, pickle

class BC(object):
    pass

C = type("NewClassName", (BC,), {})

pickle.dump(C, sys.stdout)

这导致了以下错误:

pickle.PicklingError: Can't pickle <class '__main__.NewClassName'>: it's not found as __main__.NewClassName

为了保存一个动态生成的类的对象,你可以定义一个 __reduce__ 方法,但有没有办法仅仅为类定义实现这个功能呢?

我不想直接使用BC,因为我只需要它作为新类的工厂。

3 个回答

2

你可以使用 dill,它可以处理动态类的定义,也就是说它能把这些类保存下来。这样你就不需要找其他方法来解决问题,可以直接做到你想做的事情。

Python 2.7.7 (default, Jun  2 2014, 01:33:50) 
[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
>>>>
>>> class BC(object):
...   pass
... 
>>> c = type("NewClassName", (BC,), {})
>>> _c = dill.dumps(c)    
>>> c2 = dill.loads(_c)
>>> c2
<class '__main__.NewClassName'>
>>> 

在这里获取 dillhttps://github.com/uqfoundation

2

试试下面的方法:

C = type("C", (BC,), {})

这个类必须是模块级别的变量,名字要和类型名一样。

不过,像这样动态生成的类是不能被序列化的(可以看看@otus的回答)。


我想到的最好解决办法是,把传给type的参数序列化,然后在反序列化的时候再重新创建这个类。

序列化:

import sys, pickle

class BC(object):
    pass

args = ("NewClassName", (BC,), {})
C = type(*args)
C._pickle_args = args

pickle.dump(C._pickle_args, sys.stdout)

反序列化:

type_args = pickle.loads("<pickled string">)
C = type(*args)
2

一个简单的解决办法是把类的名字当作变量名,这样 pickle 就能找到它:

import sys, pickle

class BC(object):
    pass

NewClassName = type("NewClassName", (BC,), {})

pickle.dump(NewClassName, sys.stdout)

不过,这样做可能并不能完全达到你的目的。当你加载已经保存的类时:

pickle.loads("""c__main__
NewClassName
p0
.""")

你又会遇到错误:

AttributeError: 'module' object has no attribute 'NewClassName'

除非你已经定义了这个类。


正如文档所说:

pickle可以透明地保存和恢复类的实例,但类的定义必须是可以导入的,并且要和对象存储时在同一个模块里。

所以你不能用它来生成新的类,只能确保你的对象指向正确的类。


还有一些变通的方法,比如像其他回答中展示的那样,使用 type 参数进行保存,但即便如此,你也无法保存那些动态类的对象,除非在保存和加载的过程中都把这个类暴露在全局命名空间中(也就是说 __main__.ClassName 必须指向这个类)。

因此,我建议你重新考虑整个动态类的方法。

撰写回答