
Python
Master sophisticated exception raising patterns for enterprise systems.
Implement contracts using exceptions:
# Design by contract: preconditions, postconditions, invariants
class BankAccount:
def __init__(self, balance=0):
self._balance = balance
self._check_invariant()
def _check_invariant(self):
"""Check that object is in valid state"""
if self._balance < 0:
raise InvariantViolationError("Balance cannot be negative")
def withdraw(self, amount):
"""
Precondition: amount > 0 and amount <= balance
Postcondition: new_balance = old_balance - amount
"""
# Check preconditions
if not isinstance(amount, (int, float)):
raise PreconditionError("Amount must be numeric")
if amount <= 0:
raise PreconditionError("Amount must be positive")
if amount > self._balance:
raise PreconditionError(f"Insufficient funds: need {amount}, have {self._balance}")
old_balance = self._balance
self._balance -= amount
# Check postcondition
if self._balance != old_balance - amount:
raise PostconditionError("Balance calculation error")
# Check invariant
self._check_invariant()
return self._balance
class ContractError(Exception):
pass
class PreconditionError(ContractError):
pass
class PostconditionError(ContractError):
pass
class InvariantViolationError(ContractError):
pass
# Usage
account = BankAccount(100)
try:
account.withdraw(150) # Precondition violation
except PreconditionError as e:
print(f"Contract violation: {e}")# Multi-level validation strategy
class ValidationError(Exception):
def __init__(self, field, constraint, value):
self.field = field
self.constraint = constraint
self.value = value
super().__init__(f"{field}: {constraint}")
class ValidationRules:
"""Composable validation rules"""
def __init__(self):
self.rules = []
def add_rule(self, predicate, message):
self.rules.append((predicate, message))
return self
def validate(self, value):
for predicate, message in self.rules:
if not predicate(value):
raise ValidationError('field', message, value)
# Usage
email_rules = ValidationRules()\
.add_rule(lambda x: '@' in x, "must contain @")\
.add_rule(lambda x: '.' in x.split('@')[1], "domain must have extension")\
.add_rule(lambda x: len(x) > 5, "must be at least 5 characters")
def validate_email(email):
email_rules.validate(email)
return True
try:
validate_email("invalid")
except ValidationError as e:
print(f"Validation failed: {e}")
# Complex nested validation
class User:
def __init__(self, name, email, age):
self._validate_and_set('name', name, str, lambda x: len(x) > 0)
self._validate_and_set('email', email, str, self._is_valid_email)
self._validate_and_set('age', age, int, lambda x: 0 < x < 150)
def _validate_and_set(self, field, value, expected_type, predicate):
if not isinstance(value, expected_type):
raise ValidationError(field, f"must be {expected_type.__name__}", value)
if not predicate(value):
raise ValidationError(field, "constraint failed", value)
setattr(self, f'_{field}', value)
@staticmethod
def _is_valid_email(email):
return '@' in email and '.' in email.split('@')[1]
# Usage
try:
user = User("Alice", "alice@example.com", 25)
except ValidationError as e:
print(f"User creation failed: {e}")# Strategy 1: Guard clauses
def process_order(order):
"""Use early returns to fail fast"""
if not order:
raise ValueError("Order cannot be None")
if order['items'] is None or len(order['items']) == 0:
raise ValueError("Order must have at least one item")
if order['total'] <= 0:
raise ValueError("Order total must be positive")
if order['customer_id'] is None:
raise ValueError("Order must have customer")
# Only process if all conditions met
return _process_valid_order(order)
# Strategy 2: Accumulate errors
def validate_form(form_data):
"""Collect all errors before raising"""
errors = {}
if not form_data.get('username'):
errors['username'] = "Required"
elif len(form_data['username']) < 3:
errors['username'] = "Must be at least 3 characters"
if not form_data.get('password'):
errors['password'] = "Required"
elif len(form_data['password']) < 8:
errors['password'] = "Must be at least 8 characters"
if not form_data.get('email'):
errors['email'] = "Required"
elif '@' not in form_data['email']:
errors['email'] = "Invalid email format"
if errors:
raise FormValidationError(errors)
return True
class FormValidationError(Exception):
def __init__(self, errors):
self.errors = errors
super().__init__(f"Form validation failed with {len(errors)} errors")
# Strategy 3: Conditional raising
def safe_convert_to_int(value, allow_none=False):
"""Raise only if truly invalid"""
if value is None:
if allow_none:
return None
raise ValueError("Value cannot be None")
if isinstance(value, int):
return value
if isinstance(value, str):
if value.isdigit():
return int(value)
raise ValueError(f"'{value}' is not a valid integer")
raise TypeError(f"Cannot convert {type(value).__name__} to int")# Include rich context in exceptions
class ContextualException(Exception):
def __init__(self, message, context=None):
self.message = message
self.context = context or {}
self.timestamp = datetime.now()
super().__init__(self._format_message())
def _format_message(self):
msg = self.message
if self.context:
ctx_str = ', '.join(f"{k}={v}" for k, v in self.context.items())
msg = f"{msg} | Context: {ctx_str}"
return msg
def add_context(self, **kwargs):
self.context.update(kwargs)
return self
# Usage
def api_request(endpoint, method='GET', timeout=5, retries=3):
for attempt in range(retries):
try:
return make_request(endpoint, method=method, timeout=timeout)
except requests.RequestException as e:
if attempt == retries - 1:
raise ContextualException(
"API request failed",
context={
'endpoint': endpoint,
'method': method,
'attempts': attempt + 1,
'error': str(e),
'timestamp': datetime.now()
}
) from e
# Exception with structured data
class StructuredError(Exception):
"""Exception with JSON-serializable structure"""
def __init__(self, error_code, message, details=None):
self.error_code = error_code
self.message = message
self.details = details or {}
super().__init__(message)
def to_json(self):
return {
'error_code': self.error_code,
'message': self.message,
'details': self.details,
'timestamp': datetime.now().isoformat()
}
def process_payment(amount, card_token):
try:
charge_card(card_token, amount)
except CardDeclined as e:
raise StructuredError(
error_code='PAYMENT_DECLINED',
message='Payment was declined',
details={
'reason': str(e),
'amount': amount,
'card_last_4': card_token[-4:]
}
) from e# Raise based on system state
class StatefulOperation:
def __init__(self):
self.state = 'IDLE'
self.data = None
def start(self):
if self.state != 'IDLE':
raise InvalidStateError(
f"Cannot start from {self.state} state. Must be IDLE"
)
self.state = 'RUNNING'
def process(self, data):
if self.state != 'RUNNING':
raise InvalidStateError(
f"Cannot process in {self.state} state. Must be RUNNING"
)
self.data = data
self.state = 'PROCESSING'
def finish(self):
if self.state not in ('RUNNING', 'PROCESSING'):
raise InvalidStateError(
f"Cannot finish from {self.state} state"
)
self.state = 'COMPLETE'
class InvalidStateError(Exception):
pass
# Raise with different messages based on condition
def create_resource(resource_type, name, quota):
"""Raise different errors based on why creation failed"""
if resource_type not in ['compute', 'storage', 'network']:
raise InvalidResourceType(f"Unknown resource type: {resource_type}")
if not name or len(name) < 3:
raise InvalidResourceName("Name must be at least 3 characters")
if quota.get(resource_type, 0) <= 0:
raise QuotaExceeded(
f"No quota available for {resource_type}. "
f"Limit: {quota.get(f'{resource_type}_limit', 0)}"
)
return _create_resource(resource_type, name, quota)# Avoid creating expensive exceptions
class ExpensiveException(Exception):
def __init__(self, data):
# Don't serialize large data in exception
self.data = data
super().__init__(f"Error processing {len(data)} items")
# Better: store only summary
class EfficientException(Exception):
def __init__(self, data_size, error_message):
self.data_size = data_size
self.error_message = error_message
super().__init__(error_message)
# Pre-check conditions to avoid exception creation
def validate_large_dataset(dataset):
# Check once before processing
if len(dataset) == 0:
raise EmptyDatasetError()
if len(dataset) > 1000000:
raise DatasetTooLargeError()
# Process without raising exceptions in loop
results = []
for item in dataset:
try:
results.append(process_item(item))
except ProcessingError:
continue # Skip bad items instead of accumulating exceptions
if not results:
raise NoValidItemsError()
return resultsResources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward