Python中如何实现跨多层异步函数的‘双重返回’?
Problem Statement
I have three nested async functions with the call chain start_swings → get_question → swing_math (or swing_reverse). In the swing_math function, when detecting the user input >swing, the current return only takes me back to get_question. I want to achieve a "double return" that jumps directly back to start_swings, or even stop the entire script execution. How can I do this?
Here's my code:
async def start_swings(self, server, user): try: while self.jailed[server.id][user.id]['current'] != 0: await self.get_question(server, user) else: await self.escape_jail(server, user) except KeyError: # User isn't jailed pass async def get_question(self, server, user): rnd = random.choice(range(2)) if rnd == 0: await self.swing_math(server, user) elif rnd == 1: await self.swing_reverse(server, user) async def swing_math(self, server, user): num1 = random.randint(1, 1000) num2 = random.randint(1, 1000) ops = ['+', '-'] op = random.choice(ops) ans = int(eval(str(num1) + op + str(num2))) await self.bot.send_message(user, "**\N{HAMMER} It's going to take you `{}` more swings to break free.**\n```What is {} {} {}?```\n*I might doze off if you take too long to answer. If you don't get a reply from me within a few seconds, type `>swing`. If you try to use `>swing` as a way to reset your current question, you'll get a cooldown penalty.*".format(self.jailed[server.id][user.id]['current'], num1, op, num2)) resp = await self.bot.wait_for_message(author=user) resp = resp.clean_content if resp == str(ans): await self.bot.send_message(user, "Correct. \N{WHITE HEAVY CHECK MARK} Take a swing.") self.jailed[server.id][user.id]['current'] -= 1 dataIO.save_json('data/jail/jailed.json', self.jailed) elif resp == ">swing": return else: await self.bot.send_message(user, "Wrong. \N{CROSS MARK} Let me put that brick back in place.") self.jailed[server.id][user.id]['current'] += 1 dataIO.save_json('data/jail/jailed.json', self.jailed)
Solutions
1. Custom Exception (Recommended)
Custom exceptions are perfect for this scenario because they can bypass multiple layers of function calls and be caught directly at the top level, letting you jump straight back to start_swings or terminate execution entirely.
Step 1: Define the custom exception
First, create a simple exception class to signal the reset request:
class SwingReset(Exception): """Triggered when the user enters >swing to reset the flow""" pass
Step 2: Throw the exception in swing_math
Replace the return statement with raising your custom exception:
elif resp == ">swing": raise SwingReset("User requested to reset the swing process")
Step 3: Catch the exception in start_swings
Let the exception propagate up from swing_math to get_question (no extra code needed there) and catch it in start_swings. You can then choose to break the loop (stop execution) or add additional reset logic:
async def start_swings(self, server, user): try: while self.jailed[server.id][user.id]['current'] != 0: try: await self.get_question(server, user) except SwingReset: await self.bot.send_message(user, "Resetting your current question. Feel free to start over!") break # Stop the entire script execution here else: await self.escape_jail(server, user) except KeyError: # User isn't jailed pass
This approach is clean and efficient, especially if you need to terminate the flow abruptly.
2. Return Status Flags
If you prefer avoiding exceptions, you can pass a boolean flag up through each nested function to signal whether to terminate the flow.
Step 1: Update swing_math to return a status
Modify swing_math to return True when the user enters >swing, and False otherwise:
async def swing_math(self, server, user): # ... existing code ... if resp == str(ans): # ... existing logic ... return False # No reset needed elif resp == ">swing": return True # Reset requested else: # ... existing logic ... return False
Step 2: Pass the flag up in get_question
Have get_question capture the return value from swing_math/swing_reverse and pass it to start_swings:
async def get_question(self, server, user): rnd = random.choice(range(2)) if rnd == 0: reset_requested = await self.swing_math(server, user) elif rnd == 1: reset_requested = await self.swing_reverse(server, user) return reset_requested
Step 3: Act on the flag in start_swings
Check the returned flag in start_swings and decide whether to break the loop:
async def start_swings(self, server, user): try: while self.jailed[server.id][user.id]['current'] != 0: reset_requested = await self.get_question(server, user) if reset_requested: await self.bot.send_message(user, "Resetting your question flow.") break # Stop execution else: await self.escape_jail(server, user) except KeyError: # User isn't jailed pass
This method is more explicit but requires modifying each layer of the call chain to pass the status along.
Which Approach Should You Choose?
- Use the custom exception method if you need an abrupt termination of the nested flow—it's cleaner and avoids cluttering your code with status flag passes.
- Use the status flag method if you prefer a more procedural, exception-free approach and need to handle intermediate states along the way.
内容的提问来源于stack exchange,提问作者Craig

