Description
Summary
The fix for unnecessary-from-float
(FURB164) does not validate argument lists, so it can change the program’s behavior. It should be considered unsafe by default with a few exceptions based on type inference. When the original code is invalid, the fix should be suppressed. When the fixed code is invalid, the fix should be suppressed. When both are valid and the original uses keyword arguments, the fix should remove the keywords, because the recommended constructors do not use the same keywords. Decimal.from_float
has exactly one positional-only parameter. Fraction.from_decimal
has exactly one positional-or-keyword parameter, dec
. Fraction.from_float
has exactly one positional-or-keyword parameter, f
.
$ cat >furb164_1.py <<'# EOF'
from decimal import Decimal
from fractions import Fraction
try: print(Fraction.from_decimal(dec=Decimal("4.2")))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Fraction.from_decimal(Decimal("4.2"), 1))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Decimal.from_float(4.2, None))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Fraction.from_decimal(numerator=Decimal("4.2")))
except Exception as e: print(f"{type(e).__name__}: {e}")
# EOF
$ python furb164_1.py
21/5
TypeError: Fraction.from_decimal() takes 2 positional arguments but 3 were given
TypeError: Decimal.from_float() takes exactly one argument (2 given)
TypeError: Fraction.from_decimal() got an unexpected keyword argument 'numerator'
$ ruff --isolated check furb164_1.py --select FURB164 --preview --fix
Found 4 errors (4 fixed, 0 remaining).
$ python furb164_1.py
TypeError: Fraction.__new__() got an unexpected keyword argument 'dec'
TypeError: both arguments should be Rational instances
4.20000000000000017763568394002504646778106689453125
21/5
If the original expression raises a TypeError
, the fix can make it raise a different TypeError
. Although that is safe, there is probably a bug in the original code, so it is not clear what the user intended and it would be better to suppress the fix. It is still useful for the rule to raise a diagnostic. Examples:
$ cat >furb164_2.py <<'# EOF'
from decimal import Decimal
from fractions import Fraction
try: print(Decimal.from_float(f=4.2))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Fraction.from_decimal(Decimal("4.2"), 1))
except Exception as e: print(f"{type(e).__name__}: {e}")
# EOF
$ python furb164_2.py
TypeError: Decimal.from_float() takes no keyword arguments
TypeError: Fraction.from_decimal() takes 2 positional arguments but 3 were given
$ ruff --isolated check furb164_2.py --select FURB164 --preview --fix
Found 2 errors (2 fixed, 0 remaining).
$ python furb164_2.py
TypeError: 'f' is an invalid keyword argument for this function
TypeError: both arguments should be Rational instances
from_float
and from_decimal
are not redundant with the constructors: they validate the types of their inputs. The fix removes that validation, which can change behavior. Examples:
$ cat >furb164_3.py <<'# EOF'
from decimal import Decimal
from fractions import Fraction
try: print(Decimal.from_float("4.2"))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Fraction.from_decimal(4.2))
except Exception as e: print(f"{type(e).__name__}: {e}")
try: print(Fraction.from_float("4.2"))
except Exception as e: print(f"{type(e).__name__}: {e}")
# EOF
$ python furb164_3.py
TypeError: argument must be int or float
TypeError: Fraction.from_decimal() only takes Decimals, not 4.2 (float)
TypeError: Fraction.from_float() only takes floats, not '4.2' (str)
$ ruff --isolated check furb164_3.py --select FURB164 --preview --fix
Found 3 errors (3 fixed, 0 remaining).
$ python furb164_3.py
4.2
4728779608739021/1125899906842624
21/5
The fix should be marked unsafe except when the argument is known to be of a valid type. Decimal.from_float
and Fraction.from_float
accept int
, bool
, and float
. Fraction.from_decimal
accepts int
, bool
, and Decimal
. The fix is also safe when it simplifies a non-finite float as in Decimal.from_float(float("inf"))
to Decimal("inf")
. Ruff’s ResolvedPythonType
, typing::is_float
, and typing::is_int
may be useful here.
Version
ruff 0.12.1 (32c5418 2025-06-26)