Python How to Exit from Deeply Nested Methods Back to Main Menu

whitehousechef·2025년 7월 3일

Problem Summary

Current Code Structure:

Main Menu run() 
  → _add_movie() 
    → system.run() 
      → _select_cinema()

Problem: When user presses Enter in _select_cinema(), I want to return all the way back to the Main Menu run(), not just continue in the current method.

Current Issue: The return in _select_cinema() only exits that method, but system.run() continues to display the main menu instead of going back to the Main Menu.


Original Code (With Problem)

def _select_cinema(self):
    try:
        print("Enter movie name to buy/update tickets or Press Enter to return to previous page")
        user_input_movie_title = self._get_user_input("").strip()
        if not user_input_movie_title:
            return  # THIS ONLY EXITS _select_cinema(), NOT system.run()
        self.select_cinema = self.theatre.get_cinema_by_title(user_input_movie_title)
    except KeyboardInterrupt:
        print("Returning")

def run(self):  # This is system.run()
    """Run the main application loop."""
    try:
        while True:
            self._select_cinema()  # When this returns, code continues below
            self._display_main_menu()  # THIS STILL RUNS even when user pressed Enter
            choice = self._get_user_input("").strip()
            # ... rest of menu logic
    except KeyboardInterrupt:
        print("\n\nThank you for using GIC Cinemas system. Bye!")

def _add_movie(self, theatre: Theatre):
    while True:
        # ... movie creation logic ...
        system = CinemaBookingSystemFactory.add_cinema_to_theatre(theatre, user_input, tmp_lines)
        system.run()  # THIS KEEPS RUNNING even when user wants to go back

Solution Option 2: Exception-Based Control Flow

Concept: Use custom exceptions to "bubble up" navigation commands through the call stack.

# Define custom exceptions
class NavigationException(Exception):
    pass

class BackToMainMenuException(NavigationException):
    pass

# Modify _select_cinema to throw exception
def _select_cinema(self):
    user_input_movie_title = self._get_user_input("").strip()
    if not user_input_movie_title:  # User pressed Enter
        raise BackToMainMenuException()
    self.select_cinema = self.theatre.get_cinema_by_title(user_input_movie_title)

# Modify _add_movie to catch and handle
def _add_movie(self, theatre: Theatre):
    try:
        # ... existing code ...
        system.run()
    except BackToMainMenuException:
        return  # Go back to main menu

# system.run() doesn't need to change - exception bubbles up automatically

Pros: Clean, automatically bubbles up through call stack, easy to add more navigation types
Cons: Uses exceptions for control flow (some consider this bad practice)

Exceptions are menat for exception, unforseen situations (errors and failures), not normal program flow. There is also code readability issue and performance issues like
1)Memory allocation for exception objects
2)Call stack inspection
3)Much slower than simple return statements


Solution Option 3: Boolean Return Values

Concept: Use boolean returns to indicate success/failure, check at each level.

# Modify _select_cinema to return boolean
def _select_cinema(self):
    user_input_movie_title = self._get_user_input("").strip()
    if not user_input_movie_title:  # User pressed Enter
        return False  # Indicate user wants to go back
    self.select_cinema = self.theatre.get_cinema_by_title(user_input_movie_title)
    return True  # Indicate success

# Modify system.run() to check return value
def run(self):  # This is system.run()
    while True:
        if not self._select_cinema():
            return False  # User wants to go back
        # ... rest of menu logic ...
        return True  # Normal completion

# Modify _add_movie to check return value
def _add_movie(self, theatre: Theatre):
    # ... existing code ...
    if not system.run():
        return  # Go back to main menu

Pros: Simple, explicit, easy to understand
Cons: Need to check return values at each level, more boilerplate code


Clean Code Analysis

Option 3 (Boolean) - CLEANEST ✅

def _select_cinema(self):
    if not user_input_movie_title:
        return False  # Clear intent: operation failed
    # ... success logic
    return True  # Clear intent: operation succeeded

Pros:

  • Explicit and readable - clear success/failure semantics
  • Follows common patterns - many methods return boolean for success/failure
  • Easy to understand - no special knowledge needed
  • Predictable - caller knows exactly what to expect
  • Testable - easy to unit test return values

Option 2 (Exceptions) - LESS CLEAN ❌

def _select_cinema(self):
    if not user_input_movie_title:
        raise BackToMainMenuException()  # Using exceptions for control flow

Cons:

  • Misuses exceptions - exceptions should be for exceptional cases, not normal flow
  • Harder to understand - need to know custom exception hierarchy
  • Performance overhead - exception throwing is expensive
  • Violates clean code principles - exceptions aren't for control flow

Even Better Option 4 - CLEANEST OVERALL ✅

Use an Enum for even cleaner semantics:

from enum import Enum

class UserAction(Enum):
    CONTINUE = "continue"
    GO_BACK = "go_back"
    EXIT = "exit"

def _select_cinema(self):
    if not user_input_movie_title:
        return UserAction.GO_BACK
    # ... success logic
    return UserAction.CONTINUE

def run(self):
    while True:
        action = self._select_cinema()
        if action == UserAction.GO_BACK:
            return UserAction.GO_BACK
        # ... rest of logic

Why this is cleanest:

  • Self-documenting - UserAction.GO_BACK is crystal clear
  • Type-safe - can't accidentally return wrong values
  • Extensible - easy to add new actions later
  • IDE support - autocomplete and refactoring support

Ranking:
1. Option 4 (Enum) - Best for larger applications
2. Option 3 (Boolean) - Best for simple cases like yours
3. Option 2 (Exceptions) - Avoid for control flow

For your current needs: Use Option 3 (Boolean). It's simple, clean, and perfect for your use case.

finally

once u receive the result from system.run(), you can handle it in your FIRST and outermost run() however you like. But be careful!! I ran system.run() twice with this if elif statement, which doesnt exit the code! Instead, store the result in a variable and do logic onto it

wrong:

system = CinemaBookingSystemFactory.add_cinema_to_theatre(theatre, user_input, tmp_lines)
if system.run() == UserAction.GO_BACK:  # ← First call to run()
    return
elif system.run() == UserAction.EXIT:   # ← Second call to run()
    sys.exit(0)

correct:

system = CinemaBookingSystemFactory.add_cinema_to_theatre(theatre, user_input, tmp_lines)
result = system.run()  # Call it once and store the result
if result == UserAction.GO_BACK:
    return
elif result == UserAction.EXIT:
    sys.exit(0)

0개의 댓글