【问题标题】:How to pickle an instance of a class which is written inside a function?如何腌制写在函数内的类的实例?
【发布时间】:2012-08-04 08:09:01
【问题描述】:

假设我在一个模块中有这个 sn-p

def func(params):
   class MyClass(object):
       pass

如何腌制 MyClass 类的实例?

【问题讨论】:

  • 为什么不把你的类放到模块范围内?

标签: python pickle


【解决方案1】:

你不能,因为 picklable 对象的类定义必须驻留在导入模块的范围内。只需将您的课程放在模块范围内,您就可以开始了。

也就是说,在 Python 中,通过对机器内部(在本例中为 sys.modules)进行一些黑客攻击是无法实现的,但我不建议这样做。

【讨论】:

    【解决方案2】:

    MyClass 定义是func 函数的局部变量。您不能直接创建它的实例,但可以将其功能映射到新类,然后像原来的那样使用新类。这是一个例子:

    def func(params):
        class MyClass(object):
            some_param = 100
            def __init__(self, *args):
                print "args:", args
            def blabla(self):
                self.x = 123
                print self.some_param
            def getme(self):
                print self.x
    

    func.func_codefunc函数的代码,func.func_code.co_consts[2]包含MyClass定义的字节码:

    In : func.func_code.co_consts
    Out: 
    (None,
     'MyClass',
     <code object MyClass at 0x164dcb0, file "<ipython-input-35-f53bebe124be>", line 2>)
    

    所以我们需要MyClass 函数的字节码:

    In : eval(func.func_code.co_consts[2])
    Out: 
    {'blabla': <function blabla at 0x24689b0>,
     '__module__': '__main__',
     'getme': <function getme at 0x2468938>,
     'some_param': 100,
     '__init__': <function __init__ at 0x219e398>}
    

    最后我们用元类创建一个新类,将 MyClass 函数分配给新类:

    def map_functions(name, bases, dict):
        dict.update(eval(func.func_code.co_consts[2]))
        return type(name, bases, dict)
    
    class NewMyClass(object):
        __metaclass__ = map_functions
    
    n = NewMyClass(1, 2, 3, 4, 5)
    >> args: (1, 2, 3, 4, 5)
    
    n.blabla()
    >> 100
    
    n.getme()
    >> 123
    

    【讨论】:

    • 有趣...但我不清楚如何使用它来腌制函数中定义的类的实例——你也不能腌制代码对象。
    【解决方案3】:

    这有点难做,因为默认情况下,Pickle 对用户定义类中的对象所做的方式是创建该类的新实例 - 使用对象的 __class__.__name__ 属性在对象的原始模块中检索其类型。这意味着:pickling 和 unpickling 仅适用于(默认情况下)在它们定义的模块中具有明确定义的名称的类。

    当在函数内部定义一个类时,通常不会有一个模块级(即全局)变量保存函数内部创建的每个类的名称。

    pickle 和 npickle 的行为可以通过类上的 __getstate____setstate__ 方法进行自定义 - 检查 docs - 但即使是他们,为动态类做正确的事情也可能很棘手,但我设法做到了为另一个 S.O. 创建它的工作实现。问题--在这里检查我的答案: Pickle a dynamically parameterized sub-class

    【讨论】:

      【解决方案4】:

      您可以通过将类定义作为字符串包含在为实例腌制的数据中以及 exec()通过添加传递类的 __reduce__() 方法自行取消腌制时自行解决腌制要求,即类定义可导入可调用的定义。这是一个简单的例子来说明我的意思:

      from textwrap import dedent
      
      # Scaffolding
      definition = dedent('''
          class MyClass(object):
              def __init__(self, attribute):
                  self.attribute = attribute
              def __repr__(self):
                  return '{}({!r})'.format(self.__class__.__name__, self.attribute)
              def __reduce__(self):
                  return instantiator, (definition, self.attribute)
      ''')
      
      def instantiator(class_def, init_arg):
          """ Create class and return an instance of it. """
          exec(class_def)
          TheClass = locals()['MyClass']
          return TheClass(init_arg)
      
      # Sample usage
      import pickle
      from io import BytesIO
      
      stream = BytesIO()  # use a memory-backed file for testing
      
      obj = instantiator(definition, 'Foo')  # create instance of class from definition
      print('obj: {}'.format(obj))
      pickle.dump(obj, stream)
      
      stream.seek(0) # rewind
      
      obj2 = pickle.load(stream)
      print('obj2: {}'.format(obj2))
      

      输出:

      obj: MyClass('Foo')
      obj2: MyClass('Foo')
      

      显然,将类定义字符串包含在每个已腌制的类实例中效率低下,因此冗余可能使其不切实际,具体取决于所涉及的类实例的数量。

      【讨论】:

        猜你喜欢
        • 2021-03-30
        • 1970-01-01
        • 1970-01-01
        • 2016-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-22
        • 2019-04-04
        相关资源
        最近更新 更多