Unit 5: Exception handling - Subjective Questions
ECAP776 • Practice Questions with Detailed Answers
20 questions
Define an exception in Python. Explain how an exception differs from a syntax error.
Exception: An exception is an event that occurs during program execution and interrupts the normal flow of instructions. Examples include ZeroDivisionError, ValueError, and FileNotFoundError.
Difference from a syntax error:
- A syntax error occurs when code violates Python's grammatical rules. It is detected before normal execution begins.
- An exception usually occurs while syntactically correct code is running.
- For example,
10 / 0is syntactically valid but raisesZeroDivisionErrorat runtime. - Exceptions can generally be caught using
tryandexcept, whereas syntax errors must normally be corrected in the source code.
Explain the purpose and execution flow of Python's try and except statements with an example.
The try and except statements allow a program to respond to runtime errors without terminating unexpectedly.
Execution flow:
- Python executes the statements in the
tryblock. - If no exception occurs, the
exceptblock is skipped. - If a matching exception occurs, the rest of the
tryblock is skipped. - Python executes the corresponding
exceptblock. - Execution then continues after the exception-handling structure.
Example:
try:
number = int(input("Enter a number: "))
result = 10 / number
except ValueError:
print("The input must be an integer.")
except ZeroDivisionError:
print("The number cannot be zero.")
This code handles invalid integer input and division by zero separately.
Describe the roles of the else and finally clauses in exception handling. Illustrate their order of execution.
else clause:
- Runs only when the
tryblock completes without raising an exception. - Keeps successful-operation code separate from protected code.
finally clause:
- Runs whether an exception occurs or not.
- Is commonly used for cleanup, such as closing files or releasing resources.
Example:
try:
value = int(input("Enter an integer: "))
except ValueError:
print("Invalid input")
else:
print("Accepted:", value)
finally:
print("Operation completed")
Order:
- On success:
try→else→finally. - On a handled exception:
try→ matchingexcept→finally. - On an unhandled exception:
try→finally→ exception propagation.
Distinguish between catching multiple exceptions using separate except blocks and catching them using an exception tuple.
Separate except blocks are appropriate when different exception types require different responses:
try:
result = int(text) / divisor
except ValueError:
print("Invalid integer")
except ZeroDivisionError:
print("Division by zero")
An exception tuple is appropriate when several exception types require the same response:
try:
result = int(text) / divisor
except (ValueError, ZeroDivisionError) as error:
print("Operation failed:", error)
Key distinction:
- Separate blocks provide specialized handling.
- A tuple reduces duplication when handling is identical.
- In both approaches, only the first matching handler is executed.
- The
as errorsyntax stores the raised exception object for inspection.
Explain why the order of multiple except blocks is important when exceptions have an inheritance relationship.
Python checks except blocks from top to bottom and executes the first compatible handler. Because a handler for a base exception class also matches its subclasses, specific exceptions must appear before general exceptions.
Correct order:
try:
value = int(data)
except ValueError as error:
print("Conversion failed:", error)
except Exception as error:
print("Another error occurred:", error)
ValueError is a subclass of Exception. If except Exception were placed first, it would catch ValueError, making the later specialized handler unreachable in practice.
Rule: Arrange handlers from the most specific exception type to the most general exception type. Avoid a bare except unless there is a compelling reason, because it can hide unexpected problems.
What is the significance of capturing an exception object with the as keyword? Demonstrate how useful information can be obtained from it.
The as keyword assigns the exception instance to a variable. The program can then inspect the object or include its message in diagnostics.
Example:
try:
number = int("abc")
except ValueError as error:
print("Type:", type(error).__name__)
print("Message:", str(error))
print("Arguments:", error.args)
Useful information includes:
type(error).__name__: the exception class name.str(error): a readable description of the error.error.args: the arguments stored in the exception.- Custom exception attributes, when defined.
Capturing the object supports meaningful logging and user feedback. Sensitive internal details should not be exposed directly to end users.
Compare a bare except clause, except Exception, and an exception-specific handler such as except ValueError.
except ValueError:
- Catches only
ValueErrorand its subclasses. - Clearly documents the anticipated failure.
- Is generally the preferred approach.
except Exception:
- Catches most ordinary application-level exceptions.
- Can be useful at a well-defined boundary for logging or recovery.
- May conceal programming defects if used too broadly.
Bare except:
- Catches exceptions derived directly from
BaseException, includingKeyboardInterruptandSystemExit. - Can prevent users from interrupting a program or stop normal shutdown behavior.
- Should therefore be used only in rare cases, usually followed by cleanup and re-raising.
Good exception handling catches only errors the program can meaningfully handle.
Explain exception propagation through nested function calls. What happens when no matching handler is found?
When a function raises an exception and does not handle it, Python terminates that function's current execution and passes the exception to its caller. This process continues upward through the call stack until a matching handler is found.
Example:
def divide(a, b):
return a / b
def calculate():
return divide(10, 0)
try:
calculate()
except ZeroDivisionError:
print("Calculation attempted division by zero")
Here, divide raises ZeroDivisionError. It propagates through calculate and is caught by the outer handler.
If no matching handler exists, Python terminates the program's current execution path and prints a traceback. The traceback identifies the exception and shows the sequence of calls that led to it.
Describe how the raise statement is used to generate exceptions explicitly. Write an example that validates a person's age.
The raise statement explicitly creates or triggers an exception when a program detects an invalid condition.
Syntax: raise ExceptionType("message")
Example:
def validate_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0:
raise ValueError("Age cannot be negative")
return age
The function raises:
TypeErrorwhen the value has an inappropriate type.ValueErrorwhen the type is correct but the value is outside the valid range.
Using standard exception types communicates the nature of the error to callers and lets them decide where and how to handle it.
What does a bare raise statement do inside an except block? Explain re-raising with an example.
A bare raise inside an active except block re-raises the exception currently being handled. It preserves the original exception and traceback.
Example:
def load_number(path):
try:
with open(path) as file:
return int(file.read())
except (OSError, ValueError) as error:
print("Unable to load number:", error)
raise
The handler records useful context but does not know how to recover, so it re-raises the original exception for a higher-level caller to handle.
Writing only raise is generally preferable to raise error for this purpose because the bare form preserves the original traceback more accurately. A bare raise used when no exception is active causes a RuntimeError.
Explain exception chaining using raise ... from .... Why is it useful when translating exceptions?
Exception chaining links a newly raised exception to the exception that caused it. It is useful when low-level implementation errors must be translated into meaningful domain-level errors without losing diagnostic context.
Example:
class ConfigurationError(Exception):
pass
def read_port(text):
try:
return int(text)
except ValueError as error:
raise ConfigurationError("Port must be an integer") from error
The caller receives ConfigurationError, while the traceback states that it was directly caused by ValueError.
Benefits:
- Presents an abstraction-appropriate error to the caller.
- Preserves the original cause for debugging.
- Makes relationships between failures explicit.
Using raise NewError(...) from None intentionally suppresses display of the original context, but it should be used carefully because it can hide useful diagnostic information.
Define a custom exception. Describe the steps for creating, raising, and catching one in Python.
A custom exception is a user-defined exception class representing an error specific to an application or domain.
Steps:
- Define a class that normally inherits from
Exception. - Raise an instance when the relevant invalid condition occurs.
- Catch the custom type where meaningful recovery is possible.
Example:
class InsufficientBalanceError(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientBalanceError("Withdrawal exceeds balance")
return balance - amount
try:
withdraw(500, 800)
except InsufficientBalanceError as error:
print("Transaction rejected:", error)
A custom class makes the failure easier to distinguish from unrelated built-in errors and gives calling code a precise exception type to catch.
Design a custom exception hierarchy for a banking application and explain the advantages of using a common base exception.
A suitable hierarchy is:
class BankingError(Exception):
"""Base class for banking-related failures."""
class InvalidAmountError(BankingError):
pass
class InsufficientFundsError(BankingError):
pass
class AccountLockedError(BankingError):
pass
Usage:
def withdraw(balance, amount, locked=False):
if locked:
raise AccountLockedError("The account is locked")
if amount <= 0:
raise InvalidAmountError("Amount must be positive")
if amount > balance:
raise InsufficientFundsError("Insufficient balance")
return balance - amount
Advantages of a common base class:
- Callers can catch one specific subtype for specialized recovery.
- Higher-level code can catch
BankingErrorto handle every anticipated domain error together. - Unrelated errors, such as programming defects, are not accidentally treated as banking failures.
- The hierarchy improves readability, extensibility, and separation between domain and implementation errors.
How can a custom exception store additional information? Create an exception for an invalid examination score and explain its attributes.
A custom exception can define an initializer that stores contextual values as instance attributes. Calling super().__init__ initializes the standard exception message.
Example:
class InvalidScoreError(ValueError):
def __init__(self, score, minimum=0, maximum=100):
self.score = score
self.minimum = minimum
self.maximum = maximum
message = f"Score {score} must be between {minimum} and {maximum}"
super().__init__(message)
def record_score(score):
if not 0 <= score <= 100:
raise InvalidScoreError(score)
return score
The exception stores:
score: the rejected value.minimum: the lowest permitted value.maximum: the highest permitted value.- A readable message available through
str(error).
A handler can use these structured attributes for logging, user feedback, or recovery without parsing the message text.
Compare using built-in exception classes with defining custom exception classes. When should each approach be preferred?
Built-in exceptions should be preferred when the failure already has a standard meaning:
TypeErrorfor an inappropriate object type.ValueErrorfor an acceptable type with an invalid value.KeyErrorfor a missing mapping key.FileNotFoundErrorfor a missing file.
Custom exceptions should be used when:
- The failure is specific to the application's domain.
- Callers need to distinguish it from similar low-level failures.
- Extra structured information is needed.
- A family of related application errors should share a common base class.
For example, a negative generic function argument may justify ValueError, while a failed bank withdrawal may justify InsufficientFundsError. Custom exceptions should normally inherit from Exception or a suitable built-in subclass and have clear names ending in Error.
Analyze the following pattern and explain why placing too much code inside a try block can cause incorrect exception handling: try: value = int(text); save(value) except ValueError: print("Invalid input").
The handler assumes every ValueError in the try block means that text is invalid. However, save(value) might also raise ValueError for a different reason. That unrelated failure would then be mislabeled as an input-conversion problem.
Improved structure:
try:
value = int(text)
except ValueError:
print("Invalid input")
else:
save(value)
Why this is better:
- The
tryblock contains only the operation expected to raise the handled exception. - A
ValueErrorfromsaveis not mistakenly caught by the conversion handler. - The source and meaning of each failure remain clear.
- Unexpected errors can propagate rather than being concealed.
A narrow try block is therefore an important exception-handling practice.
Write and explain a Python function that repeatedly asks for two integers, handles invalid input and division by zero, and returns the quotient only after a successful calculation.
A possible implementation is:
def read_and_divide():
while True:
try:
numerator = int(input("Numerator: "))
denominator = int(input("Denominator: "))
quotient = numerator / denominator
except ValueError:
print("Enter integers only.")
except ZeroDivisionError:
print("The denominator cannot be zero.")
else:
return quotient
Explanation:
while Truerepeats the operation until it succeeds.intmay raiseValueErrorfor non-integer input.- Division may raise
ZeroDivisionErrorwhen the denominator is zero. - Separate handlers provide error-specific feedback.
- The
elseblock executes only when both conversion and division succeed. returnexits the loop and function with the calculated quotient.
The design catches only anticipated exceptions and does not suppress unrelated failures.
Explain how exception handling should be applied when working with files. Include catching multiple exceptions and guaranteed cleanup.
File operations can fail because a path does not exist, access is denied, data has an invalid format, or another input/output problem occurs.
Example:
def read_integer(path):
try:
with open(path, "r", encoding="utf-8") as file:
content = file.read()
return int(content)
except FileNotFoundError:
print("The requested file does not exist.")
except PermissionError:
print("Permission to read the file was denied.")
except ValueError:
print("The file does not contain a valid integer.")
except OSError as error:
print("Another file-system error occurred:", error)
The context manager created by with closes the file automatically, even if reading fails. If a context manager is unavailable, cleanup should be placed in finally. Specific handlers must precede the broader OSError handler because classes such as FileNotFoundError and PermissionError inherit from OSError.
Discuss the relationship between return, exceptions, and the finally clause. Why is returning from finally considered dangerous?
The finally clause runs before control leaves a try statement, including when the code is about to return or propagate an exception.
Example:
def example():
try:
return 10
finally:
print("Cleanup runs before return")
The function prints the message and then returns 10.
A return inside finally is dangerous because it can replace an earlier return value and can suppress a pending exception:
def unsafe():
try:
raise ValueError("failure")
finally:
return 0
Here, the function returns 0, and the ValueError is lost. Similar problems can occur with control-flow statements in cleanup logic. Therefore, finally should normally perform cleanup only, without returning or otherwise overriding pending control flow.
Develop a robust validation function for a percentage value. It must reject inappropriate types, values outside the valid range, and demonstrate a custom exception, exception chaining, and caller-side handling.
A robust design can separate conversion failures from domain validation failures:
class PercentageError(ValueError):
pass
def parse_percentage(raw_value):
try:
value = float(raw_value)
except (TypeError, ValueError) as error:
raise PercentageError("Percentage must be numeric") from error
if not 0 <= value <= 100:
raise PercentageError(
f"Percentage {value} is outside the range 0 to 100"
)
return value
try:
percentage = parse_percentage(user_input)
except PercentageError as error:
print("Validation failed:", error)
else:
print("Accepted percentage:", percentage)
Explanation:
floatcan raiseTypeErrororValueError; both are translated into one domain-specific exception.from errorpreserves the original conversion failure as the cause.- The range check ensures that the mathematical condition holds.
- The caller catches one clear custom type for all anticipated percentage-validation failures.
- The
elseblock processes the value only after successful validation.
Define an exception in Python. Explain how an exception differs from a syntax error.
Exception: An exception is an event that occurs during program execution and interrupts the normal flow of instructions. Examples include ZeroDivisionError, ValueError, and FileNotFoundError.
Difference from a syntax error:
- A syntax error occurs when code violates Python's grammatical rules. It is detected before normal execution begins.
- An exception usually occurs while syntactically correct code is running.
- For example,
10 / 0is syntactically valid but raisesZeroDivisionErrorat runtime. - Exceptions can generally be caught using
tryandexcept, whereas syntax errors must normally be corrected in the source code.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →