fractions: round() Without ndigits Returns int, Not Fraction (tested)
round() on a Fraction looks like it should just round the value and hand back another Fraction. We measured what actually comes back with and without an explicit ndigits argument, and the return type silently changes depending on which form you use -- even though the numeric result is identical.
1. round(fraction) with no second argument returns a plain int
from fractions import Fraction
f = Fraction(5, 2)
r1 = round(f)
print(r1, type(r1))
2 <class 'int'>
Measured: round(Fraction(5, 2)) returns the plain Python int 2, not a Fraction. Fraction's tie-breaking is also round-half-to-even here, same as the builtin round() on floats.
2. round(fraction, 0) returns a Fraction instead -- same value, different type
from fractions import Fraction
f = Fraction(5, 2)
r2 = round(f, 0)
print(r2, type(r2))
print(r2 == round(f))
2 <class 'fractions.Fraction'>
True
Measured: passing an explicit ndigits=0 returns a Fraction (Fraction(2, 1), displayed as 2), not an int. It's numerically equal to the no-argument result, but the type differs -- type(round(f)) is not type(round(f, 0)) even though round(f) == round(f, 0).
3. The split follows Python's general round() protocol, not a fractions-specific quirk
from fractions import Fraction
for val in (Fraction(7, 2), Fraction(-5, 2)):
print(val, "->", round(val), type(round(val)).__name__,
"|", round(val, 0), type(round(val, 0)).__name__)
7/2 -> 4 int | 4 Fraction
-5/2 -> -2 int | -2 Fraction
Measured: the split holds for every value and sign we tried: round(x) with no ndigits always returns int, round(x, n) with an explicit ndigits (including 0) always returns Fraction. This matches Python's documented __round__ protocol for any numbers.Real type -- round(x) is specified to return an Integral, while round(x, n) returns the same type as x -- so the same split applies to Decimal and float too, but it's easy to hit by surprise on Fraction specifically because both forms look like "just round it" and both commonly appear mid-expression, e.g. round(total) added during debugging versus round(total, 0) copy-pasted from code that rounds to other precisions. If downstream code calls .limit_denominator(), compares .numerator/.denominator, or otherwise relies on a Fraction interface, swapping in the zero-argument form of round() breaks it with an AttributeError even though the printed value looks unchanged. Use round(f, 0) (or Fraction(round(f))) when a Fraction result is required, and the bare round(f) only when an int is actually wanted.
Frequently asked questions
Does round() on a Fraction return a Fraction?
Only if you pass an explicit ndigits argument. We measured round(Fraction(5, 2)) returning the plain int 2, while round(Fraction(5, 2), 0) returns the Fraction 2 -- same value, different type.
Why does round(Fraction(5, 2), 0) have a different type than round(Fraction(5, 2))?
Python's round() protocol specifies that the no-argument form returns an Integral (int), while the ndigits form returns the same type as the input. We confirmed this split holds for every Fraction value and sign we tested, including negatives.
Why did my code break with AttributeError after I added round() to a Fraction calculation?
If you called round(f) without an ndigits argument, the result is a plain int, which doesn't have Fraction-only attributes like .numerator, .denominator, or methods like .limit_denominator(). Use round(f, 0) instead if you need to keep it a Fraction.
Is this round() type-switching specific to Fraction?
No -- it follows the general __round__ protocol used by any numbers.Real type, including Decimal and float. It's just easy to hit by surprise on Fraction because both round(f) and round(f, 0) look interchangeable and both commonly appear in the same code paths.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in fractions module: called round() on the same Fraction value with no second argument, with ndigits=0, and with a negative Fraction, and compared the return type and value in each case. PlainHeaders is our extension; Python's fractions module is built in.