Skip to content

Commit de6e6fa

Browse files
Implement too-many-positional-arguments (#9842)
1 parent 83ade13 commit de6e6fa

27 files changed

+138
-45
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class FiveArgumentMethods:
2+
"""The max positional arguments default is 5."""
3+
4+
def take_five_args(self, a, b, c, d, e): # [too-many-positional-arguments]
5+
pass
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Positional arguments work well for cases where the the use cases are
2+
self-evident, such as unittest's ``assertEqual(first, second, msg=None)``.
3+
Comprehensibility suffers beyond a handful of arguments, though, so for
4+
functions that take more inputs, require that additional arguments be
5+
passed by *keyword only* by preceding them with ``*``:
6+
7+
.. code-block:: python
8+
9+
def make_noise(self, volume, *, color=noise.PINK, debug=True):
10+
...
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class FiveArgumentMethods:
2+
"""The max positional arguments default is 5."""
3+
4+
def take_five_args(self, a, b, c, d, *, e=False):
5+
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[MESSAGES CONTROL]
2+
disable=too-many-arguments

doc/data/messages/t/too-many-positional/details.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

doc/user_guide/checkers/features.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,8 @@ Design checker Messages
437437
simpler (and so easier to use) class.
438438
:too-many-locals (R0914): *Too many local variables (%s/%s)*
439439
Used when a function or method has too many local variables.
440-
:too-many-positional (R0917): *Too many positional arguments in a function call.*
441-
Will be implemented in https://github.com/pylint-
442-
dev/pylint/issues/9099,msgid/symbol pair reserved for compatibility with
443-
ruff, see https://github.com/astral-sh/ruff/issues/8946.
440+
:too-many-positional-arguments (R0917): *Too many positional arguments (%s/%s)*
441+
Used when a function has too many positional arguments.
444442
:too-many-public-methods (R0904): *Too many public methods (%s/%s)*
445443
Used when class has too many public methods, try to reduce this to get a
446444
simpler (and so easier to use) class.

doc/user_guide/configuration/all-options.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,13 @@ Standard Checkers
765765
**Default:** ``7``
766766

767767

768+
--max-positional-arguments
769+
""""""""""""""""""""""""""
770+
*Maximum number of positional arguments for function / method.*
771+
772+
**Default:** ``5``
773+
774+
768775
--max-public-methods
769776
""""""""""""""""""""
770777
*Maximum number of public methods for a class (see R0904).*
@@ -822,6 +829,8 @@ Standard Checkers
822829
823830
max-parents = 7
824831
832+
max-positional-arguments = 5
833+
825834
max-public-methods = 20
826835
827836
max-returns = 6

doc/user_guide/messages/messages_overview.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ All messages in the refactor category:
539539
refactor/too-many-instance-attributes
540540
refactor/too-many-locals
541541
refactor/too-many-nested-blocks
542-
refactor/too-many-positional
542+
refactor/too-many-positional-arguments
543543
refactor/too-many-public-methods
544544
refactor/too-many-return-statements
545545
refactor/too-many-statements

doc/whatsnew/fragments/9099.new_check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Added `too-many-positional-arguments` to allow distinguishing the configuration for too many
2+
total arguments (with keyword-only params specified after `*`) from the configuration
3+
for too many positional-or-keyword or positional-only arguments.
4+
5+
As part of evaluating whether this check makes sense for your project, ensure you
6+
adjust the value of `--max-positional-arguments`.
7+
8+
Closes #9099

examples/pylintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ max-statements=50
319319
# Minimum number of public methods for a class (see R0903).
320320
min-public-methods=2
321321

322+
# Minimum number of public methods for a class (see R0903).
323+
max-positional-arguments=5
322324

323325
[EXCEPTIONS]
324326

examples/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ max-statements = 50
281281
# Minimum number of public methods for a class (see R0903).
282282
min-public-methods = 2
283283

284+
# Maximum number of positional arguments (see R0917).
285+
max-positional-arguments = 5
286+
284287
[tool.pylint.exceptions]
285288
# Exceptions that will emit a warning when caught.
286289
overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]

pylint/checkers/design_analysis.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from pylint.checkers import BaseChecker
1818
from pylint.checkers.utils import is_enum, only_required_for_messages
19+
from pylint.interfaces import HIGH
1920
from pylint.typing import MessageDefinitionTuple
2021

