Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
7 changes: 7 additions & 0 deletions doc/whatsnew/fragments/10621.new_check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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.

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