pylint
pylint copied to clipboard
False positive: overriding a generic `ParamSpec` method triggers `arguments-differ` (W0221)
Bug description
Consider the following pattern
example1.py
# pylint: disable=missing-docstring
class Base:
@classmethod
def apply(cls, *args, **kwargs):
return cls.forward("ctx", *args, **kwargs)
@staticmethod
def forward(ctx, *args, **kwargs):
raise NotImplementedError()
class Derived(Base):
@staticmethod
def forward(ctx, x, y):
del x, y
return ctx
Derived.apply(1, 2)
running pylint over this produces the following warnings:
************* Module example
example.py:14:4: W0221: Number of parameters was 3 in 'Base.forward' and is now 3 in overriding 'Derived.forward' method (arguments-differ)
example.py:14:4: W0221: Variadics removed in overriding 'Derived.forward' method (arguments-differ)
Now, this is somewhat understandable since the signature of Derived.forward is narrower than Base.forward, but conceptually, that is exactly how this pattern is supposed to be used (forward is supposed to only be called by apply and apply is supposed to directly forward its arguments).
I attempted to add some typing annotations using ParamSpec to make this relation more explicit:
example2.py
# pylint: disable=missing-docstring
from typing import ParamSpec, Generic, Any
P = ParamSpec("P")
class Base(Generic[P]):
@classmethod
def apply(cls, *args: P.args, **kwargs: P.kwargs) -> Any:
return cls.forward("ctx", *args, **kwargs)
@staticmethod
def forward(ctx: str, *args: P.args, **kwargs: P.kwargs) -> Any:
raise NotImplementedError()
class Derived(Base[int, int]):
@staticmethod
def forward(ctx: str, x: int, y: int) -> Any:
del x, y
return ctx
Derived.apply(1, 2)
But this still causes the same warnings.
By the way, mypy --strict accepts the version annotated with ParamSpec as well as this:
example3.py
# pylint: disable=missing-docstring
from typing import Any
class Base:
@classmethod
def apply(cls, *args: Any, **kwargs: Any) -> Any:
return cls.forward("ctx", *args, **kwargs)
@staticmethod
def forward(ctx: str, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
class Derived(Base):
@staticmethod
def forward(ctx: str, x: int, y: int) -> Any:
del x, y
return ctx
Derived.apply(1, 2)
Also, see issue https://github.com/pytorch/pytorch/issues/119475 for a real world example of this pattern.
Pylint version
pylint 3.0.3
astroid 3.0.3
Python 3.11.7 (main, Dec 4 2023, 18:10:11) [GCC 13.2.0]