为什么pyrlp的rlp.Serializable类初始化后无法修改属性?如何实现属性可变?
rlp.Serializable 从设计之初就是不可变值对象,类初始化时会把fields中声明的所有字段自动注册为只读属性,只实现读取逻辑、不提供写入setter,所以直接执行account.balance +=1这类赋值操作时,必然会触发AttributeError: can't set attribute的报错。
因为你需要把序列化后的账户存入Merkle Trie结构,要优先保证序列化格式、哈希计算的一致性,避免出现旧状态篡改导致的根哈希不匹配问题:
方案一:用内置copy方法生成更新后的新实例(最推荐)
rlp.Serializable自带copy方法,可以完整继承原有实例的所有字段值,同时支持传入参数覆盖需要修改的字段,生成全新的合法实例。整个过程不会改动原对象的值,新实例完全保留原有的RLP类型校验和序列化逻辑,可以直接存入Trie结构。
原有的余额自增逻辑可以直接替换为:
# 基于原账户生成新对象,仅覆盖需要更新的balance字段 account = account.copy(balance=account.balance + 1)
这种方式和区块链状态更新的逻辑天然匹配:每次状态变更生成独立的新对象、重新计算哈希,完全符合Merkle Trie的不可篡改设计要求。
方案二:为可变属性手动实现setter(不推荐用于生产逻辑)
如果确实需要保留原实例的引用、直接修改属性值,可以绕过原生的只读property限制,为需要可修改的字段手动实现setter,示例代码如下:
class Account(rlp.Serializable): fields = ( ('nonce', big_endian_int), ('address', binary), ('balance', big_endian_int) ) @property def nonce(self): return super().nonce @nonce.setter def nonce(self, value): # 将修改后的值直接存入实例字典,绕过类层面的只读property限制 self.__dict__['nonce'] = value @property def balance(self): return super().balance @balance.setter def balance(self, value): self.__dict__['balance'] = value
注意:这种方式会绕过
rlp原生的类型校验,修改时如果传入的字段值不符合对应sedes的类型要求(比如balance传入字符串而非整数),会在序列化阶段直接报错;同时因为原实例可以被随意修改,很容易出现已经计算过哈希的旧状态节点被篡改、导致Merkle根哈希和实际状态不匹配的问题,仅适合临时调试场景使用。
内容的提问来源于stack exchange,提问作者oweshoal

