🕯️ Magic Note
The word “linter” comes from the tool lint, written in 1979 for the C language. Lint was named after the lint that collects in a dryer filter—small, unwanted bits that should be removed. Today, “linter” means any tool that finds issues in source code.
- Catch syntax errors and undefined variables before runtime
- Enforce consistent code style across a project
- Identify potential bugs (unused variables, dangerous patterns)
- Teach Python best practices and conventions
- Save debugging time by catching issues early
- Make code reviews focus on logic, not style
PEP 8 Key Rules
# Indentation: 4 spaces per level (no tabs)
def calculate(x):
return x * 2 # 4 spaces, not a tab
# Line length: maximum 79 characters (72 for docstrings)
# This line is too long and should be broken into multiple lines
# Imports: one per line, standard library first, then third-party, then local
import os
import sys
import requests
import numpy as np
from my_package import utils
# Whitespace: spaces around operators, after commas, no spaces inside parentheses
result = (a + b) * c # Good
result = (a+b)*c # Bad
# Naming conventions
def snake_case(): # Functions and variables
pass
class PascalCase: # Classes
pass
CONSTANT_UPPER = 42 # Constants
Bash
# Install flake8
pip install flake8
# Run on a single file
flake8 my_script.py
# Run on entire project (all .py files)
flake8 .
# Ignore specific errors
flake8 –ignore E501,W503 my_script.py
# Maximum line length
flake8 –max-line-length=100 my_script.py
Python (Code with issues)
# File: messy.py
import os, sys # F401 (imported but unused), E401 (multiple imports on one line)
def my_function( x,y ): # E201/E202 (whitespace inside parentheses)
result=x+y # E225 (missing whitespace around operator)
return result
unused_var = 42 # F841 (variable assigned but never used)
# Flake8 output:
# messy.py:1:1: F401 ‘os’ imported but unused
# messy.py:1:1: F401 ‘sys’ imported but unused
# messy.py:1:1: E401 multiple imports on one line
# messy.py:3:1: E302 expected 2 blank lines, found 1
# messy.py:3:15: E201 whitespace after ‘(‘
# messy.py:3:18: E202 whitespace before ‘)’
# messy.py:3:20: E231 missing whitespace after ‘,’
# messy.py:4:1: E302 expected 2 blank lines, found 3
# messy.py:4:11: E225 missing whitespace around operator
# messy.py:8:1: F841 local variable ‘unused_var’ is assigned to but never used
🕯️ Magic Note
Flake8 is fast and has a very low false-positive rate. It is the recommended linter for most Python projects. Many editors and IDEs (VS Code, PyCharm, Sublime) have built-in flake8 integration.
Bash
# Install pylint
pip install pylint
# Run on a file
pylint my_script.py
# Generate a config file (to customize rules)
pylint –generate-rcfile > .pylintrc
Bash (Pylint Output Example)
# ************* Module my_script
# C: 1, 0: Missing module docstring (missing-docstring)
# C: 3, 0: Function name ‘my_function’ doesn’t conform to snake_case naming style
# (invalid-name)
# W: 4, 4: Unused variable ‘unused_var’ (unused-variable)
# R: 3, 0: Too many arguments (6/5) (too-many-arguments)
#
# Your code has been rated at 6.42/10
Bash
# Install black
pip install black
# Format a file (rewrites it in place)
black my_script.py
# Check if files need formatting without changing them
black –check .
# Show differences without writing
black –diff my_script.py
# Format entire project
black .
Python (Before Black)
def badly_formatted( x,y,z ):
result=x+y+z
return result
a = [1,2, 3,4,5]
Python (After Black)
def badly_formatted(x, y, z):
result = x + y + z
return result
a = [1, 2, 3, 4, 5]
🕯️ Magic Note
Black is called “the uncompromising code formatter” because it has very few configuration options. The goal is to end all debates about code style. Everyone uses the same format. No arguments. No exceptions. Many large projects (including the Python standard library) now use Black.
Bash
# Install isort
pip install isort
# Sort imports in a file
isort my_script.py
# Check without changing
isort –check .
Python (Before isort)
from my_package import utils
import sys
import os
from datetime import datetime
import requests
Python (After isort)
import os
import sys
from datetime import datetime
import requests
from my_package import utils
Bash
# Install mypy
pip install mypy
# Run type checker
mypy my_script.py
Python
def greet(name: str) -> str:
return f”Hello, {name}!”
greet(42) # mypy error: Argument 1 to “greet” has incompatible type “int”; expected “str”
def add(a: int, b: int) -> int:
return a + b
result = add(5, “3”) # mypy error: Argument 2 to “add” has incompatible type “str”; expected “int”
🕯️ Magic Note
Mypy brings optional static typing to Python. It does not force you to use types everywhere. You can start with just a few type hints and gradually add more. Many large Python projects now use mypy to catch type errors before deployment.
Bash
# Install ruff
pip install ruff
# Lint files
ruff check .
# Auto-fix issues (like isort and some flake8 fixes)
ruff check –fix .
# Format code (like black)
ruff format .
- **Editor integration:** Most editors (VS Code, PyCharm, Sublime) have plugins that run linters as you type.
- **Pre-commit hooks:** Run linters before each commit (using pre-commit framework)
- **CI/CD pipelines:** Run linters in GitHub Actions, GitLab CI, or Jenkins
- **Makefile targets:** Create make lint and make format commands
Makefile
.PHONY: lint format check
lint:
ruff check .
mypy .
format:
ruff format .
ruff check –fix .
check:
ruff check –no-fix .
ruff format –check .
mypy .
| Warning | What It Means | Fix |
|---|---|---|
| F401 ‘module’ imported but unused | Import is not used | Remove import or use it |
| E501 line too long | Line exceeds 79 characters | Break into multiple lines |
| E302 expected 2 blank lines | Need 2 blank lines before function | Add blank line |
| F841 local variable assigned but never used | Variable not used | Remove or prefix with underscore |
| C0103 invalid name | Name does not follow naming conventions | Rename using correct case |
| W0611 unused import | Same as F401 | Remove import |
Python
# Fixing common issues
# F401: Remove unused import
# import math # Delete this line
# E501: Break long line
# result = function_with_many_parameters(param1, param2, param3, param4, param5, param6)
result = function_with_many_parameters(
param1, param2, param3, param4, param5, param6
)
# F841: Unused variable – use underscore for dummy variables
# for i in range(10):
for _ in range(10): # _ indicates intentionally unused
print(“Hello”)
YAML (.pre-commit-config.yaml)
repos:
– repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
hooks:
– id: ruff
args: [–fix, –exit-non-zero-on-fix]
– id: ruff-format
– repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
– id: mypy
additional_dependencies: [types-all]
Python
# flake8: noqa (disable all warnings for this file)
# Ignore specific warning on one line
import math # noqa: F401 (unused import is intentional)
# With pylint
# pylint: disable=unused-argument
def callback(event, context): # context is required by API but unused
return event
# pylint: enable=unused-argument
- What is the difference between flake8 and Black?
- What does PEP 8 specify?
- How do you ignore a specific flake8 warning on a single line?
- What is the purpose of mypy?
- Name three benefits of using linters.
- What does the ruff tool do?
⚡ Whisper
Your code works. But does it shine? Linters are not critics. They are teachers. They point to the spaces where a line is too long. The import that never got used. The variable that was assigned but forgotten. The style that differs from the rest of the project. These are not failures. They are opportunities. Each warning is a chance to write cleaner code. Each error caught before runtime is time saved debugging. A linter is like a patient friend who reads every line you write and whispers: “This could be better.” Listen to the whisper. Not every warning must be fixed. But every warning should be considered. Over time, you will internalize the rules. You will write cleaner code from the start. The linter will complain less. And your code will be better for it. This is not about perfection. It is about progress. One lint at a time.