如何修复UserString替换builtins.str引发的RecursionError问题
递归错误原因
原代码触发RecursionError的核心逻辑是:UserString内部实现会依赖全局命名空间的str类型做数据转换、实例初始化操作。你把builtins.str替换为自定义的String子类后,每次创建String实例,UserString的内部逻辑就会再次调用被替换后的str(也就是String自身)创建新实例,无限循环嵌套最终触发栈溢出。
修复方案
方案1:不替换内置str,单独使用自定义字符串类(推荐)
直接替换内置类型属于高风险的猴子补丁操作,很容易和标准库、第三方依赖的逻辑产生兼容问题,绝大多数场景下直接使用自定义子类即可满足需求,不会触发递归:
import unicodedata from collections import UserString class CustomStr(UserString): def isnumber(self): # 校验是否为合法浮点格式字符串 try: float(self.data) return True except ValueError: pass # 校验是否为Unicode定义的数值类字符 try: unicodedata.numeric(self.data) return True except (TypeError, ValueError): pass return False # 需要使用带isnumber方法的字符串时,手动实例化CustomStr即可 print(CustomStr('0').isnumber()) # True print(CustomStr('1').isnumber()) # True print(CustomStr('-1').isnumber()) # True print(CustomStr('0.1').isnumber()) # True print(CustomStr('a1').isnumber()) # False
方案2:直接给原生str类动态绑定方法(适配字面量直接调用的需求)
如果你一定要实现所有原生字符串、字符串字面量都能直接调用isnumber()的效果,不要通过替换builtins.str的方式实现,可以直接给原生str类动态绑定方法,全程不会修改str的引用,自然不会触发递归:
import ctypes import unicodedata def isnumber(self): try: float(self) return True except ValueError: pass try: unicodedata.numeric(self) return True except (TypeError, ValueError): pass return False # 绕过Python的内置类属性修改限制,动态给str绑定方法 class PyObject(ctypes.Structure): _fields_ = [("ob_refcnt", ctypes.c_ssize_t), ("ob_type", ctypes.c_void_p)] def get_class_dict(cls): if hasattr(cls, '__dictoffset__') and cls.__dictoffset__: addr = id(cls) + cls.__dictoffset__ return ctypes.cast(addr, ctypes.POINTER(ctypes.py_object))[0] return cls.__dict__ str_class_dict = get_class_dict(str) str_class_dict['isnumber'] = isnumber # 测试:原生字符串、字面量都可以直接调用 print('0'.isnumber()) # True print('-1'.isnumber()) # True print('0.1'.isnumber()) # True print('a1'.isnumber()) # False print(str(123).isnumber()) # True
注意:直接修改内置类的方法属于侵入式操作,仅适合在完全可控的个人脚本环境中使用,不要在通用库、线上生产环境中使用,避免引发不可预知的兼容问题。
内容的提问来源于stack exchange,提问作者XerCis
相关产品推荐
相关产品推荐

