Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False positive non-parent-init-called with base inheriting Generic and Protocol #3505

Closed
mthuurne opened this issue Apr 21, 2020 · 10 comments
Closed

Comments

@mthuurne
Copy link
Contributor

Steps to reproduce

Run PyLint on:

from typing import Generic, Protocol, TypeVar

T = TypeVar('T')

class PriorityProtocol(Protocol):
    def getPriority(self) -> int:
        ...

class Base(PriorityProtocol, Generic[T]):
    def getPriority(self) -> int:
        return 0

class Sub(Base[T]):
    def __init__(self) -> None:
        Base.__init__(self)

Note that typing.Protocol is new in Python 3.8. In older Python versions you can get it from the typing_extensions package.

Current behavior

W0233: __init__ method from a non direct base class 'Base' is called (non-parent-init-called)

Expected behavior

No warning is issued.

pylint --version output

pylint 2.4.4
astroid 2.3.3
Python 3.8.2 (default, Mar 05 2020, 18:58:42) [GCC]

Workaround

Using super instead of explicitly calling the base class __init__ avoids the warning.

@PCManticore
Copy link
Contributor

PCManticore commented Apr 22, 2020

Thanks for the report. It seems pylint cannot deduce the base classes properly which is why it fails to see the class being called as part of the ancestors when checking if it can emit non-parent-init-called
https://github.com/PyCQA/pylint/blob/09999a60be90a0bfc025a5218e96fb5f7b1a7f36/pylint/checkers/classes.py#L1656

@mthuurne
Copy link
Contributor Author

mthuurne commented Apr 24, 2020

I have a proof of concept that fixes this and also #3131, by having infer_subscript() return value if it inherits from typing.Generic. I'll try and make a clean patch from it.

Edit: Something fishy is going on: what makes the test case pass is a side effect of calling is_subtype_of() on the context, not the actual value returned from infer_subscript().

@jamesbraza
Copy link

jamesbraza commented Jun 8, 2020

I am just hitting this false positive as well, nice work @mthuurne .

I would like to add that inheriting from typing.Protocol or using multiple inheritance is not part of the false positive. It's triggered by inheriting from a typing.Generic.


Please see the below sample code: false_pos_w0233_generic_init.py

from typing import Generic, TypeVar


T = TypeVar("T")  # pylint: disable=invalid-name


class Base:
    pass


class Mixin(Generic[T]):
    pass


class Child1(Base, Mixin[float]):
    def __init__(self):
        Base.__init__(self)
        Mixin.__init__(self)  # Raises W0233 (non-parent-init-called)


class Child2(Base, Mixin[float]):
    def __init__(self):
        super().__init__()
        super(Child2, self).__init__()  # Doesn't raise W0233


class Child3(Mixin[float]):
    def __init__(self):
        Mixin.__init__(self)  # Raises W0233 (non-parent-init-called)

And the pylint output:

************* Module false_pos_w0233_generic_init
/path/to/false_pos_w0233_generic_init.py:18:8: W0233: __init__ method from a non direct base class 'Mixin' is called (non-parent-init-called)
/path/to/false_pos_w0233_generic_init.py:29:8: W0233: __init__ method from a non direct base class 'Mixin' is called (non-parent-init-called)

Versions

pylint 2.5.2
astroid 2.4.1
Python 3.6.5 (default, Mar 10 2020, 12:09:24)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)]

@cdce8p
Copy link
Member

cdce8p commented Apr 10, 2021

This seems to be fixed with astroid 2.5.3, probably fixed by pylint-dev/astroid#931.
If the false-positive is still present, please write and I'll reopen this issue.

@cdce8p cdce8p closed this as completed Apr 10, 2021
@jamesbraza
Copy link

As far as my false_pos_w0233_generic_init.py, the false positive no longer occurs. Nice!

@demberto
Copy link

@cdce8p Not yet

from typing import Any, Protocol, runtime_checkable, TypeVar

T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)

@runtime_checkable
class ROProperty(Protocol[T_co]):
    def __get__(self, instance: Any, owner: Any = None) -> T_co | None:
        ...

@runtime_checkable
class RWProperty(ROProperty[T], Protocol):
    def __set__(self, instance: Any, value: T):
        ...

class NamedPropMixin:
    ...

class KWProp(NamedPropMixin, RWProperty[T]):  # E0239: Inheriting 'RWProperty[T]', which is not a class. (inherit-non-class)
    ...

@cdce8p
Copy link
Member

cdce8p commented Jan 27, 2023

@cdce8p Not yet

Could you check again? I just tried reproducing it with the current main but everything looks fine. Alternatively, you could try pylint==2.16.0b1 if that's a bit easier.

@mheripsos
Copy link

I'm still seeing this. See this code:

class ParentA[I: int]():
    def __init__(self) -> None:
        # imagine more stuff was done here
        pass

class Child1[I: int](ParentA[I]):
    def __init__(self, arg: I) -> None:
        ParentA.__init__(self)
        self._arg = arg

class Child2[I: int](ParentA[I]):
    def __init__(self) -> None:
        ParentA[I].__init__(self)

Child1 generates a lint in its __init__ here:

    {
        "type": "warning",
        "module": "test",
        "obj": "Child1.__init__",
        "line": 8,
        "column": 8,
        "endLine": 8,
        "endColumn": 24,
        "path": "test.py",
        "symbol": "non-parent-init-called",
        "message": "__init__ method from a non direct base class 'ParentA' is called",
        "message-id": "W0233"
    }

This can be resolved (w.r.t. pylint) with the syntax in Child2 above, which partially (syntactically) makes sense in that we specify the generic parameter. However, this is actually a run-time error:

>>> from test import Child2 
>>> ch = Child2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "///test.py", line 13, in __init__
    ParentA[I].__init__(self)
TypeError: _GenericAlias.__init__() missing 1 required positional argument: 'args'

Parent[I], is not the specified generic, but actually references some other class above apparently (_GenericAlias).

I'm using the VSCode extension, which specifies:

A Visual Studio Code extension with support for the Pylint linter. This extension ships with pylint=3.0.2.

@cdce8p
Copy link
Member

cdce8p commented Sep 13, 2024

This extension ships with pylint=3.0.2.

@mheripsos Unfortunately this is quite an old version. Please try the latest release 3.2.7. Wth the VSCode extension I believe you can pip install pylint and add pylint.importStrategy: "fromEnvironment" to your config.
https://marketplace.visualstudio.com/items?itemName=ms-python.pylint#settings

@mheripsos
Copy link

Excellent, that immediately resolved it. I was still studying this and seeing some truly bizarre behavior I was going to follow up with, but it seems like you guys have got it figured out with the new versions. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants