酸洗类方法

时间:2012-02-27 18:42:34

标签: python methods python-3.x pickle

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

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)

可以吗?让我知道是否有更好的方法来设计它。

不幸的是,我需要挑选这个类的实例。但是只能在模块顶层定义的函数进行酸洗; 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会创造这种限制?对于最终用户来说,这无疑是一种无端和实质性的痛苦。

2 个答案:

答案 0 :(得分:5)

对于类实例或函数(以及方法)的pickle,Python的pickle取决于它们的名称可用作全局变量 - 字典中对方法的引用指向全局名称空间中不可用的名称 - 我更好地说“模块命名空间” -

你可以通过创建你的类的酸洗,通过创建“__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)

答案 1 :(得分:2)

如果您使用dill模块,那么您的两种方法中的任何一种都只会工作&#34; 原样dill可以挑选lambda以及类和类方法的实例。

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

dill基本上十年左右值得找到正确的copy_reg函数来注册如何序列化标准python中的大多数对象。没有什么特别或棘手的,只需要时间。那么为什么pickle为我们这样做呢?为什么pickle有此限制?

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

  

基本上:函数和类通过引用进行pickle。

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