2122
if TYPE_CHECKING:
@@ -81,11 +82,9 @@
8182
"Used when an if statement contains too many boolean expressions.",
8283
),
8384
"R0917": (
84-
"Too many positional arguments in a function call.",
85-
"too-many-positional",
86-
"Will be implemented in https://github.com/pylint-dev/pylint/issues/9099,"
87-
"msgid/symbol pair reserved for compatibility with ruff, "
88-
"see https://github.com/astral-sh/ruff/issues/8946.",
85+
"Too many positional arguments (%s/%s)",
86+
"too-many-positional-arguments",
87+
"Used when a function has too many positional arguments.",
8988
),
9089
}
9190
)
@@ -311,6 +310,15 @@ class MisdesignChecker(BaseChecker):
311310
"help": "Maximum number of arguments for function / method.",
312311
},
313312
),
313+
(
314+
"max-positional-arguments",
315+
{
316+
"default": 5,
317+
"type": "int",
318+
"metavar": "<int>",
319+
"help": "Maximum number of positional arguments for function / method.",
320+
},
321+
),
314322
(
315323
"max-locals",
316324
{
@@ -525,6 +533,7 @@ def leave_classdef(self, node: nodes.ClassDef) -> None:
525533
"too-many-branches",
526534
"too-many-arguments",
527535
"too-many-locals",
536+
"too-many-positional-arguments",
528537
"too-many-statements",
529538
"keyword-arg-before-vararg",
530539
)
@@ -536,13 +545,20 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
536545
self._returns.append(0)
537546
# check number of arguments
538547
args = node.args.args + node.args.posonlyargs + node.args.kwonlyargs
548+
pos_args = node.args.args + node.args.posonlyargs
539549
ignored_argument_names = self.linter.config.ignored_argument_names
540550
if args is not None:
541551
ignored_args_num = 0
542552
if ignored_argument_names:
543-
ignored_args_num = sum(
544-
1 for arg in args if ignored_argument_names.match(arg.name)
553+
ignored_pos_args_num = sum(
554+
1 for arg in pos_args if ignored_argument_names.match(arg.name)
555+
)
556+
ignored_kwonly_args_num = sum(
557+
1
558+
for arg in node.args.kwonlyargs
559+
if ignored_argument_names.match(arg.name)
545560
)
561+
ignored_args_num = ignored_pos_args_num + ignored_kwonly_args_num
546562

547563
argnum = len(args) - ignored_args_num
548564
if argnum > self.linter.config.max_args:
@@ -551,6 +567,16 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
551567
node=node,
552568
args=(len(args), self.linter.config.max_args),
553569
)
570+
pos_args_count = (
571+
len(args) - len(node.args.kwonlyargs) - ignored_pos_args_num
572+
)
573+
if pos_args_count > self.linter.config.max_positional_arguments:
574+
self.add_message(
575+
"too-many-positional-arguments",
576+
node=node,
577+
args=(pos_args_count, self.linter.config.max_positional_arguments),
578+
confidence=HIGH,
579+
)
554580
else:
555581
ignored_args_num = 0
556582
# check number of local variables

pylint/testutils/pyreverse.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class PyreverseConfig(
2525

2626
def __init__(
2727
self,
28+
*,
2829
mode: str = "PUB_ONLY",
2930
classes: list[str] | None = None,
3031
show_ancestors: int | None = None,

pylintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ max-attributes=11
433433
# Maximum number of statements in a try-block
434434
max-try-statements = 7
435435

436+
# Maximum number of positional arguments (see R0917).
437+
max-positional-arguments = 12
438+
436439
[CLASSES]
437440

438441
# List of method names used to declare (i.e. assign) instance attributes.

tests/functional/a/async_functions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ async def some_method(self):
2222
super(OtherClass, self).test() # [bad-super-call]
2323

2424

25-
# +1: [too-many-arguments,too-many-return-statements, too-many-branches]
25+
# +1: [line-too-long]
26+
# +1: [too-many-arguments, too-many-positional-arguments, too-many-return-statements, too-many-branches]
2627
async def complex_function(this, function, has, more, arguments, than,
2728
one, _, should, have):
2829
if 1:
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
redefined-builtin:5:0:5:14:next:Redefining built-in 'next':UNDEFINED
22
unused-argument:8:30:8:34:some_function:Unused argument 'arg2':HIGH
33
bad-super-call:22:8:22:31:Class.some_method:Bad first argument 'OtherClass' given to super():UNDEFINED
4-
too-many-arguments:26:0:26:26:complex_function:Too many arguments (10/5):UNDEFINED
5-
too-many-branches:26:0:26:26:complex_function:Too many branches (13/12):UNDEFINED
6-
too-many-return-statements:26:0:26:26:complex_function:Too many return statements (10/6):UNDEFINED
7-
dangerous-default-value:59:0:59:14:func:Dangerous default value [] as argument:UNDEFINED
8-
duplicate-argument-name:59:18:59:19:func:Duplicate argument name 'a' in function definition:HIGH
9-
disallowed-name:64:0:64:13:foo:"Disallowed name ""foo""":HIGH
10-
empty-docstring:64:0:64:13:foo:Empty function docstring:HIGH
4+
line-too-long:26:0:None:None::Line too long (104/100):UNDEFINED
5+
too-many-arguments:27:0:27:26:complex_function:Too many arguments (10/5):UNDEFINED
6+
too-many-branches:27:0:27:26:complex_function:Too many branches (13/12):UNDEFINED
7+
too-many-positional-arguments:27:0:27:26:complex_function:Too many positional arguments (9/5):HIGH
8+
too-many-return-statements:27:0:27:26:complex_function:Too many return statements (10/6):UNDEFINED
9+
dangerous-default-value:60:0:60:14:func:Dangerous default value [] as argument:UNDEFINED
10+
duplicate-argument-name:60:18:60:19:func:Duplicate argument name 'a' in function definition:HIGH
11+
disallowed-name:65:0:65:13:foo:"Disallowed name ""foo""":HIGH
12+
empty-docstring:65:0:65:13:foo:Empty function docstring:HIGH

tests/functional/ext/docparams/parameter/missing_param_doc_required_Google.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def test_finds_args_with_xref_type_google(named_arg, **kwargs):
383383

384384
def test_ignores_optional_specifier_google(
385385
param1, param2, param3=(), param4=[], param5=[], param6=True
386-
):
386+
): # pylint: disable=too-many-positional-arguments
387387
"""Do something.
388388
389389
Args:
@@ -411,7 +411,7 @@ def test_finds_multiple_complex_types_google(
411411
named_arg_eight,
412412
named_arg_nine,
413413
named_arg_ten,
414-
):
414+
): # pylint: disable=too-many-positional-arguments
415415
"""The google docstring
416416
417417
Args:

tests/functional/ext/docparams/parameter/missing_param_doc_required_Numpy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def my_func(
344344
named_arg_six,
345345
named_arg_seven,
346346
named_arg_eight,
347-
):
347+
): # pylint: disable=too-many-positional-arguments
348348
"""The docstring
349349
350350
Args

tests/functional/t/too/too_many_arguments.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# pylint: disable=missing-docstring,wrong-import-position,unnecessary-dunder-call
22

3-
def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): # [too-many-arguments]
3+
# +1: [too-many-arguments, too-many-positional-arguments]
4+
def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9):
45
return arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
56

67

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
too-many-arguments:3:0:3:19:stupid_function:Too many arguments (9/5):UNDEFINED
2-
too-many-arguments:36:0:36:9:name1:Too many arguments (6/5):UNDEFINED
1+
too-many-arguments:4:0:4:19:stupid_function:Too many arguments (9/5):UNDEFINED
2+
too-many-positional-arguments:4:0:4:19:stupid_function:Too many positional arguments (9/5):HIGH
3+
too-many-arguments:37:0:37:9:name1:Too many arguments (6/5):UNDEFINED

tests/functional/t/too/too_many_locals.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ def too_many_locals_function(): # [too-many-locals]
2929
args15 = args14 * 15
3030
return args15
3131

32-
def too_many_arguments_function(arga, argu, argi, arge, argt, args): # [too-many-arguments]
32+
# +1: [too-many-arguments, too-many-positional-arguments]
33+
def too_many_arguments_function(arga, argu, argi, arge, argt, args):
3334
"""pylint will complain about too many arguments."""
3435
arga = argu
3536
arga += argi
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
too-many-locals:4:0:4:12:function:Too many local variables (16/15):UNDEFINED
22
too-many-locals:12:0:12:28:too_many_locals_function:Too many local variables (16/15):UNDEFINED
3-
too-many-arguments:32:0:32:31:too_many_arguments_function:Too many arguments (6/5):UNDEFINED
3+
too-many-arguments:33:0:33:31:too_many_arguments_function:Too many arguments (6/5):UNDEFINED
4+
too-many-positional-arguments:33:0:33:31:too_many_arguments_function:Too many positional arguments (6/5):HIGH
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
class FiveArgumentMethods:
3+
"""The max positional arguments default is 5."""
4+
def fail1(self, a, b, c, d, e): # [too-many-arguments, too-many-positional-arguments]
5+
pass
6+
def fail2(self, a, b, c, d, /, e): # [too-many-arguments, too-many-positional-arguments]
7+
pass
8+
def okay1(self, a, b, c, d, *, e=True): # [too-many-arguments]
9+
pass
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
too-many-arguments:4:4:4:13:FiveArgumentMethods.fail1:Too many arguments (6/5):UNDEFINED
2+
too-many-positional-arguments:4:4:4:13:FiveArgumentMethods.fail1:Too many positional arguments (6/5):HIGH
3+
too-many-arguments:6:4:6:13:FiveArgumentMethods.fail2:Too many arguments (6/5):UNDEFINED
4+
too-many-positional-arguments:6:4:6:13:FiveArgumentMethods.fail2:Too many positional arguments (6/5):HIGH
5+
too-many-arguments:8:4:8:13:FiveArgumentMethods.okay1:Too many arguments (6/5):UNDEFINED

tests/functional/u/unexpected_special_method_signature.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Valid:
7474
def __new__(cls, test, multiple, args):
7575
pass
7676

77+
# pylint: disable-next=too-many-positional-arguments
7778
def __init__(self, this, can, have, multiple, args, as_well):
7879
pass
7980

0 commit comments

Comments
 (0)