问题描述
我有一个类,其实例需要按照用户的指示格式化输出.有一个默认格式,可以被覆盖.我是这样实现的:
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 会创建这个限制?对于最终用户来说,这无疑是一种无端的痛苦.
推荐答案
对于类实例或函数(以及方法)的 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)
问题描述
I have a class whose instances need to format output as instructed by the user. There's a default format, which can be overridden. I implemented it like this:
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)
Is it ok? Let me know if there is a better way to design this.
Unfortunately, I need to pickle instances of this class. But only functions defined at the top level of the module can be pickled; lambda functions are unpicklable, so my format_functions instance attribute breaks the pickling.
I tried rewriting this to use a class method instead of lambda functions, but still no luck for the same reason:
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
Note that pickling here doesn't work even if I don't override the defaults; just the fact that I assigned self.format_functions = {float : self.default_float_format} breaks it.
What to do? I'd rather not pollute the namespace and break encapsulation by defining default_float_format at the module level.
Incidentally, why in the world does pickle create this restriction? It certainly feels like a gratuitous and substantial pain to the end user.
推荐答案
For pickling of class instances or functions (and therefore methods), Python's pickle depend that their name is available as global variables - the reference to the method in the dictionary points to a name that is not available in the global name space - which iis better said "module namespace" -
You could circunvent that by customizing the pickling of your class, by creating teh "__setstate__" and "__getstate__" methods - but I think you be better, since the formatting function does not depend on any information of the object or of the class itself (and even if some formatting function does, you could pass that as parameters), and define a function outside of the class scope.
This does work (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)