我有一个类,其实例需要按照用户的指示格式化输出。有一种默认格式,可以覆盖。我这样实现了它:
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
会创造这种限制?对于最终用户来说,这无疑是一种无端和实质性的痛苦。
答案 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的选项,因此您可以序列化动态修改的对象...以及类实例,类方法(绑定和未绑定)等等。