如何重写__getstate__适配jsonpickle且不影响deepcopy?
这个问题我碰到过好几次——当你想用__getstate__来给JSON API输出过滤掉私有属性,结果发现deepcopy也跟着遭殃,因为它底层依赖pickle机制,会触发同一个__getstate__方法。别担心,有好几种办法能区分这两种场景,给你捋一捋:
__deepcopy__方法(最优雅) 直接绕过__getstate__实现deepcopy逻辑,让deepcopy走单独的处理流程,不触发你的属性过滤代码。你可以在类里添加这个方法:
import copy class LevelMixin(object): def __init__(self): self.public_attr = "foo" self._private_attr = "bar" self.__super_private = "baz" def __getstate__(self): # JSON序列化场景:过滤所有私有属性 return {k: v for k, v in self.__dict__.items() if not k.startswith('_')} def __deepcopy__(self, memo): # 自定义deepcopy逻辑,保留所有属性 new_instance = self.__class__() # 完整复制实例的所有属性(包括私有、超私有) new_instance.__dict__ = copy.deepcopy(self.__dict__, memo) return new_instance
这样调用copy.deepcopy(obj)时会直接走__deepcopy__,完全不碰__getstate__;而JSON序列化(假设你的序列化逻辑依赖__getstate__转字典)依然会用过滤后的属性。
如果不想写完整的__deepcopy__,可以给实例加个临时标记,让__getstate__根据标记判断当前是哪种调用场景:
import copy class LevelMixin(object): def __init__(self): self.public_attr = "foo" self._private_attr = "bar" self.__super_private = "baz" def __getstate__(self): # 检查是否处于deepcopy流程中 if getattr(self, '_is_deepcopying', False): # 保留所有属性,同时移除临时标记避免影响后续操作 state = self.__dict__.copy() del state['_is_deepcopying'] return state # JSON序列化场景:过滤私有属性 return {k: v for k, v in self.__dict__.items() if not k.startswith('_')} def __deepcopy__(self, memo): # 添临时标记告知__getstate__当前是deepcopy self._is_deepcopying = True # 调用默认deepcopy逻辑(此时__getstate__会返回所有属性) new_instance = copy.deepcopy(self, memo) # 移除原实例的临时标记 del self._is_deepcopying return new_instance
这个方法逻辑清晰,不会破坏原有流程,只是通过一个临时属性做场景判断。
如果不想改太多代码,可以在__getstate__里检查调用栈,判断当前是否是deepcopy在调用它:
import copy import inspect class LevelMixin(object): def __init__(self): self.public_attr = "foo" self._private_attr = "bar" self.__super_private = "baz" def __getstate__(self): # 遍历调用栈,检查是否来自copy模块的deepcopy函数 stack = inspect.stack() is_deepcopy = any('copy.py' in frame.filename and 'deepcopy' in frame.function for frame in stack) if is_deepcopy: return self.__dict__.copy() # JSON序列化场景:过滤私有属性 return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
这个方法不需要额外写方法,但依赖Python的调用栈结构,属于hack性质,适合快速临时解决问题,不推荐长期使用。
其实最稳妥的方式是别用__getstate__处理JSON序列化——毕竟__getstate__本来就是pickle协议的一部分。你可以专门写一个to_json_dict()方法负责JSON输出,__getstate__只处理pickle(包括deepcopy):
import copy class LevelMixin(object): def __init__(self): self.public_attr = "foo" self._private_attr = "bar" self.__super_private = "baz" def __getstate__(self): # 给pickle/deepcopy用:返回所有属性 return self.__dict__.copy() def to_json_dict(self): # 专门给JSON序列化用:过滤私有属性 return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
这样两个场景完全分离,从根源上避免冲突,是最推荐的长期方案。
内容的提问来源于stack exchange,提问作者Bud

