pylint icon indicating copy to clipboard operation
pylint copied to clipboard

false positive: unsupported-membership-test

Open larsch opened this issue 6 years ago • 10 comments

Steps to reproduce

# pylint: disable=missing-docstring,too-few-public-methods

class MyClass:
    _all = None

    @classmethod
    def all(cls):
        if not cls._all:
            cls._all = find_all()
        return cls._all

    @classmethod
    def exist(cls, number):
        return number in cls.all()

def find_all():
    return [1, 2, 3]


if __name__ == '__main__':
    assert MyClass.exist(2)

Current behavior

************* Module clsmemb
clsmemb.py:14:25: E1135: Value 'cls.all()' doesn't support membership test (unsupported-membership-test)

Expected behavior

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

pylint --version output

pylint 2.3.1
astroid 2.2.5
Python 3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]

larsch avatar Aug 05 '19 13:08 larsch

This looks like an issue that could be solved with better control flow inference, so that pylint could know that all could be either None or some other iterable value.

PCManticore avatar Aug 06 '19 13:08 PCManticore

I'm not sure if it's the same issue or a separate one, but I'm also getting this false positive with subprocess.check_output on pylint 2.3.1 and astroid 2.2.5 (old Python version, though - 3.5.3 on Debian 9):

import subprocess

bytes_data = subprocess.check_output(['echo', 'hello']);
text_data = subprocess.check_output(['echo', 'hello'], universal_newlines=True);

print(b'bytes' in bytes_data) # Prints False
print('text' in text_data)    # Prints False
'text' in bytes_data   # Runtime TypeError
b'bytes' in text_data  # Runtime TypeError
$ pylint -E check_pylint_e1135.py
************* Module check_pylint_e1135
check_pylint_e1135.py:6:18: E1135: Value 'bytes_data' doesn't support membership test (unsupported-membership-test)
check_pylint_e1135.py:7:16: E1135: Value 'text_data' doesn't support membership test (unsupported-membership-test)
check_pylint_e1135.py:8:10: E1135: Value 'bytes_data' doesn't support membership test (unsupported-membership-test)
check_pylint_e1135.py:9:12: E1135: Value 'text_data' doesn't support membership test (unsupported-membership-test)

ncoghlan avatar Aug 21 '19 07:08 ncoghlan

Thanks for that report @ncoghlan It's probably a separate issue which we can resolve easier with a brain transform https://github.com/PyCQA/astroid/tree/master/astroid/brain so it's probably better to move the report to a separate issue.

PCManticore avatar Aug 22 '19 13:08 PCManticore

Also bumped into this. Even simpler example to reproduce:

from typing import Optional, Tuple

prop = None  # type: Optional[Tuple]

exists = prop and 'a' in prop

Out:

violation.py:5:27: E1135: Value 'prop' doesn't support membership test (unsupported-membership-test)

tuukkamustonen avatar Oct 08 '19 09:10 tuukkamustonen

Equally adapting the code from the example above also generates a false positive for unsubscriptable-object in both Python 2, and Python 3 on latest version.

Python 2 & Pylint 1.6.5:

from typing import Optional

prop = None  # type: Optional[dict]

if prop and 'a' in prop:
    thing = prop['a']

Python 3 & Pylint 2.5.3:

from typing import Optional

prop: Optional[dict] = None

if prop and 'a' in prop:
    thing = prop['a']

Jamie- avatar Jun 19 '20 15:06 Jamie-

It might be worth to add that the pylint-issues reported from these minimal examples might be caused by the fixed assignment of None, in which case the type of prop can be inferred as NoneType and hence the warnings are correct.

from secrets import choice
from typing import Optional, Tuple

prop = choice([None, (None, )])  # type: Optional[Tuple]

exists = prop and 'a' in prop

does not produce a pylint issue.

Edit: For those landing here from a search engine: The issue from the initial example can be avoided by initializing _all = [] and avoiding None if there is no need to distinguish between the two values.

wamserma avatar Jul 26 '21 19:07 wamserma

Another example, this time with a loop and TypedDict:

from typing import Optional, TypedDict

class ADict(TypedDict):
    a: int

x: Optional[ADict] = None
while x is None or 'a' in x:
    print('test')
    # `x` gets assigned in the loop.

tibbe avatar Dec 24 '22 07:12 tibbe

Also bumped into this. Even simpler example to reproduce:

from typing import Optional, Tuple

prop = None  # type: Optional[Tuple]

exists = prop and 'a' in prop

Out:

violation.py:5:27: E1135: Value 'prop' doesn't support membership test (unsupported-membership-test)

I'm having this exact same problem for both unsupported-assignment-operation and unsupported-membership-test. Has there been any movement on getting this fixed? Looks to be a pretty old bug/FP

akusei avatar Apr 27 '23 00:04 akusei

I believe my issue is the same. I'm running into it here:

I get the error from line 7 in this abbreviated code...

class MaintainedModel(Model):
    class_label_filters: Optional[List[str]] = None
    def __init__(self, *args, **kwargs):
        self.label_filters = self.class_label_filters

    def update_decorated_fields(self):
        if self.label_filters is not None and "name" in self.label_filters:
            ...

Is there some way I should change my code to prevent the error? Funny thing is, I wasn't getting the error until I made an innocuous refactor of the code (to change global_label_filters to a class attribute (class_label_filters) with a type hint. There are other such variables without the error and the code works as intended...

hepcat72 avatar Aug 23 '23 13:08 hepcat72

Another recent example

from pydantic.v1.dataclasses import BaseModel, Field

class Foo(BaseModel):
    my_list: list[str] = Field(default_factory=list)

The above started failing after a un-related refactor on pydantic v2 from my_list: list[str] = [] to use default_factory instead(to avoid a mutable default list)

'my_list' doesn't support membership test (unsupported-membership-test)

The type hints still specify it as a list.

sidmitra avatar Aug 08 '24 00:08 sidmitra

Hi All! I ran into this (or something very similar) yesterday, when I tried to perform lazy initialization on some dicts. Here is a simplified example, I guess it is the same issue.

Example code:

# pylint: disable=C0114, C0115, R0903

####
# Case 1 (no class involved, works).
da: dict[str, str] | None = None # None at the beginning

da = {'a': 'Hello'} # Lazy initialization on demand

if 'a' in da: # OK
    print(da['a']) # OK
da['a'] = "Hi" # OK

####
# Case 2 (class involved, error).
class MyClass:
    db: dict[str, str] | None = None # None at the beginning

MyClass.db = {'b': 'World'} # Lazy initialization on demand

if 'b' in MyClass.db: # E1135
    print(MyClass.db['b']) # E1136
MyClass.db['b'] = 'America' # E1137
####

pylint result:

example.py:20:10: E1135: Value 'MyClass.db' doesn't support membership test (unsupported-membership-test)
example.py:21:10: E1136: Value 'MyClass.db' is unsubscriptable (unsubscriptable-object)
example.py:22:0: E1137: 'MyClass.db' does not support item assignment (unsupported-assignment-operation)

pylint version (running on Ubuntu 22.04 LTS 64-bit):

pylint 3.2.7
astroid 3.2.4
Python 3.10.12 (main, Jul 29 2024, 16:56:48) [GCC 11.4.0]

The two main points of this example are probably:

  1. As @hepcat72 noted, class vs. no class does seem to make a difference in some (or all?) cases.
  2. The issue is not just about E1135, other error codes are also involved.

To answer your question @hepcat72 (I know it's fairly outdated, but may still be relevant to you or to others): The way I see it now, it is a pylint issue, I'd leave the code as is and wait for the pylint fix to arrive.

If this is the same issue (is it?), should we update the title? This is not just about unsupported membership test (E1135) then...

davidgnx avatar Sep 03 '24 06:09 davidgnx

Hi!

Like in all the issues about the false positive cases for the not-an-iterable rule (ex. https://github.com/pylint-dev/pylint/issues/701), Pylint here tries again to check static typing of the objects, while it is not one of its main intended functionality, and so it does it badly in a lot of cases.

Here is my false positive case (do not judge the stupidity of this code, it just shows the problems in a simple case):

class A:

    att: List[int] | None = None

    @staticmethod
    def f():
        A.att = [1, 2, 3]

        assert A.att is not None

        for i in A.att: # ERROR: E1133:not-an-iterable
            print(i)

        if any(value in A.att for value in A.att): # 2 ERRORS: E1135:unsupported-membership-test on the first A.att, E1133:not-an-iterable on the second A.att
            print("True")

The same way I said in the 701 issue: you should better delete this kind of rule. Pylint is not a static typing linter, and so it shouldn't do this kind of checks because it is too complex for its goal, particularly when the issues are still open several years after their first notification. If the Pylint developers still want to fix these rules about typing, so then Pylint must become a static typing linter.

vtgn avatar Dec 12 '24 01:12 vtgn