Skip to content

Using Flake8 for Style Checks

  • style (PEP8-ish)
  • unused imports
  • simple logical issues
bash
flake8 .

In .flake8:

ini
[flake8]
max-line-length = 88
extend-ignore = E203,W503
exclude = .git,__pycache__,.venv,build,dist
  • flake8-bugbear
  • flake8-comprehensions
  • flake8-docstrings

If you use Black, align flake8 ignores with Black defaults.

Every measurement on this page — and on the other five tools in this phase — comes from running the tool against this deliberately flawed file:

sample.py
import os
import subprocess
import hashlib
 
 
def process(items, user_input, flag = False):
    unused_var = 42
    result=[]
    for i in items:
        if i > 0:
            if flag:
                if i % 2 == 0:
                    result.append(i*2)
                else:
                    result.append(i)
            else:
                result.append(i)
    password = "hunter2"
    h = hashlib.md5(password.encode()).hexdigest()
    os.system("echo " + user_input)
    subprocess.call("ls " + user_input, shell=True)
    return result
 
 
def add(a: int, b: int) -> int:
    return a + b
 
 
x = add("1", 2)
toolwhat it reported on sample.py
flake85 style and dead-code issues. No security findings.
pylint4 issues, score 8.10/10
mypy1 type error, which neither linter saw
bandit5 security issues, 3 of them HIGH
radoncomplexity A (5), maintainability A (56.30)

The headline is that no tool subsumes another. flake8 read the whole file and reported nothing about the shell injection on line 20. bandit read the same file and said nothing about the type error on line 29. Running one and concluding the code is clean is the mistake this phase exists to prevent.

sketch Six tools, one file, almost no overlap p5.js
Each tool was run against the same 29-line file. Click a tool to see what it found and, more importantly, what it did not.
diagram Diagram mermaid

flake8 is a wrapper. That matters because the codes tell you which underlying tool spoke, and how much to care:

  • E / W come from pycodestyle and are layout opinions. A formatter can fix all of them.
  • F come from pyflakes and are real problems — an unused import, a name that is never used, an undefined name. These deserve attention.

Measured on sample.py:

flake8 sample.py
sample.py:6:36: E251 unexpected spaces around keyword / parameter equals
sample.py:6:38: E251 unexpected spaces around keyword / parameter equals
sample.py:7:5:  F841 local variable 'unused_var' is assigned to but never used
sample.py:8:11: E225 missing whitespace around operator
sample.py:19:5: F841 local variable 'h' is assigned to but never used

Note what is absent: nothing about os.system with concatenated user input on line 20, and nothing about the type error on line 29. flake8 was never looking for either.

This is the most common configuration problem in Python tooling, and it is measurable:

after running black, then flake8
fmt.py:1:80: E501 line too long (84 > 79 characters)

black formats to 88 columns; flake8 defaults to 79. Run both with default settings and correctly formatted code fails the linter forever. The fix is to tell flake8 the number black uses:

setup.cfg
[flake8]
max-line-length = 88
extend-ignore = E203, W503

E203 and W503 are the two other rules black deliberately violates — whitespace before : in slices, and line breaks before binary operators. Ignoring them is the documented combination, not a workaround.

setup.cfg
[flake8]
max-line-length = 88
extend-ignore = E203, W503
exclude = .git,__pycache__,build,dist,migrations,.venv
per-file-ignores =
    __init__.py:F401          # re-exports are intentional there
    tests/*:S101              # asserts are the point of a test

per-file-ignores is what stops a team from disabling a rule globally because of two files. Blanket # noqa on a line is the last resort — and a bare # noqa silences everything on that line, so write # noqa: F401 with the specific code.

pch.quizTag pch.quizDefaultTitle
  1. flake8 reports E, W and F codes. What is the difference?

    pch.quizShowAnswer

    B — E and W come from pycodestyle and are layout opinions a formatter can fix; F comes from pyflakes and marks real problems such as unused or undefined names — flake8 is a wrapper around pycodestyle, pyflakes and mccabe. The prefix tells you which tool spoke and how much the finding is worth.

  2. Code formatted by black fails flake8 with E501 line too long. Why?

    pch.quizShowAnswer

    B — black formats to 88 columns while flake8 defaults to 79, so the two disagree until flake8 is configured — Measured 84 > 79 on a black-formatted line. Set max-line-length = 88 and extend-ignore = E203, W503, which are the rules black deliberately violates.

  3. What did flake8 report about the os.system call built from user input in the sample?

    pch.quizShowAnswer

    B — nothing at all; it does not look for security issues — flake8 read the line and had nothing to say. Bandit flagged the same line B605 HIGH. A clean flake8 run says the layout is consistent, nothing more.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading