What causes Pylint no-member false positives and how to deal with this
One of the things you may encounter if you are using Pylint as part of your tooling is coming across warnings about a missing member. This is one of the most useful Pylint messages since it is almost always an error.
Consider the following:
"""Simple error that pylint will detect"""
class A:
def __init__(self, x, y):
self.x = x
# missing assigning y here!
a = A(1, 2)
print(a.x + a.y)
Running this through Pylint we get:
$ pylint a.py
************* Module a
a.py:1:0: C0114: Missing module docstring (missing-module-docstring)
a.py:1:0: C0103: Class name "A" doesn't conform to PascalCase naming style (invalid-name)
a.py:3:8: C0103: Attribute name "x" doesn't conform to snake_case naming style (invalid-name)
a.py:1:0: C0115: Missing class docstring (missing-class-docstring)
a.py:2:26: W0613: Unused argument 'y' (unused-argument)
a.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods)
a.py:6:0: C0103: Constant name "a" doesn't conform to UPPER_CASE naming style (invalid-name)
a.py:7:12: E1101: Instance of 'A' has no 'y' member; maybe 'x'? (no-member)
In this case Pylint will give you an error that y
is not a member of A
.
Automating the finding of such bugs in your tooling is very useful.
However since Python is so dynamic there's a whole bunch of ways in which you can dynamically define members of a class, and Pylint won't always catch these. Pylint is smart enough to not emit any errors in this particular case, which is correct since this program will run:
"""Case where pylint will warn you about attributes defined outside __init__"""
class B:
def __init__(self, x, y):
self.x = x
# missing assigning y here!
b = B(1, 2)
b.y = 2
print(b.x + b.y)
Running pylint on this gives you a helpful warning about an unused argument, but no errors since this code actually runs as expected.
$ pylint b.py
************* Module b
b.py:2:0: C0103: Class name "B" doesn't conform to PascalCase naming style (invalid-name)
b.py:4:8: C0103: Attribute name "x" doesn't conform to snake_case naming style (invalid-name)
b.py:8:0: C0103: Attribute name "y" doesn't conform to snake_case naming style (invalid-name)
b.py:2:0: C0115: Missing class docstring (missing-class-docstring)
b.py:3:26: W0613: Unused argument 'y' (unused-argument)
b.py:2:0: R0903: Too few public methods (0/2) (too-few-public-methods)
b.py:8:0: W0201: Attribute 'y' defined outside __init__ (attribute-defined-outside-init)
b.py:7:0: C0103: Constant name "b" doesn't conform to UPPER_CASE naming style (invalid-name)
--------------------------------------------------------------------
Your code has been rated at -3.33/10 (previous run: -5.00/10, +1.67)
But lets consider a more dynamic case where we use __getattr__
to dynamically check against attributes:
"""An example of getattr and pylint"""
class DynamicExample:
"""A class that has some dynamic lookups"""
def __init__(self, x, y):
self.x = x
self.y = y
def _sum(self):
"""Private implementation of summing"""
return self.x + self.y
def __getattr__(self, name):
if name == 'sum':
return self._sum()
# Maintain default behavior when the name doesn't match a special case
raise self.__getattribute__(name)
dyn = DynamicExample(1, 2)
print(dyn.sum)
print(dyn.bogus)
Running this we see that Pylint is now not able to deduce that dyn.bogus
is not a valid attribute. This is because Pylint doesn't have the ability to run arbitrary code to check its validity and __getattr__
turns this class into one which will determine an attribute at runtime.
$ pylint dynamic.py
************* Module dynamic
dynamic.py:5:8: C0103: Attribute name "x" doesn't conform to snake_case naming style (invalid-name)
dynamic.py:6:8: C0103: Attribute name "y" doesn't conform to snake_case naming style (invalid-name)
dynamic.py:2:0: R0903: Too few public methods (1/2) (too-few-public-methods)
dynamic.py:17:0: C0103: Constant name "dyn" doesn't conform to UPPER_CASE naming style (invalid-name)
------------------------------------------------------------------
Your code has been rated at 6.92/10 (previous run: 6.92/10, +0.00)
Since Pylint doesn't execute the code that it is scanning directly sometimes you'll see problems when dealing with libraries that use metaprogramming extensively to generate classes. When a library does dynamic attribute generation it can cause some false-positives and false-negatives when running Pylint, for example this happens with SQLAlchemy and the way it deals with sessions.
One way you can deal with this is to modify your .pylintrc
file to tell Pylint that this behavior is actually fine and you are just seeing a false-positive, in a Flask+SQLAlchemy project .pylintrc
might look something like this:
[TYPECHECK]
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=flask_sqlalchemy
# SQLAlchemy issues due to dynamic behavior
generated-members=db.session.add|db.session.commit|db.session.merge|db.session.query|db.session.flush
This will let you avoid those false positive messages.