使用python-socket时出现RecursionError及UUID序列化问题求助
Python Socket.IO 遇到 RecursionError 求助
Socket配置无问题,简单测试事件可正常运行,但在返回包含payload的响应时触发RecursionError,注释掉"payload": res["data"]后代码运行正常。
相关代码
Socket事件监听代码
@sio_server.on(EVENT.REPLY_ON_TICKET) async def reply_on_ticket_event(sid:str, data:dict): sender_id = None if data["sender_info"]["sender_id"]: sender_id= data["sender_info"]["sender_id"] else: sender_id= data["sender_id"] payload = { "ticket_id": data["ticket_id"], "sender_id": sender_id, "notify": data["notify"], "reply": data["reply"], } # Obtain a database session manually db_session = next(get_db()) res = await reply_on_ticket_service(payload, db_session) if res["success"]: # the callback data for client socket return { "status_code": 200, "success": True, "message": "reply data", "payload": res["data"], } return {"status_code": 400, "success": False}
工单回复逻辑服务代码
async def reply_on_ticket_service(data:dict, db_session: Session = Depends(get_db)): try: new_reply= TicketReplyModal(reply_text=data["reply"],ticket_id=data["ticket_id"],sender_id=data["sender_id"]) new_reply_id= await db_manager.add_record(new_reply,db_session) if not new_reply_id: return {"success":False, 'status_code':400, "message":"Error occurred while creating new replay"} new_record= await db_manager.get_record_by_id(new_reply_id, TicketReplyModal,db_session) if not new_record: return {"success":False, 'status_code':404, "message":"Unable to find record with newly created replay"} sender_data = db_session.query(UserModel).filter(UserModel.id == new_record.sender_id).first() if not sender_data: return {"success":False, 'status_code':404, "message":"Unable to find sender info"} reply = { "id":new_record.id, "created_at":new_record.created_at, "reply_text":new_record.reply_text, "ticket_id": new_record.ticket_id, "sender":{ "id": sender_data.id, "email": sender_data.email, "profile_image": sender_data.profile_image, "username": sender_data.username, "public_key": sender_data.public_key, } } return {"success": True, "data": reply} except Exception as e: raise HTTPException(status_code=500, detail=str(e))
自定义JSON编码器代码(错误根源)
class CustomJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, uuid.UUID): return str(obj) return super().default(obj) @app.on_event("startup") def startup_event(): json.JSONEncoder.default = CustomJSONEncoder().default
错误信息
RecursionError日志
Task exception was never retrieved future: <Task finished name='Task-39' coro=<AsyncServer._handle_event_internal() done, defined at C:\Users\AK\.virtualenvs\BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py:605> exception=RecursionError('maximum recursion depth exceeded')> Traceback (most recent call last): File "C:\Users\AK\.virtualenvs\BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py", line 617, in _handle_event_internal await server._send_packet(eio_sid, self.packet_class( File "C:\Users\AK\.virtualenvs\BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py", line 518, in _send_packet encoded_packet = pkt.encode() ^^^^^^^^^^^^ File "C:\Users\AK\.virtualenvs\BE-hVO0_cLf\Lib\site-packages\socketio\packet.py", line 64, in encode encoded_packet += self.json.dumps(data, separators=(',', ':')) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\__init__.py", line 238, in dumps **kw).encode(obj) ^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\encoder.py", line 200, in encode chunks = self.iterencode(o, _one_shot=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\encoder.py", line 258, in iterencode return _iterencode(o, 0) ^^^^^^^^^^^^^^^^^ File "D:\BE\app.py", line 38, in default return super().default(obj) ^^^^^^^^^^^^^^^^^^^^ File "D:\BE\app.py", line 38, in default return super().default(obj) ^^^^^^^^^^^^^^^^^^^^ File "D:\BE\app.py", line 38, in default return super().default(obj) ^^^^^^^^^^^^^^^^^^^^ [Previous line repeated 972 more times] RecursionError: maximum recursion depth exceeded
注释编码器后的UUID序列化错误
Task exception was never retrieved future: <Task finished name='Task-47' coro=<AsyncServer._handle_event_internal() done, defined at C:\Users\AK\.virtualenvs\pw7-BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py:605> exception=TypeError('Object of type UUID is not JSON serializable')> Traceback (most recent call last): File "C:\Users\AK\.virtualenvs\pw7-BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py", line 617, in _handle_event_internal await server._send_packet(eio_sid, self.packet_class( File "C:\Users\AK\.virtualenvs\pw7-BE-hVO0_cLf\Lib\site-packages\socketio\async_server.py", line 518, in _send_packet encoded_packet = pkt.encode() ^^^^^^^^^^^^ File "C:\Users\AK\.virtualenvs\pw7-BE-hVO0_cLf\Lib\site-packages\socketio\packet.py", line 64, in encode encoded_packet += self.json.dumps(data, separators=(',', ':')) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\__init__.py", line 238, in dumps **kw).encode(obj) ^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\encoder.py", line 200, in encode chunks = self.iterencode(o, _one_shot=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\encoder.py", line 258, in iterencode return _iterencode(o, 0) ^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\json\encoder.py", line 180, in default raise TypeError(f'Object of type {o.__class__.__name__} ' TypeError: Object of type UUID is not JSON serializable
问题原因
自定义JSON编码器的实现方式错误:将CustomJSONEncoder实例的default方法赋值给json.JSONEncoder.default后,在CustomJSONEncoder.default中调用super().default(obj)时,super()指向的父类JSONEncoder的default方法已经被替换成了当前的CustomJSONEncoder.default,导致无限递归调用,最终触发RecursionError。
解决方案
方案1:给Socket.IO指定自定义JSON序列化函数(推荐)
不需要全局修改JSON编码器,直接在Socket.IO初始化时指定自定义的序列化方法,避免全局污染:
import json import uuid from socketio import AsyncServer class CustomJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, uuid.UUID): return str(obj) return super().default(obj) # 定义自定义的dumps函数 def custom_json_dumps(data): return json.dumps(data, cls=CustomJSONEncoder) # 初始化Socket.IO服务器时传入json参数 sio_server = AsyncServer(json=custom_json_dumps)
方案2:正确全局替换JSONEncoder.default
保存原始的default方法,避免递归调用:
import json import uuid # 保存原始的default方法 original_default = json.JSONEncoder.default def custom_default(obj): if isinstance(obj, uuid.UUID): return str(obj) # 调用原始的default方法处理其他类型 return original_default(obj) @app.on_event("startup") def startup_event(): json.JSONEncoder.default = custom_default
内容的提问来源于stack exchange,提问作者Abidullah
相关产品推荐
相关产品推荐

