pylint
pylint copied to clipboard
False positive ``not-an-iterable`` with ``attr.s`` inheritance when base class is not an ``attr.s`` class
Steps to reproduce
Run pylint on the following file:
"""Minimal repro for pylint's not-an-iterable error.
"""
from typing import List
import attr
class Model(object):
"""Basic model showing pylint's not-an-iterable error.
"""
@classmethod
def attributes(cls):
# type: () -> List[str]
"""Get attributes of a model.
"""
# The iteration here triggers the warning
return [a.name for a in attr.fields(cls)]
@attr.s
class Basic(Model):
"""Basic model that's inherited, showing pylint's not-an-iterable error.
"""
hello = attr.ib() # type: str
assert Basic.attributes() == "hello"
Current behavior
An error (not-an-iterable) is found:
pylint_reproducer.py:18:32: E1133: Non-iterable value attr.fields(cls) is used in an iterating context (not-an-iterable)
Expected behavior
No errors are raised, as the code is valid and works.
pylint --version output
pylint 2.3.1
astroid 2.2.5
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)]
Adding an @attr.s on the base Model class works in the example above -- it seems to be a case of pylint's logic for attrs magic, not considering base classes somehow.
FWIW, my actual Model class has __init__() so I am doing attr.s(init=False).
Thanks for the report!
I am seeing issues with inheritance in the following code:
from abc import ABC
import attr
@attr.s(auto_attribs=True)
class AttrBase:
member: int
class AttrSub(AttrBase):
pass
if __name__ == "__main__":
attr_sub = AttrSub(1)
print(attr_sub.member)
Pylint results:
> pylint --disable=all --enable=no-member .\test.py
************* Module test
test.py:17:10: E1101: Instance of 'AttrSub' has no 'member' member (no-member)
Pylint version:
pylint 2.5.3
astroid 2.4.2
Python 3.7.4
Should I report it as a separate issue?
Possibly related to https://github.com/PyCQA/astroid/issues/1330 in astroid 2.10
When we infer attr.fields in the unannotated base class, we get nodes.None: attrs = getattr(cls, "__attrs_attrs__", None). If we annotate the base class, we infer an iterable.
We could check to see if any child classes of the class being checked have the annotation, but this would fail if the parent and child classes are in separate files.
Also note that in the example above, calling Model.attributes() raises attr.exceptions.NotAnAttrsClassError: <class '__main__.Model'> is not an attrs-decorated class.
This is definitely a false positive but I do not immediately see a solution and my suggestion is to annotate the base class as well.