Batu Lab NotesPractical developer guides

Trap Decimal division by zero in a validation helper

By Batu ยท English technical notes

Python can trap Decimal division by zero in a validation helper by using a copied local context with DivisionByZero trapped, then catching that specific exception and returning a structured failure. With that trap disabled, Decimal division by zero produces Infinity and sets a context flag; a validator that returns the arithmetic result could mistakenly treat that non-finite value as valid.

The literal input is a mapping with numerator 10 and denominator 0. The first calculation explicitly disables the DivisionByZero trap in a local context, making the unwanted Infinity visible without changing the surrounding context. The helper then creates another local context, enables ctx.traps[DivisionByZero], and performs the same operation. The signal raises DivisionByZero, which the helper converts into {'ok': False, 'error': 'zero_denominator'}. A nonzero denominator follows the normal path and returns a Decimal result.

Catching DivisionByZero intentionally covers only this contract violation. A production validator may also need explicit rules for invalid text, overflow, precision, rounding, and non-finite inputs. Do not use a broad except that turns unrelated programming errors into validation failures. localcontext, context traps, and DivisionByZero are standard-library features available in Python 3.4 and later.

The decimal signals and traps documentation explains how trapped signals raise exceptions, and the DivisionByZero documentation identifies this signal.

AI assistance disclosure: This article was prepared with AI assistance and checked with the shown synthetic example.

from decimal import Decimal, DivisionByZero, localcontext

record = {"numerator": Decimal("10"), "denominator": Decimal("0")}

with localcontext() as ctx:
    ctx.traps[DivisionByZero] = False
    unchecked = record["numerator"] / record["denominator"]

def validate_ratio(numerator, denominator):
    try:
        with localcontext() as ctx:
            ctx.traps[DivisionByZero] = True
            return {"ok": True, "value": numerator / denominator}
    except DivisionByZero:
        return {"ok": False, "error": "zero_denominator"}

result = validate_ratio(record["numerator"], record["denominator"])
valid_result = validate_ratio(Decimal("10"), Decimal("2"))

assert record == {"numerator": Decimal("10"), "denominator": Decimal("0")}
assert unchecked == Decimal("Infinity")
assert result == {"ok": False, "error": "zero_denominator"}
assert valid_result == {"ok": True, "value": Decimal("5")}

print("input:", record)
print("unchecked:", unchecked)
print("validated:", result)
print("nonzero denominator:", valid_result)
input: {'numerator': Decimal('10'), 'denominator': Decimal('0')}
unchecked: Infinity
validated: {'ok': False, 'error': 'zero_denominator'}
nonzero denominator: {'ok': True, 'value': Decimal('5')}