遍历Django请求对象遇字典迭代大小变更错误及字节键转换
解决Python字典字节键转字符串及Django request对象迭代报错问题
背景问题
在Python里,当我们用json.dumps序列化带有字节字符串键的字典时会直接报错——因为json.dumps的default参数只能处理值的转换,完全不会处理字典的键。为了搞定这个问题,我们通常会写一个递归函数keys_string,把所有字节类型的键解码成字符串,同时递归处理嵌套的容器(字典、列表、元组、集合)。
最初的实现代码
def keys_string(d): rval = {} if not isinstance(d, dict): if isinstance(d,(tuple,list,set)): v = [keys_string(x) for x in d] return v else: return d for k,v in d.items(): if isinstance(k,bytes): k = k.decode() if isinstance(v,dict): v = keys_string(v) elif isinstance(v,(tuple,list,set)): v = [keys_string(x) for x in v] rval[k] = v return rval
遇到的RuntimeError问题
在Python 3.7.3 + Django 2.2.6的调试场景中,当我们尝试处理request对象时:
def articles(request): request_dir = dir(request) request_dir_keys_stringed = keys_string(request_dir) print(json.dumps(request_dir_keys_stringed, indent=4, sort_keys=True, default=str)) return render(request, 'articles/main_page/articles.html')
触发了RuntimeError: dictionary changed size during iteration错误,具体是处理键user对应的SimpleLazyObject时出现的,但处理request.session时没有这个问题。
报错原因分析
这是因为直接迭代d.items()或者d.keys()时,如果迭代过程中原字典的大小发生了变化(比如Django的一些惰性对象在访问时会动态生成新的键),就会触发这个错误。而把d.keys()转换成列表list(d.keys())后,会先把所有键一次性加载到内存中,后续迭代的是这个固定的列表,不会受原字典动态变化的影响。
修复后的函数实现
def keys_string(d): rval = {} if not isinstance(d, dict): if isinstance(d,(tuple,list,set)): v = [keys_string(x) for x in d] return v else: return d # 先把键转为列表,避免迭代时原字典动态变化导致报错 keys = list(d.keys()) for k in keys: v = d[k] if isinstance(k,bytes): k = k.decode() if isinstance(v,dict): v = keys_string(v) elif isinstance(v,(tuple,list,set)): v = [keys_string(x) for x in v] rval[k] = v return rval
效果验证
修改后的函数可以正常转换Django的request对象,并且成功执行json.dumps序列化,不会再出现RuntimeError。
内容的提问来源于stack exchange,提问作者Santhosh
相关产品推荐
相关产品推荐

