If a function returns runtime-determined namedtuple, pylint will asume it always returns the same type.
Example code:
from collections import namedtuple
def build_named_tuple(name, dict_):
return namedtuple(name, dict_.keys())(**dict_)
obj1 = build_named_tuple('NamedTuple1', {'x': 1, 'y': 2})
obj2 = build_named_tuple('NamedTuple2', {'a': 1, 'b': 2})
print(obj1.x)
print(obj2.a)
pylint will report:
test.py:10:6: E1101: Instance of 'NamedTuple1' has no 'a' member; maybe 'x'? (no-member)
pylint version:
pylint 2.4.3
astroid 2.3.2
Python 3.7.4 (default, Oct 4 2019, 06:57:26)
[GCC 9.2.0]
Thanks for opening an issue. Unfortunately pylint is not capable of understanding the indirections of attributes from the build_named_tuple to the namedtuple constructor. Simply instantiating the namedtuples should not yield any error, as pylint can properly check what attributes it's supposed to have. This is simply a dynamic feature for which static analysis tools are not capable enough.
@PCManticore I think if pylint can't do the check correctly, it should not do it.
I feel OK if pylint does not check attribute accessing on build_named_tuple return value. But in this case errors are raised everywhere we use obj2. It is even impossible to solve this problem with # pylint: disable inline hints. The only choice is to disable this useful rule globally.
False negative is better than false positive I suppose.
You can use --ignored-classes or --generated-members to patch this up, as well as a local plugin for astroid that transforms build_named_tuple to whatever you like, loaded with --load-plugins. Regarding your point about pylint doing something it can't, I disagree. Even these limited checks were capable of finding bugs in various codebases I worked with.
OK, I should have used a better expression.
If pylint can't do the check perfectly, it should do it more conservatively.
If return type of a function depends on its parameters and they have changed, pylint should either re-analyze it again or consider the return type is "any", instead of assuming it still returns the same type.
--generated-members is an acceptable solution to me, but it looks a little weird. I have to write --generated-members=NamedTuple1, while the purpose is to disable checks on NamedTuple2. (in the real project their names are not that similar)
That's a fair point @liuzhe-lz Unfortunately pylint's inference engine, astroid, is quite old and does not have the concept of Any from the new type inference efforts in modern Python. Better type inference is something we want to have at some point, but given this project is maintained by volunteers, there's not much free time to contribute running around. I appreciate the feedback though, it's definitely a problematic behaviour.
Thanks for your reply.
Maybe I'll check if I can write a PR.
It would be neat to have the option of adding ignored-classes on a per-file basis. For instance, I have a namedtuple named Settings, and I create it dynamically from a dict. Naturally, every use of this class Settings.<attrib> will produce a no-member error. Yes, I can do --ignored-classes=Settings or even add Settings to .pylintrc. But what I'd really like is to put
# pylint: ignored-classes=Settings
in the source file where Settings is created... and then have every use of this particular class become pylint-kosher.
(This reflects a Stackoverflow question: https://stackoverflow.com/questions/50767469/pylint-ignored-classes-in-source-file)
Most helpful comment
It would be neat to have the option of adding
ignored-classeson a per-file basis. For instance, I have a namedtuple namedSettings, and I create it dynamically from adict. Naturally, every use of this classSettings.<attrib>will produce ano-membererror. Yes, I can do--ignored-classes=Settingsor even addSettingsto.pylintrc. But what I'd really like is to putin the source file where
Settingsis created... and then have every use of this particular class become pylint-kosher.(This reflects a Stackoverflow question: https://stackoverflow.com/questions/50767469/pylint-ignored-classes-in-source-file)