使具有不可选取字段的对象可选取的正确方法是什么?
我相信这个问题的答案属于您链接的问题 -Python - 如何使这个不可腌制的对象可腌制?。我添加了一个新答案这个问题解释了如何以正确的方式使不可腌制的对象可腌制,而不使用__reduce__
.
那么,检查没有莳萝或官方泡菜的可采摘物的官方(可能是优化的)方法是什么?
可picklable 的对象在文档中定义如下:
-
None
, True
, and False
- 整数、浮点数、复数
- 字符串、字节、字节数组
- 仅包含可腌制对象的元组、列表、集合和字典
- 在模块顶层定义的函数(使用
def
, not lambda
)
- 在模块顶层定义的内置函数
- 在模块顶层定义的类
- 此类类的实例dict或调用的结果getstate() 是可腌制的(有关详细信息,请参阅腌制类实例部分)。
棘手的部分是(1)知道函数/类是如何定义的(您可能可以使用inspect
模块)和(2)通过对象递归,根据上述规则进行检查。
对此有很多注意事项,例如泡菜协议版本,该对象是扩展类型(例如在 numpy 等 C 扩展中定义的)还是“用户定义”类的实例。的用法__slots__
还可以影响对象是否可腌制(因为__slots__
意味着没有__dict__
),但可以用__getstate__
。有些物体也可能被注册具有用于酸洗的自定义功能。因此,您需要知道这种情况是否也发生过。
从技术上讲,您可以在 Python 中实现一个函数来检查所有这些,但相比之下,它会相当慢。最简单的(也可能是性能最好的,如pickle
is 用C实现)方法是简单地尝试腌制您想要检查的对象。
我用 PyCharm 对各种东西进行了测试...它不会用这种方法停止。关键是您必须预见到几乎所有类型的异常(请参阅脚注3在文档中)。警告是可选的,它们主要是对该问题的上下文的解释。
def is_picklable(obj: Any) -> bool:
try:
pickle.dumps(obj)
return True
except (pickle.PicklingError, pickle.PickleError, AttributeError, ImportError):
# https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled
return False
except RecursionError:
warnings.warn(
f"Could not determine if object of type {type(obj)!r} is picklable"
"due to a RecursionError that was supressed. "
"Setting a higher recursion limit MAY allow this object to be pickled"
)
return False
except Exception as e:
# https://docs.python.org/3/library/pickle.html#id9
warnings.warn(
f"An error occurred while attempting to pickle"
f"object of type {type(obj)!r}. Assuming it's unpicklable. The exception was {e}"
)
return False
使用我上面链接的其他答案中的示例,您可以通过实现来使您的对象可腌制__getstate__
and __setstate__
(或子类化并添加它们,或制作包装类)调整您的make_args_pickable
...
class Unpicklable:
"""
A simple marker class so we can distinguish when a deserialized object
is a string because it was originally unpicklable
(and not simply a string to begin with)
"""
def __init__(self, obj_str: str):
self.obj_str = obj_str
def __str__(self):
return self.obj_str
def __repr__(self):
return f'Unpicklable(obj_str={self.obj_str!r})'
class PicklableNamespace(Namespace):
def __getstate__(self):
"""For serialization"""
# always make a copy so you don't accidentally modify state
state = self.__dict__.copy()
# Any unpicklables will be converted to a ``Unpicklable`` object
# with its str format stored in the object
for key, val in state.items():
if not is_picklable(val):
state[key] = Unpicklable(str(val))
return state
def __setstate__(self, state):
self.__dict__.update(state) # or leave unimplemented
在实际操作中,我将腌制一个属性包含文件句柄(通常不可腌制)的名称空间,然后加载腌制数据。
# Normally file handles are not picklable
p = PicklableNamespace(f=open('test.txt'))
data = pickle.dumps(p)
del p
loaded_p = pickle.loads(data)
# PicklableNamespace(f=Unpicklable(obj_str="<_io.TextIOWrapper name='test.txt' mode='r' encoding='cp1252'>"))