Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/data/messages/c/consider-math-not-float/bad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
swag = float("inf") # [consider-math-not-float]
40 changes: 40 additions & 0 deletions doc/data/messages/c/consider-math-not-float/details.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
This is an extension check because the typing advantage could be fixed.

Regarding performance, float("nan") and float("inf") are slower than their counterpart math.inf and math.nan by a factor of 4 after the initial import of math.

.. code-block:: python

import math
import timeit

time_math_inf = timeit.timeit('math.nan', globals=globals(), number=10**8)
print(f'math.nan: {time_math_inf:.2f} seconds')

import timeit
time_inf_str = timeit.timeit('float("nan")', number=10**8)
print(f'float("nan"): {time_inf_str:.2f} seconds')

Result::

math.nan: 1.24 seconds
float("nan"): 5.15 seconds

But if we take the initial import into account it's worse.

.. code-block:: python

import timeit

time_math_inf = timeit.timeit('import math;math.nan', globals=globals(), number=10**8)
print(f'math.nan: {time_math_inf:.2f} seconds')

import timeit
time_inf_str = timeit.timeit('float("nan")', number=10**8)
print(f'float("nan"): {time_inf_str:.2f} seconds')

Result::

math.nan: 9.08 seconds
float("nan"): 5.33 seconds

So the decision depends on how and how often you need to use it and what matter to you.
3 changes: 3 additions & 0 deletions doc/data/messages/c/consider-math-not-float/good.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import math

