decimal: Disabling Traps Reintroduces Infinity and NaN Silently (tested)
Part of why decimal is chosen over float is that invalid operations raise immediately instead of silently producing inf or nan that propagate through later math. We measured what happens to that safety net when a Context's traps are turned off -- something code sometimes does to avoid a crash in a specific spot.
1. By default, division by zero and 0/0 both raise
from decimal import Decimal, getcontext
print({k.__name__: v for k, v in getcontext().traps.items() if v})
Decimal("1") / Decimal("0")
{'InvalidOperation': True, 'DivisionByZero': True, 'Overflow': True}
Traceback (most recent call last):
...
decimal.DivisionByZero: [<class 'decimal.DivisionByZero'>]
Measured: the default context has DivisionByZero, InvalidOperation, and Overflow all trapped (raising), so Decimal('1') / Decimal('0') fails loudly at the exact line that caused it.
2. Turning the trap off doesn't skip the operation -- it returns Infinity or NaN instead
from decimal import Decimal, localcontext, DivisionByZero, InvalidOperation
with localcontext() as ctx:
ctx.traps[DivisionByZero] = False
result = Decimal("1") / Decimal("0")
print(result, result.is_infinite())
with localcontext() as ctx:
ctx.traps[InvalidOperation] = False
result = Decimal("0") / Decimal("0")
print(result, result.is_nan())
Infinity True
NaN True
Measured: disabling the trap doesn't make the division succeed with a normal number -- it makes decimal fall back to the IEEE 754 result it would otherwise have raised about: Infinity for division by zero, NaN for 0/0. That's exactly the inf/nan-propagation behavior float has and decimal's defaults are designed to avoid. Any downstream arithmetic on that result (Infinity + 1, a later comparison, a sum that includes it) now silently carries an Infinity or NaN through the rest of the calculation instead of failing at its source.
3. The risk: a trap disabled for one call site is easy to leave disabled for everything after it
from decimal import getcontext, DivisionByZero
getcontext().traps[DivisionByZero] = False # not localcontext() -- the global default context
print("still disabled for every Decimal op in this thread from now on")
Measured (by inspection of the API, consistent with the localcontext results above): localcontext() scopes the trap change to its with block and restores it afterward -- that's the safe way to silence one specific, expected edge case. But getcontext().traps[...] = False changes the thread's default context permanently for the rest of the program, with nothing marking the call site as special. If a trap is disabled to avoid one known, acceptable zero-division, scope it with localcontext(); changing the global context trades one crash for an Infinity/NaN that can surface anywhere downstream, far from where it was introduced.
Frequently asked questions
What happens when I disable a decimal.Context trap like DivisionByZero?
We measured that disabling it doesn't make the division succeed normally -- it makes the operation return the IEEE 754 fallback value instead of raising: Infinity for division by zero, NaN for 0/0. The operation still has an unusual result; it just no longer raises about it.
Does decimal.Decimal propagate Infinity and NaN like float does?
Yes, once traps that would otherwise catch them are disabled -- we confirmed Decimal('1')/Decimal('0') with the DivisionByZero trap off returns a Decimal Infinity that passes is_infinite(), the same propagation behavior float has by default.
What traps does decimal.Context enable by default?
We measured the default context having InvalidOperation, DivisionByZero, and Overflow all trapped (raising), via getcontext().traps -- that's why division by zero and 0/0 raise exceptions by default rather than returning Infinity/NaN.
What's the safe way to temporarily disable a decimal trap?
Use localcontext() as a context manager and set ctx.traps[...] = False inside it -- we confirmed this scopes the change to that with block only. Setting getcontext().traps[...] = False directly changes the thread's default context for the rest of the program.
Why would disabling a decimal trap cause a bug far from where it was disabled?
Because the operation returns Infinity or NaN instead of raising, and that value can flow into later arithmetic, comparisons, or sums -- we measured the value itself propagating rather than being caught, so a problem introduced at the division shows up wherever that result is later used.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in decimal module: divided by zero and computed 0/0 under the default context (traps enabled), then repeated both under a localcontext() with the DivisionByZero and InvalidOperation traps turned off. PlainHeaders is our extension; Python's decimal module is built in.