【问题标题】:pickling class method酸洗类方法
【发布时间】:2012-02-27 18:42:34
【问题描述】:

我有一个类,其实例需要按照用户的指示格式化输出。有一个默认格式,可以被覆盖。我是这样实现的:

class A:
  def __init__(self, params):
    # ...
    # by default printing all float values as percentages with 2 decimals
    self.format_functions = {float: lambda x : '{:.2%}'.format(x)}
  def __str__(self):
    # uses self.format_functions to format output
    # ...

a = A(params)
print(a) # uses default output formatting

# overriding default output formatting
# float printed as percentages 3 decimal digits; bool printed as Y / N
a.format_functions = {float : lambda x: '{:.3%}'.format(x),
                      bool : lambda x: 'Y' if x else 'N'}
print(a)

没事吧?如果有更好的设计方法,请告诉我。

不幸的是,我需要腌制这个类的实例。但是只有在模块顶层定义的函数才能被pickle; lambda 函数是不可腌制的,所以我的 format_functions 实例属性会破坏腌制。

我尝试重写它以使用类方法而不是 lambda 函数,但出于同样的原因仍然没有运气:

class A:
  @classmethod
  def default_float_format(cls, x):
    return '{:.2%}'.format(x)
  def __init__(self, params):
    # ...
    # by default printing all float values as percentages with 2 decimals
    self.format_functions = {float: self.default_float_format}
  def __str__(self):
    # uses self.format_functions to format output
    # ...

a = A(params)
pickle.dump(a) # Can't pickle <class 'method'>: attribute lookup builtins.method failed

请注意,即使我不覆盖默认值,这里的酸洗也不起作用;只是我分配了self.format_functions = {float : self.default_float_format} 的事实打破了它。

怎么办?我宁愿不要通过在模块级别定义default_float_format 来污染命名空间和破坏封装。

顺便说一句,为什么pickle 会创建这个限制?对于最终用户来说,这无疑是一种无端的痛苦。

【问题讨论】:

    标签: python methods python-3.x pickle


    【解决方案1】:

    对于类实例或函数(以及方法)的酸洗,Python 的酸洗依赖于它们的名称可用作全局变量 - 字典中对方法的引用指向全局名称空间中不可用的名称 -哪个 iis 更好地说是“模块命名空间” -

    您可以通过自定义类的酸洗,通过创建“__setstate__”和“__getstate__”方法来规避这一点 - 但我认为你会更好,因为格式化函数不依赖于对象或对象的任何信息类本身(即使有一些格式化函数,您也可以将其作为参数传递),并在类范围之外定义一个函数。

    这确实有效(Python 3.2):

    def default_float_format( x):
        return '{:.2%}'.format(x)
    
    class A:
    
      def __init__(self, params):
        # ...
        # by default printing all float values as percentages with 2 decimals
        self.format_functions = {float: default_float_format}
      def __str__(self):
        # uses self.format_functions to format output
        pass
    
    a = A(1)
    pickle.dumps(a)
    

    【讨论】:

    • 我不介意,但是如果另一个类需要不同的默认值怎么办?然后我将在模块级别拥有default_float_format_for_class_Adefault_float_format_for_class_B 等。它们作为类方法(或静态方法)不是更好吗?此外,如果pickle 可以与类方法一起使用,为什么不在标准pickle 模块中完成呢?这有什么缺点吗?
    【解决方案2】:

    如果您使用dill 模块,您的两种方法中的任何一种都将按原样“工作”。 dill 可以腌制 lambda 以及类的实例和类方法。

    不需要污染命名空间和破坏封装,正如你所说的你不想这样做......但另一个答案是

    dill 基本上需要十年左右的时间才能找到正确的 copy_reg 函数,该函数注册了如何在标准 python 中序列化大多数对象。没有什么特别或棘手的,只是需要时间。那么为什么pickle 不为我们做这件事呢?为什么pickle有这个限制?

    好吧,如果您查看pickle 文档,答案就在那里: https://docs.python.org/2/library/pickle.html#what-can-be-pickled-and-unpickled

    基本上:函数和类是通过引用腌制的。

    这意味着pickle 不适用于__main__ 中定义的对象,并且它也不适用于许多动态修改 的对象。 dill__main__ 注册为模块,因此它具有有效的命名空间。 dill 还为您提供了不通过引用进行腌制的选项,因此您可以序列化 动态修改 对象……以及类实例、类方法(绑定和未绑定)等等。

    【讨论】:

    • 观察:你错过了 dill 的链接
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-15
    • 1970-01-01
    • 1970-01-01
    • 2012-07-24
    • 2014-08-13
    • 1970-01-01
    • 2021-10-25
    相关资源
    最近更新 更多