如何为Protobuf消息实现__hash__方法以存入集合?
如何为Protobuf消息实现__hash__方法以存入集合?
嘿,我太懂你这种碰壁的感觉了——Protobuf生成的Python消息类确实不能直接通过继承来重写__hash__,它们的内部是靠元编程动态构建的,直接继承会打乱DESCRIPTOR这类核心属性的加载逻辑,这就是你碰到KeyError: 'DESCRIPTOR'的根源。
给你两个实用的解决方案,都是我实际项目里用过的:
方案一:用包装类(组合模式)
这种方式最稳妥,不会改动原Protobuf类的结构,通过包装器来实现哈希和相等判断:
import message_pb2 class HashableMessageWrapper: def __init__(self, proto_msg): # 把原始Protobuf消息实例作为属性存起来 self.proto_msg = proto_msg def __eq__(self, other): # 先判断类型是否一致 if not isinstance(other, HashableMessageWrapper): return False # 用序列化后的字节串判断两个消息是否完全相等 return self.proto_msg.SerializeToString() == other.proto_msg.SerializeToString() def __hash__(self): # 基于序列化字节串生成哈希值,保证相等的消息哈希值一致 return hash(self.proto_msg.SerializeToString())
使用的时候,把你的消息包一层再放进集合:
# 创建两个内容相同的Protobuf消息 msg1 = message_pb2.Message() msg1.user_id = 123 msg1.content = "hello" msg2 = message_pb2.Message() msg2.user_id = 123 msg2.content = "hello" # 包装后加入集合 unique_messages = set() unique_messages.add(HashableMessageWrapper(msg1)) unique_messages.add(HashableMessageWrapper(msg2)) print(len(unique_messages)) # 输出1,因为两个消息内容相同,被视为同一个元素
如果只关心特定字段的唯一性(不需要全字段比较),也可以自定义哈希和相等判断的逻辑,比如只基于user_id:
def __eq__(self, other): if not isinstance(other, HashableMessageWrapper): return False return self.proto_msg.user_id == other.proto_msg.user_id def __hash__(self): return hash(self.proto_msg.user_id)
方案二:猴子补丁(直接修改原类)
如果你不想写包装类,也可以直接给生成的Protobuf消息类动态添加__hash__和__eq__方法,注意这种方式会全局生效,适合所有地方都需要哈希该消息的场景:
import message_pb2 # 自定义哈希函数:基于消息序列化后的字节串 def _message_hash(self): return hash(self.SerializeToString()) # 自定义相等判断函数 def _message_eq(self, other): if not isinstance(other, message_pb2.Message): return False return self.SerializeToString() == other.SerializeToString() # 把方法绑定到原Protobuf类上 message_pb2.Message.__hash__ = _message_hash message_pb2.Message.__eq__ = _message_eq
之后就可以直接把Protobuf消息放进集合了:
msg1 = message_pb2.Message() msg1.user_id = 123 msg2 = message_pb2.Message() msg2.user_id = 123 unique_set = set() unique_set.add(msg1) unique_set.add(msg2) print(len(unique_set)) # 输出1
为什么不能直接继承?
简单说,Protobuf生成的Python类是通过google.protobuf.descriptor动态创建的,DESCRIPTOR是类的核心元数据,直接继承会破坏它的初始化流程,导致找不到这个关键属性,所以才会抛出KeyError。组合或者猴子补丁才是更适配Protobuf设计的方式。
备注:内容来源于stack exchange,提问作者masoud
相关产品推荐
相关产品推荐

