
Why Pylint Is Both Useful and Unusable, and How You Can Actually Use It - itamarst
https://codewithoutrules.com/2016/10/19/pylint/
======
raymondh
FWIW, I like pyflakes because of its design philosophy:

""" Pyflakes makes a simple promise: it will never complain about style, and
it will try very, very hard to never emit false positives.

Pyflakes is also faster than Pylint or Pychecker. This is largely because
Pyflakes only examines the syntax tree of each file individually. As a
consequence, Pyflakes is more limited in the types of things it can check. """

~~~
gbog
I have pyflakes as a vim plugin but it is not enough on big projects, as it
will not catch import errors (which are the most common code-breaking errors)

~~~
masklinn
Yep pyflakes is fast, but the tradeoff is that it does relatively little and
can't do cross-file inspections or anything somewhat complex.

It's also even less well-documented, I have no idea if you can even create a
custom flake. pylint's got the advantage that as badly documented as it is
creating custom lints is supported and normal.

------
frobozz
> The real problem is the amount of output.

That's a nonsense argument. The output is proportional to the number of
violations. If you keep your project clean, then you'll have about a report of
about 100 lines showing the statistics, with a one-line message: "your code
has been rated at 10/10" at the end.

Of course, if you run it against a project that is full of violations, you'll
get lots of violation messages. If you use a linter or style checker or
whatever on a project that is full of violations, and it doesn't produce lots
of output, then the tool is rubbish.

The right thing to do is to use it from the start and either fix or explicitly
ignore any violations as they come up.

The approach described in this blog post is good for existing projects.
However, the linked examples disable all warnings and explicitly enable those
that the author has deemed important. It would be better to explicitly disable
unimportant ones.

~~~
raverbashing
But having classes without an __init__ method is not a violation. (Amongst
several other warnings, like number of methods, size of methods, etc)

Don't get me wrong, pylint is great, but at some point it seems those tools
begin to get opinionated instead of helping

~~~
frobozz
It's a violation of someone's coding standard, if it's not a violation of
yours, then add it to your exclusion list. The point of the __init__ warning
is that a class without a constructor might not really be a class at all.

Number and size of methods are both code smells that indicate that you might
be creating a God Objects or God functions. If there's a good reason for an
enormous function, then violations can be ignored on a case, project or user
basis. I'd prefer a tool that can warn about that sort of thing, to one that
can't.

Most of the limits are configurable, and set to reasonably generous defaults.
Methods per class is 20 and statements per method is 50.

~~~
jerf
This seems like one of those articles where nobody seems to have read past the
first couple of paragraphs. It doesn't matter how wonderful the warning about
things missing __init__ are if it's buried in a pile of other noise. The noise
is the problem, not the specific warnings.

And the author has _already provided_ a constructive solution to the problem
other than "not linting", which is to start with a whitelist and slowly crank
the linter up, providing instant value and a path to move forward to obtain
incrementally more value over time, instead of trying to crank the linter
down, which means a lot of work before you're provided with any value,
followed even then by, in the real world, probably staring at the list of
issues, getting overwhelmed, and deciding the code seems to be working fine
the way it is so why bother?

(Remember, we are not superhumans. None of us respond to a list of 20,000
style issues by dropping everything and fixing them all. This is not even
wrong; we can not operate like that, so there's no point in pretending that we
either do or should.)

~~~
gbog
First thing I do with a legacy code base is to commit it to git, then pass it
through pep8 and pylint to get a sense of its level of dirtyness, then run
autopep8 to fix all that can be autofixed. Then commit the autofix. Then
manually fix the remaining pep8 warnings after muting the ones that are too
many. Then run pylint on it. I'll mute some of the most offended violations,
and fix the others, so to have pyflakes and pylint run with zero warnings.
Then hook the linters in ci, so the dirtyness do not come back through the
window.

In this process, I may exclude a folder or two if they are non vital and have
a lot of dirty files. I think I have fixed code with nearly 10.000 violations
this way, it takes 3 hours max if you use an efficient editor. Then the code
is is _some_ shape that you can start working on.

