Python 2.7中不重新实例化修改str子类实例存储字符串
解答
Python 2.7 中str是CPython在C层实现的不可变内置类型,实例创建时就固定了内存分配大小和内部存储的字符序列,常规Python层面的属性修改、方法重写都无法改动这个原始值。你观察到的部分场景直接返回实例化传入的原始字符串,是因为这些场景下解释器直接读取C结构体中存储的字符数组,完全绕过了你在Python层重写的__str__和__repr__方法。
不需要重建实例、直接修改原有str子类实例内部存储值的方案,仅能在CPython 2.7环境下通过ctypes直接操作内存实现,具体代码如下:
import ctypes # 映射CPython2.7内部PyStringObject的内存结构 class _PyStringObject(ctypes.Structure): _fields_ = [ ("ob_refcnt", ctypes.c_long), ("ob_type", ctypes.c_void_p), ("ob_size", ctypes.c_long), ("ob_shash", ctypes.c_long), ("ob_sstate", ctypes.c_int), ("ob_sval", ctypes.c_char * 1) ] def modify_str_instance(inst, new_content): """ 原地修改str/str子类实例的内部存储值,不生成新实例 注意:new_content长度必须和实例原有字符串长度完全一致 """ original_len = len(inst) if len(new_content) != original_len: raise ValueError("新字符串长度必须与原字符串完全相等,否则会触发内存越界") # 定位实例对应的C结构体内存地址 c_struct = _PyStringObject.from_address(id(inst)) # 覆写字符数组内容 ctypes.memmove(ctypes.addressof(c_struct.ob_sval), new_content, original_len) # 清空缓存的哈希值,避免字典、集合等哈希场景逻辑异常 c_struct.ob_shash = -1 # 测试对应代码逻辑 class A(str): def __str__(self): return "the string" def __repr__(self): return "the repr" a = A("change me") # 此时直接访问a返回"change me" # 原地修改,注意新字符串长度和原串"change me"一致(共9个字符) modify_str_instance(a, "changed!!") # 此时直接访问a会返回"changed!!",全程没有生成新的A实例
约束说明
- 该方案仅支持CPython 2.7解释器,PyPy、Jython等其他实现的内存结构不匹配,无法使用
- 新传入的字符串长度必须和实例创建时的原始字符串长度完全相等:str作为不可变类型,实例创建时就固定了内存块大小,没有预留扩容空间,长度不匹配会直接造成内存越界崩溃
- 修改会作用于所有指向该实例的引用,因为改动的是对象本身的内存数据,不是变量的绑定关系
- 禁止用该方法修改Python解释器自动驻留的短字符串、代码中硬编码的字符串常量,否则会导致全局字符串逻辑错乱
如果需要将实例内部值修改为不同长度的字符串,不存在原地修改的可行方案:不可变类型的设计就不支持创建后改动内存大小,所有这类操作本质都要生成新实例。
内容的提问来源于stack exchange,提问作者Adam Sirrelle
相关产品推荐
相关产品推荐

