Somebody recently introduced me to Vulture, a Python project for finding dead code.
Vulture uses the ast module to build abstract syntax trees for all given files. While traversing all syntax trees it records the names of defined and used objects. Afterwards, it reports the objects which have been defined, but not used. This analysis ignores scopes and focuses only on object names.
That quote is taken from the project readme, it gives you a good overview of how it works.
For example, given this small bit of Python
def fn(): x = 1 return 2 fn()
Saving that and running vulture against it will give me the following output.
$ vulture code.py code.py:2: Unused variable 'x'
It cleverly finds the variable defined in a function that isn't used. It does know that the function is used. This works well over multiple files too.
--exclude can create more useful results. Often you can find code that
isn't used with coverage.py. However, I generally run only with my unit
tests, so it is quite common for dead code to be left, with a single unit test
validating that it still works. Vulture's exclude flag means you can exclude
the tests, so code only used in the tests will appear unused.
I used this trick to look for dead code in MkDocs.
$ vulture mkdocs --exclude mkdocs/tests
Unfortunately, false positives are common. Vulture only gives you a good place to start your investigation. For example, if used on a library, you will have code that is expected to be used externally (and is only called by your tests). Vulture will also complain that every unittest test class isn't used as you never directly make instances of it. This is also an issue for projects that follow convention, like Django.
Despite these limitations, I found it very useful for getting some easy wins and removing dead code. If you are familiar with the code, scanning through the report is pretty quick and easy.