Consider this code:
previous = None
for word in ['you', 'cannot', 'be', 'serious']:
if previous is not None:
print(previous[0], word)
previous = word
Here is the output (no surprises here):
$ python3 test.py
y cannot
c be
b serious
Checking it using pylint
gives the following:
$ pylint3 test.py
No config file found, using default configuration
************* Module test
C: 1, 0: Missing module docstring (missing-docstring)
C: 1, 0: Constant name "previous" doesn't conform to UPPER_CASE naming style (invalid-name)
E: 4,14: Value 'previous' is unsubscriptable (unsubscriptable-object)
--------------------------------------------------------------------
Your code has been rated at -4.00/10 (previous run: -4.00/10, +0.00)
As you can see, pylint
has complained about the expression previous[0]
based on the fact that previous
is initialised to None
, even though the statement cannot be reached when it has that value. Additionally, it has complained about the naming style on the basis that previous
is a constant, despite the assignment on the final line.
QUESTION: Is there any suitable configuration of pylint that avoids such false positives without introducing equally blatant false negatives, and if so, why is this not the default?
I am able for example to suppress the unsubscriptable-object
warning entirely, by creating a $HOME/.pylintrc
containing the output of pylint3 --generate-rcfile
, and editing it to add unsubscriptable-object
to the list of disabled tests (see under disable=
in section [MESSAGES CONTROL]
).
However, if I simply disable the test in this way, and then validate the following code:
"false negative" # silence docstring warning (fair enough)
print(None[0]) # this line will fail
it tells me:
$ pylint3 test2.py
Using config file /home/<myuser>/.pylintrc
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
So just disabling the test is not really the answer.
Version info:
$ pylint3 --version
Using config file /home/<myuser>/.pylintrc
pylint3 1.8.3,
astroid 1.6.0
Python 3.6.9 (default, Apr 18 2020, 01:56:04)
[GCC 8.4.0]
Platform: Ubuntu 18.04.4 LTS
Pylint is a static type checker and has all the limitations of that type of utility. It doesn't read your mind, it only reads your code. Its purpose is to tell you about suspicious-looking code, and sometimes you have to tell it to ignore certain things because you actually want to write the program that way.
If you expect Pylint to be a flawless code-Nazi that identifies your every mistake, never gets carried away and never misses anything, you will be disappointed. If you think of it as a friendly observer who can catch some of your errors before you run your program, it is quite powerful. I consider it an essential tool, as long as you don't ask too much of it.
You say that previous
is not a constant but that is not how Pylint uses the term. You declare previous
at the outermost indentation level of the module and it is therefore a global; Pylint considers the only valid use of globals is as constants. Hence the error.
To get pylint to accept any format for constants, use this line in the resource configuration file:
const-rgx=[A-Za-z0-9_]{1,30}$
That's what I have done since I found the default setting, which is
const-naming-style=UPPER_CASE
to be too restrictive.
The RC file is important, IMO. It allows you a lot of control over what Pylint does. If your RC file is named pylint.rc you invoke pylint with the flag --rcfile=pylint.rc
. You may have to provide a full path name depending on how your environment is set up.
With the configuration described, Pylint accepts this code without complaint:
previous = ''
for word in ['you', 'cannot', 'be', 'serious']:
if previous:
print(previous[0], word)
previous = word
It's the same logic, but is easier to understand because it makes clear that previous is a string. So Pylint has indeed helped you to write more readable code.
It is also sometimes necessary to tell Pylint to stop complaining about a particular line. In your example, if you really want to initialize previous to None, all you have to do is add an inline pragma, like this:
previous = None
for word in ['you', 'cannot', 'be', 'serious']:
if previous is not None:
print(previous[0], word) # pylint: disable=unsubscriptable-object
previous = word
This is a better solution that the one you tried because it defeats the error in one line only, and other occurrences of the same error will get flagged.