swag = math.inf
2 changes: 2 additions & 0 deletions doc/data/messages/c/consider-math-not-float/pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[MAIN]
load-plugins=pylint.extensions.code_style
4 changes: 4 additions & 0 deletions doc/user_guide/checkers/extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ Code Style checker Messages
'typing.NamedTuple' uses the well-known 'class' keyword with type-hints for
readability (it's also faster as it avoids an internal exec call). Disabled
by default!
:consider-math-not-float (R6106): *Consider %smath.%s instead of %s*
Using math.inf or math.nan permits to benefit from typing and it is up to 4
times faster than a float call (after the initial import of math). This check
also catches typos in float calls as a side effect.


.. _pylint.extensions.comparison_placement:
Expand Down
8 changes: 4 additions & 4 deletions doc/user_guide/checkers/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -689,11 +689,11 @@ Match Statements checker Messages
:invalid-match-args-definition (E1902): *`__match_args__` must be a tuple of strings.*
Emitted if `__match_args__` isn't a tuple of strings required for match.
:too-many-positional-sub-patterns (E1903): *%s expects %d positional sub-patterns (given %d)*
Emitted when the number of allowed positional sub-patterns exceeds the
number of allowed sub-patterns specified in `__match_args__`.
Emitted when the number of allowed positional sub-patterns exceeds the number
of allowed sub-patterns specified in `__match_args__`.
:multiple-class-sub-patterns (E1904): *Multiple sub-patterns for attribute %s*
Emitted when there is more than one sub-pattern for a specific attribute in
a class pattern.
Emitted when there is more than one sub-pattern for a specific attribute in a
class pattern.
:match-class-bind-self (R1905): *Use '%s() as %s' instead*
Match class patterns are faster if the name binding happens for the whole
pattern and any lookup for `__match_args__` can be avoided.
Expand Down
1 change: 1 addition & 0 deletions doc/user_guide/messages/messages_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ All messages in the refactor category:
refactor/condition-evals-to-constant
refactor/confusing-consecutive-elif
refactor/consider-alternative-union-syntax
refactor/consider-math-not-float
refactor/consider-merging-isinstance
refactor/consider-refactoring-into-while-condition
refactor/consider-swap-variables
Expand Down
8 changes: 8 additions & 0 deletions doc/whatsnew/fragments/10621.new_check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Add a ``consider-math-not-float`` message. ``float("nan")`` and ``float("inf")`` are slower
than their counterpart ``math.inf`` and ``math.nan`` by a factor of 4 (notwithstanding
the initial import of math) and they are also not well typed when using mypy.
This check also catches typos in float calls as a side effect.

The :ref:`pylint.extensions.code_style` need to be activated for this check to work.

Refs #10621
40 changes: 38 additions & 2 deletions pylint/extensions/code_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from __future__ import annotations

import difflib
from typing import TYPE_CHECKING, TypeGuard, cast

from astroid import nodes
Expand Down Expand Up @@ -74,6 +75,13 @@ class CodeStyleChecker(BaseChecker):
"default_enabled": False,
},
),
"R6106": (
"Consider %smath.%s instead of %s",
"consider-math-not-float",
"Using math.inf or math.nan permits to benefit from typing and it is up "
"to 4 times faster than a float call (after the initial import of math). "
"This check also catches typos in float calls as a side effect.",
),
}
options = (
(
Expand Down Expand Up @@ -101,14 +109,42 @@ def open(self) -> None:
or self.linter.config.max_line_length
)

@only_required_for_messages("prefer-typing-namedtuple")
@only_required_for_messages("prefer-typing-namedtuple", "consider-math-not-float")
def visit_call(self, node: nodes.Call) -> None:
if self._py36_plus:
called = safe_infer(node.func)
if called and called.qname() == "collections.namedtuple":
if not called:
return
if called.qname() == "collections.namedtuple":
self.add_message(
"prefer-typing-namedtuple", node=node, confidence=INFERENCE
)
elif called.qname() == "builtins.float":
if (
node.args
and isinstance(node.args[0], nodes.Const)
and isinstance(node.args[0].value, str)
and any(
c.isalpha() and c.lower() != "e" for c in node.args[0].value
)
):
value = node.args[0].value.lower()
math_call: str
if "nan" in value:
math_call = "nan"
elif "inf" in value:
math_call = "inf"
else:
math_call = difflib.get_close_matches(
value, ["inf", "nan"], n=1, cutoff=0
)[0]
minus = "-" if math_call == "inf" and value.startswith("-") else ""
self.add_message(
"consider-math-not-float",
node=node,
args=(minus, math_call, node.as_string()),
confidence=INFERENCE,
)

@only_required_for_messages("consider-using-namedtuple-or-dataclass")
def visit_dict(self, node: nodes.Dict) -> None:
Expand Down
18 changes: 18 additions & 0 deletions tests/functional/ext/code_style/cs_use_math_not_float.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Functional test for consider-math-not-float."""

inf_float = float("inf") # [consider-math-not-float]
neg_inf_float = float('-inf') # [consider-math-not-float]
pos_inf_float = float("+inf") # [consider-math-not-float]
infinity_float = float("infinity") # [consider-math-not-float]
neg_infinity_float = float("-infinity") # [consider-math-not-float]
large_exp_float = float("1e1000")
neg_large_exp_float = float("-1e1000")
very_large_exp_float = float("2.5E9999")
invalid_inf_float = float("in") # [consider-math-not-float]
invalid_float_call = float("in", base=10) # [consider-math-not-float]
nan_float = float("nan") # [consider-math-not-float]
neg_nan_float = float("-nan") # [consider-math-not-float]
pos_nan_float = float("+nan") # [consider-math-not-float]
upper_nan_float = float("NaN") # [consider-math-not-float]
typo_nan_float = float("nani") # [consider-math-not-float]
other_typo_nan_float = float("nna") # [consider-math-not-float]
3 changes: 3 additions & 0 deletions tests/functional/ext/code_style/cs_use_math_not_float.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[MAIN]
load-plugins=pylint.extensions.code_style
enable=consider-math-not-float
13 changes: 13 additions & 0 deletions tests/functional/ext/code_style/cs_use_math_not_float.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
consider-math-not-float:3:12:3:24::Consider math.inf instead of float('inf'):INFERENCE
consider-math-not-float:4:16:4:29::Consider -math.inf instead of float('-inf'):INFERENCE
consider-math-not-float:5:16:5:29::Consider math.inf instead of float('+inf'):INFERENCE
consider-math-not-float:6:17:6:34::Consider math.inf instead of float('infinity'):INFERENCE
consider-math-not-float:7:21:7:39::Consider -math.inf instead of float('-infinity'):INFERENCE
consider-math-not-float:11:20:11:31::Consider math.inf instead of float('in'):INFERENCE
consider-math-not-float:12:21:12:41::Consider math.inf instead of float('in', base=10):INFERENCE
consider-math-not-float:13:12:13:24::Consider math.nan instead of float('nan'):INFERENCE
consider-math-not-float:14:16:14:29::Consider math.nan instead of float('-nan'):INFERENCE
consider-math-not-float:15:16:15:29::Consider math.nan instead of float('+nan'):INFERENCE
consider-math-not-float:16:18:16:30::Consider math.nan instead of float('NaN'):INFERENCE
consider-math-not-float:17:17:17:30::Consider math.nan instead of float('nani'):INFERENCE
consider-math-not-float:18:23:18:35::Consider math.nan instead of float('nna'):INFERENCE