Python Exceptions and Error Handling Study Guide

Syntax Errors vs. Runtime Errors

  • Exceptions represent errors or problems in code execution where something has gone wrong that ideally requires a programmatic solution.
  • Syntax Errors:
    • Occur when typed code violates the grammatical rules of the Python language.
    • Example: Running hello.Py in Versus Code with the code print("hello, world results in SyntaxError: unterminated string literal.
    • An unterminated string literal means a sequence of text (str or literal string) was started with a quotation mark but was not terminated before the end of the line.
    • Syntax errors cannot be caught or handled at runtime by defensive code; they must be manually fixed directly in the source code file before execution.
  • Runtime Errors:
    • Errors that occur dynamically while the program is running.
    • Arise due to unpredictable user input, requiring defensive programming to anticipate mistakes or malicious input.

Handling Input Errors with Try and Except

  • Input conversion behavior in number.Py:
    • Basic code structure to prompt for an integer xx: python x = int(input("what's x? ")) print(f"x is {x}")     
    • Testing corner cases:
    • Positive integer: Inputting 5050 prints x is 50.
    • Zero: Inputting 00 prints x is 0.
    • Negative integer: Inputting 1-1 prints x is -1.
    • Non-numeric string: Inputting cat causes a runtime error.
  • Understanding ValueError:
    • Output message: ValueError: invalid literal for int() with base 10: 'cat'.
    • Invalid literal: The supplied text string literally cannot be converted to an integer.
    • Base 10: Refers to the standard decimal number system used by int() by default.
  • Exception handling using try and except:
    • try: Block used to attempt executing code that might raise an exception.
    • except: Block used to catch and respond to specific exceptions.
    • Capitalization rule: Exception class names are case-sensitive (e.g., ValueError requires capital V and capital E).
    • Indentation rule: Code inside try and except blocks must be indented by 44 spaces.
    • Basic implementation catching ValueError: python try: x = int(input("what's x? ")) print(f"x is {x}") except ValueError: print("x is not an integer")     

Variable Scope, Name Errors, and the Else Block

  • Best practice for try blocks:
    • Minimize code within the try block to only include lines capable of raising the specific target exception.
    • Printing xx does not raise a ValueError, so it should reside outside the try block.
  • Cause of NameError:
    • Moving print(f"x is {x}") below except without an else block: python try: x = int(input("what's x? ")) except ValueError: print("x is not an integer") print(f"x is {x}")     
    • Entering cat produces NameError: name 'x' is not defined on line 66
    • Assignment order of operations:
    • In x = int(input(...)), the right-hand side of the = operator evaluates first.
    • int() fails and raises a ValueError before completing assignment to the left-hand side variable x.
    • Control jumps straight to except, leaving xx completely undefined when line 66 attempts to reference it.
  • Utilizing the else block:
    • else: Executes only if the preceding try block completes successfully without raising exceptions.
    • Refactored code structure using else: python try: x = int(input("what's x? ")) except ValueError: print("x is not an integer") else: print(f"x is {x}")     

Reprompting with Loops and Break Statements

  • Continually prompting users using loops:
    • Replacing immediate exit behavior with an infinite loop (while True) to re-prompt until valid input is given.
  • Using break inside else:
  while True:
      try:
          x = int(input("what's x? "))
      except ValueError:
          print("x is not an integer")
      else:
          break

  print(f"x is {x}")
  ```
* Alternative loop design placing `break` directly inside `try`:

python while True: try: x = int(input("what's x? ")) break except ValueError: print("x is not an integer")

print(f"x is {x}")   ```

  • Execution logic: If int() succeeds, line execution continues to break to exit the loop. If int() raises a ValueError, execution jumps immediately to except, skipping break entirely.

Modularizing Code with Custom Functions and Return Values

  • Encapsulating input logic into a reusable function get_int():
  def main():
      x = get_int()
      print(f"x is {x}")

  def get_int():
      while True:
          try:
              x = int(input("what's x? "))
          except ValueError:
              print("x is not an integer")
          else:
              return x

  main()
  ```
* `return` vs. `break` behavior:
  * `return` exits the function entirely and handed back a value, automatically terminating any active loops in the process.
* Tightening implementation by eliminating intermediate variables:

python def get_int(): while True: try: return int(input("what's x? ")) except ValueError: print("x is not an integer")   ```

Ignoring Exceptions with Pass and Adding Function Parameters

  • Silencing errors with pass:

    • pass: Keyword used to catch an exception silently without printing messages or interrupting loop execution.
    • Silent re-prompt implementation: python def get_int(): while True: try: return int(input("what's x? ")) except ValueError: pass     
  • Parameterizing prompts for reusability:

    • Avoid hardcoding prompt strings like "what's x?" inside generic helper functions.
    • Definitions:
    • Caller: The function making a call to another function (e.g., main()).
    • Callee: The function being invoked (e.g., get_int()).
    • Dynamic function implementation: ```python def main(): x = get_int("what's x? ") print(f"x is {x}")

    def get_int(prompt): while True: try: return int(input(prompt)) except ValueError: pass

    main()     ```

Questions & Discussion

  • Question: What is an interpreter? When coding in an integrated IDE, can we code in an interpreter? What if the user types something that is not an integer?
    • Response: Programs must be written defensively because attempting to convert invalid non-integer string input using int() causes runtime errors. Corner cases such as positive integers, zero (00), negative numbers (1-1), and non-numeric strings (cat) should be tested routinely.
  • Question: To use the except block, do you need to know the type of error in advance? What if you cannot anticipate the specific type of error?
    • Response: Omitting the specific error type (e.g., writing bare except:) catches all exceptions. However, this is bad practice and lazy because it hides bugs and makes proper error resolution difficult. Although official documentation does not always explicitly list every error a function can raise, identifying explicit exceptions becomes easier with experience.
  • Question: Is the scope of variable xx restricted strictly between the try block?
    • Response: Scope refers to the region of code where a variable exists. Unlike languages like C, C++, or Java (where curly braces strictly define local block scope), Python variables assigned inside try blocks remain defined outside the block once assigned. The NameError occurs because the assignment operator failed to evaluate due to the error, not because of block scope rules.
  • Question: Can the explanation of NameError be repeated?
    • Response: In x = int(input(...)), the right side evaluates first. If int() raises ValueError, control jumps immediately to except, skipping assignment to xx. Calling xx later fails with NameError because xx was never assigned a value.
  • Question: Can break be used inside except or else blocks, or only in loops?
    • Response: break can be used inside any loop, including inside if, elif, else, or try/except statements nested within a loop.
  • Question: Can you elaborate on Python indentation rules?
    • Response: Indentation in Python syntactically defines logical block relationships. Statements indented under def, while, try, or except belong exclusively to that parent block. Indentation standardly uses 44 spaces per nesting level (e.g., 44, 88, 1212 spaces).
  • Question: Once pass is executed, can the caller still learn about the error through a system variable?
    • Response: No, using pass suppresses the exception entirely, so calling routines like main() remain completely unaware that an error occurred.
  • Question: Is using string methods like isnumeric() different from using try/except?
    • Response: isnumeric() allows pre-checking strings with conditionals (if/else) before conversion. While valid, the traditional "Pythonic" design philosophy prefers attempting the operation inside a try block and handling exceptions if they occur.