1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
from dataclasses import dataclass
from mypy.nodes import CallExpr, NameExpr, OpExpr
from refurb.checks.common import extract_binary_oper, is_equivalent
from refurb.error import Error
from refurb.settings import Settings
@dataclass
class ErrorInfo(Error):
"""
`isinstance()` and `issubclass()` both take tuple arguments, so instead of
calling them multiple times for the same object, you can check all of them
at once:
Bad:
```
if isinstance(num, float) or isinstance(num, int):
pass
```
Good:
```
if isinstance(num, (float, int)):
pass
```
Note: In Python 3.10+, you can also pass type unions as the second param to
these functions:
```
if isinstance(num, float | int):
pass
```
"""
name = "use-isinstance-issubclass-tuple"
code = 121
categories = ("python310", "readability")
def check(node: OpExpr, errors: list[Error], settings: Settings) -> None:
match extract_binary_oper("or", node):
case (
CallExpr(callee=NameExpr() as lhs, args=lhs_args),
CallExpr(callee=NameExpr() as rhs, args=rhs_args),
) if (
lhs.fullname == rhs.fullname
and lhs.fullname in {"builtins.isinstance", "builtins.issubclass"}
and len(lhs_args) == 2
and is_equivalent(lhs_args[0], rhs_args[0])
):
type_args = "y | z" if settings.get_python_version() >= (3, 10) else "(y, z)"
errors.append(
ErrorInfo.from_node(
lhs_args[1],
f"Replace `{lhs.name}(x, y) or {lhs.name}(x, z)` with `{lhs.name}(x, {type_args})`", # noqa: E501
)
)
|