------
wink
I'm also not a fan of pylint - and I just ran it against a small project, 812
LOC of Python.

The code was developed with help of the 'pep8' tool which only finds 12
offending lines we declared ok (all too long, but it's SQL where formatting
for readability is more important than 80 chars..) and wlthough pylint gives
it a 7.26/10 there's a TON of errors.

So yes, I could now spend a while blacklisting some rules, fixing some stuff,
but the defaults are just so bad I won't bother, as usual. Because I think the
shape of this code is pretty good.

~~~
gbog
I beg to differ, to me python (and js) are tolerable languages only when they
are controlled with linters and automated tests. And pylint is still the best
linter for big python projects. It is fully configurable and each and every
default rule is interesting per se. For example, a class with no `__init__`,
no or too many attributes or methods, is a potential code smell and candidate
for refactoring.

~~~
sly010
I don't know any other linter for any other language that complains about too
many methods (or too many lines in a method, or too long variable names, etc)
in a class. I should know my domain better than pylint.

~~~
frobozz
PMD: [http://pmd.sourceforge.net/snapshot/pmd-
java/rules/java/code...](http://pmd.sourceforge.net/snapshot/pmd-
java/rules/java/codesize.html#ExcessivePublicCount)

Scalastyle:
[http://www.scalastyle.org/rules-0.8.0.html#org_scalastyle_sc...](http://www.scalastyle.org/rules-0.8.0.html#org_scalastyle_scalariform_NumberOfMethodsInTypeChecker)

JSHint:
[http://jshint.com/docs/options/#maxstatements](http://jshint.com/docs/options/#maxstatements)

------
blacksmythe

      >> configure it with a whitelist of lint checks
    

I have found Pylint to be very helpful, and always use it on Python projects
past the size of a short script.

If you start using Pylint at the beginning of a project, you can blacklist
warnings that you want to ignore fairly easily.

~~~
guitarbill
Agreed. I mean you don't need to follow it religiously, but it's pretty
workable with a small blacklist (docstrings, locally disabled, and a few more
I don't instantly recall). When the hacks get bad, maybe the odd `#pylint:
disable=<blah>` but at least it shows you've though about the pylint warning.

>> If the errors are utter garbage, delete those checks from the
configuration.

I've never had "utter garbage" errors. I've had errors I didn't agree
with/made the conscious choice to ignore. And I've had errors I thought were
garbage, but really I just didn't fully understand them.

(Although I've experienced how applying it to an existing, unlinted project is
horrible.)

~~~
_Wintermute
>> I've never had "utter garbage" errors. I've had errors I didn't agree
with/made the conscious choice to ignore. And I've had errors I thought were
garbage, but really I just didn't fully understand them.

I've had lots of spurious errors with numpy code, where pylint seems to think
everything is a list.

~~~
guitarbill
I've experienced something similar with SQLAlchemy, because of the amount of
auto-magical stuff it does. I solved it with `[TYPECHECK] generated-
members=query`, although this can be slow so blacklisting SQLAlchemy classes
is still an option.

NumPy's `__init__.py` code is a bit messy and manipulates `__all__` a lot [0],
so I can understand why pylint (and other static code analysis tools) struggle
with it.

Yeah, it's frustrating hitting errors in third party modules, and it's tedious
for one person initially to set up pylint correctly, but so worth it for a
team.

[0]
[https://github.com/numpy/numpy/blob/master/numpy/__init__.py...](https://github.com/numpy/numpy/blob/master/numpy/__init__.py#L183)

------
xapata
Why weren't the functionality of those for-loops tested? It seems like a
strange thing to burden PyLint with catching.

~~~
itamarst
They were tested, but it didn't catch the bug unfortunately. I got bit by the
fact that I was testing with synchronous callbacks, but real world use was
with asynchronous callbacks.

~~~
OskarS
It's an extremely common bug in basically all languages that allow closures to
capture "live" variables. Almost every developer that uses any kind of async
programming gets bit by it eventually. For-loops and closures do not mix well.

~~~
xapata
The author did not create any closures in the example code. Python only
creates a closure for a def in a def.

Regardless, using functools.partial solves that problem: ``callback =
partial(print, i)``.

------
denfromufa
Pylint is usable in PTVS projects:

[https://github.com/Microsoft/PTVS/wiki/Pylint](https://github.com/Microsoft/PTVS/wiki/Pylint)

Also Sublime Text has a nice plugin called Anaconda, where PyLint can be
enabled:

[http://damnwidget.github.io/anaconda/IDE/](http://damnwidget.github.io/anaconda/IDE/)

------
tclancy
Like an article that is both for and against it's subject, I agree and
disagree. Pylint is painful for the author this time but it should get easier
and easier with each project. If you use pylint from the start you won't get
bitten by that "mountain of errors" problem and you develop a default
.pylintrc file that you can bring from project to project, tweaking as needed.

A (hopefully) short story: on my last contact job I was brought in as a senior
developer for a growing Django project that had 0 coding standards. I imposed
a small set of coding rules (what I actually did was say we would follow the
Django coding style guide, as I've learned over time pointing at an existing,
3rd-party guide means when people get butthurt about having to change a habit
they get mad at the guide and not at a coworker) and then added pep8 and
pylint to the git commit hook. That lasted for about a day because of the
massive amount of errors from pylint (the situation the author describes), so
I modified the hook to block on pep8 violations and just give a guilt-trip in
pylint errors for the time being. Then myself and a couple of team members who
drew short straws sucked it up and actually fixed pylint errors as we went
until we could turn pylint back on. We fixed a number of bugs in that process
and then going forward pylint started to show it's strength by catching the
sorts of bugs the article mentions (and still enforcing a standard coding
style so you could no longer look at a file and say "That's tclancy's code"
but rather saw "That's our-project's code".

Having pylint in the hook process is better than running it as part of CI or
ad hoc because it's run more often, meaning you get hit with a smaller number
of errors at once. Having pylint or pep8 as part of your IDE is even better
because you fix problems as you go. One of my current side projects is a
Django project written by a Rails dev (no idea why) that I do maintenance on.
The first thing I do when I open a file I've never seen is get rid of all the
red and green squigly lines (or at least look at why they are there). Most of
the time it's just a formatting issue but there are plenty of actual bugs it
turns up. Just by opening the files to make an update or fix a bug I often
wind up improving things people didn't even know were broken as a side effect.

Linters are a good thing. They can always be better and they will always annoy
for some idiosyncrasy they have (or you do), but it beats ignorance.

~~~
draw_down
I think a formatter is really best. Linters know what's wrong, the obvious
next step is to just fix it. I'm not interested in thinking about this stuff
anymore, just fix it so I can move on.

------
hadriendavid
I use pylama, it wraps pylint and other code checking tools. It also has a
cool logo.

[https://github.com/klen/pylama](https://github.com/klen/pylama)

------
w_t_payne
I've got my pylint set up to work right from the very beginning of the
project, with most rules enabled, so I can keep a fairly clean linted state
and never have to play "catch up" to any great extent. (I also use pep8;
pep256 and radon to control formatting; documentation and complexity).

I wrote a wrapper around all of these linting tools so I can call them
programmatically from the same Python process, and each time a file changes I
run my linting tools on _that_ file. (Not the whole codebase). I also
intercept the output and re-format it to give a (brief) report in a common
format. I can also control thestrictness of linting (i.e. which config file to
use) with a YAML-format comment in the file header, so we can write quick
prototypes without worrying too much about style then bring them into the
quality system on a file-by-file basis.

In this way, linting runs fast enough to be useful in the edit-lint/test loop,
and the error messages are both telegraphic and immediately actionable.

------
pech0rin
Using pylint in emacs with flycheck has saved me more hours that I can count
debugging simple syntax and other python errors. As the author mentions,
whitelisting is the only way to go.

[http://www.flycheck.org/en/latest/](http://www.flycheck.org/en/latest/)

------
narendrapateljc
Have pylint integrated with sublime and configured it to run checks on every
save. Its really a life saver!

Plus if you want to disable some features you can even do it at the local
level rather then having a single central rule. eg:- using #pylint:
disable=I0011,E0401 in your .py files.

------
sametmax
Also, now you can use mypy for additional bug catching. Personally I have
flake8 + mypy + py.test + coverall + mccabe, running on all my new projects.

~~~
w_t_payne
I've really got to start using mypy. Looks awesome.

~~~
sametmax
The concept is awesome, the implementation is less so. There is still a lot of
ironing to do. But it does catch many bugs, even when you don't annotate
anything. Anyway, it's good tu use it early to help guido fix the last warts.

------
asah
big +1 for pylint - saved my butt more times than I can count.

Subtly, pylint makes it easy to migrate teams to new APIs, e.g. by disallow
rules.

-1 to whitelisting - I just devote ~1 day to going through the 1000s of warnings, not a big deal.

------
mhb
How does Pylint compare with PyCharm's inspections? Is there a reason to use
both?

~~~
brianwawok
I think PyCharm is closer to flake8. It's good but more surface level.

~~~
masklinn
> I think PyCharm is closer to flake8.

PyCharm does type inference and cross-file name/object checking, these are not
things pyflake can do.

~~~
brianwawok
Ah that is true, it does a little bit of MyPy stuff too.

------
ibizaman
I have pylint integrated with vim through syntastic. The amount of output is
thus limited to one module at a time and not intrusive nor disruptive. Also,
like suggested, I have blacklisted some errors I don't care about.

------
tbronchain
I'm using pylint daily and usually running it doing "pylint -E" to discard
warnings and display errors only. I found it very useful, and never seen a
case of an error that shouldn't be fixes. I'm probably missing some useful
warnings, but it's always better than not using it at all, or getting lost
with thousands of them!

~~~
itamarst
The problem is the bug that pylint caught that would've gone to production was
caught by a warning, not an error. So we could've just used -E but then we'd
miss out on some very useful stuff.

~~~
tbronchain
That's a fair point I agree. The pylint config files in the article are great,
and I will definitely look into it. Pylint -E is more like a quick lazy option
to make 80% of the work :)

------
aaronbasssett
Your unittests should have caught both of those errors. Also Django does use
linting:
[https://github.com/django/django/blob/master/tox.ini#L36-L41](https://github.com/django/django/blob/master/tox.ini#L36-L41)

~~~
itamarst
I did have unit tests, they didn't catch the bug though because the test used
a synchronous callback (much easier to test) and real world code ran with
async callback.

------
hueving
What would be awesome is an interactive pylint onboarding tool. Start with no
checks, then add them iteratively in order of severity showing the new errors
at each step asking if the rule should be adopted or not.

------
syllogism
I find frosted (a reimplementation of PyFlakes) very useful.

------
crdoconnor
I actually think that unused initialized variables might be the only pylint
rule which has found genuine production bugs for me.

------
michaelmcmillan
The fact that your unit tests did not catch that bug tells me you should spend
less time linting and more time covering.

~~~
crdoconnor
Linting is cheaper than unit testing.

~~~
michaelmcmillan
They serve two completely different purposes. Testing ensures the logic is
correct, linting is checking the code for syntax errors and bad practice.

~~~
crdoconnor
They don't actually serve completely different purposes. Linting is supposed
to catch bugs and it does, very cheaply. It just doesn't catch very many.

In terms of coding style I've found it fairly ineffective at picking up on
most bad practices.

~~~
michaelmcmillan
Sure, "bug" as a term is as elastic as you want it to be. I was pointing out
the false equivalence you implied by writing "cheaper". Testing and linting
does not provide the same value, so calling it cheaper is a kind of fallacy.

~~~
crdoconnor
That's like saying that a sandwich and a condo do not provide the same value
so calling a sandwich cheaper is a kind of fallacy.

There's no elasticity over what the OP reported. That was a bug. Clear as they
come. He may have unit tests but they didn't catch it and since you can't have
unit tests for everything, why not spend 500ms running a linter?

~~~
michaelmcmillan
You are making my point. It makes no sense to call a sandwich cheaper than a
condo _when_ you are evaluating what to buy when looking for a place to live.
That is what I was pointing out.

Testing and linting does different things. Of course a sandwich is cheaper
than a condo, but what does that have to do with what we are talking about?

As for the claim that you can't have unit tests for everything: What do you
mean? No, you can't test all branches or combinations in a system, but you can
have a 100% test coverage (aka. hitting each line at least once).

~~~
crdoconnor
>You are making my point. It makes no sense to call a sandwich cheaper than a
condo when you are evaluating what to buy when looking for a place to live.
That is what I was pointing out.

Yeah not really. Both linting and unit tests catch bugs. This is a fact that
you refuse to actually face.

>you can have a 100% test coverage

You can but it's A) a bad idea and B) it won't even necessarily catch the bug
that the OP brought up if you do.

~~~
michaelmcmillan
I grant you that linting catches bugs, but what I've argued for (perhaps
badly) is that the bugs it catches are very shallow, but more importantly they
make up a relatively small subset of the bugs proper unit tests catch. That's
what makes the comparison you made deceptive.

I use both linting (pylint actually) and unit tests, so I am not advocating
that you should scrap your linting tools. I am just trying to point out the
difference.

Your B) argument is yet another indication that the tests are not good enough.
The example bug OP posted is what made me comment in the first place. If your
tests don't catch that your tests are useless.

------
Xophmeister
Vim + Syntastic + pylint + mypy works pretty well for me

------
riquito
I've been quite happy with pycodestyle (formerly pep8), using autopep8 to
automatically apply fixes when deemed safe.

------
tome
Oh Pylint, used by my previous employer as though it were a type checker. Now
I just use a language with a type checker.

------
Mithaldu
Does python not have anything like `use strict;`?

Edit: What the heck, this is an honest question. :(

~~~
falcolas
No, but even a use strict type clause wouldn't have helped in this case, since
the entire syntax was valid (volme was being properly initialized as part of
the loop definition). The error occurred because 'volume' was considered to be
part of the function scope, despite being defined as part of the for loop
scope.

To me, it's a good reason to use different loop variable names for successive
loops. A good practice to use in any language, since it helps keep loops
distinct to your mind as well.

    
    
        for volume in get_volumes():
            print volume
        for other_volme in get_other_volumes():
            print other_volume
    

The second loop throws a runtime error and an error level linting alert.

~~~
Mithaldu
It would help if it restricted variables auto-vivified in a loop declaration
to the body of the loop itself, instead of making it available for the entire
parent scope. I'm used to working with languages that behave like that.

I'm actually a little surprised something like that hasn't been attempted yet
for Python.

Thanks for the answer!

------
cheiVia0
Some more Python checkers:

[https://anonscm.debian.org/cgit/collab-maint/check-all-
the-t...](https://anonscm.debian.org/cgit/collab-maint/check-all-the-
things.git/tree/data/python)

------
m_mueller
IMO the first example is a good use of a linter, the second should be caught
by coding style: I keep the following rule with closures:

1.) _Never_ go more than two layers deep, so you have one "def" inside another
"def" and _that 's it_.

2.) _Always_ keep the closures at the top of the containing function - no
further nesting.

Kids today coming from Javascript seem to be way too much in love with their
closures.

Example:

    
    
        def my_routine(flag, foo):
          def my_little_helper(thingy)
             if flag:
               return thingy
             return thingy + foo
      
          return my_little_helper(42)

~~~
itamarst
That's a good idea, but you shouldn't assume too much about why people are
making mistakes, I've been working with and on Twisted since 2001, so not a JS
kiddy :) I made this mistake fair and square even though I knew better.

Which is why I like having a tool that'll catch it for me.

My typical approach is `lambda x, y=y: f(x, y)`. Inline functions are much
more readable, is the thing. But I can see the argument for this coding style,
yes.

~~~
m_mueller
Sorry, didn't want to attack you personally with the 'kiddy' comment. I think
my proposed style mashes well with yours though.

