Python PEP8 and Style Practice Problems & Exercises
Practice: PEP8 and Style
← Back to lessonEasy
Predict the output. A checker function scans lines for the most common E225 patterns and reports whether each line is clean or violating.
def check_e225(line):
"""Simplified E225 detector for common cases."""
import re
# Detect assignment/comparison without surrounding spaces
# Pattern: non-space, operator, non-space (excluding keyword args context)
patterns = [
r'\w=[^=\s]', # x=1 (assignment, no space after)
r'[^=!<>]=\w', # 1=x (assignment, no space before) but not == or !=
r'\w==[^\s]', # x==1
r'\w>[^\s]', # x>1
]
for p in patterns:
if re.search(p, line):
return "E225 missing whitespace around operator"
return "Clean"
lines = [
"x=1",
"result=a+b",
"if x>0:",
"y = x * 2 + 1",
]
for line in lines:
print(check_e225(line))Solution
def check_e225(line):
import re
patterns = [
r'\w=[^=\s]',
r'[^=!<>]=\w',
r'\w==[^\s]',
r'\w>[^\s]',
]
for p in patterns:
if re.search(p, line):
return "E225 missing whitespace around operator"
return "Clean"
lines = [
"x=1",
"result=a+b",
"if x>0:",
"y = x * 2 + 1",
]
for line in lines:
print(check_e225(line))
Output:
E225 missing whitespace around operator
E225 missing whitespace around operator
E225 missing whitespace around operator
Clean
How it works: The first three lines all violate E225 in different ways:
x=1— no spaces around=in an assignment statement.result=a+b— no spaces around=, and incidentallya+balso lacks spaces (another E225).if x>0:— no space after>.
The fourth line, y = x * 2 + 1, is fully compliant — spaces around =, *, +.
Key insight: E225 applies to all binary operators in expression context. The one common exception is function default arguments: def func(x=1) — no spaces around = there (that would be E251). flake8 is aware of this distinction and will not fire E225 on default argument =.
Expected Output
E225 missing whitespace around operator\nE225 missing whitespace around operator\nE225 missing whitespace around operator\nCleanHints
Hint 1: E225 fires when there is no space on at least one side of a binary operator.
Hint 2: Assignment (=), comparison (==, !=, >, <), and arithmetic (+, -, *, /) all require spaces.
Hint 3: The exception: keyword argument defaults like func(x=1) — no spaces there.
Predict the output. The code below has E302 violations (missing blank lines before top-level functions). Despite the style issues, predict what the code prints when run.
import os
def first():
return 1
def second():
return 2
def third():
return 3
print(first())
print(second())
print(third())Solution
import os
def first():
return 1
def second():
return 2
def third():
return 3
print(first())
print(second())
print(third())
Output:
1
2
3
How it works: Python executes the code correctly — E302 is a style violation, not a syntax error. The functions run and return 1, 2, and 3. But flake8 would report:
- Line 2:
E302 expected 2 blank lines, found 0(beforefirst) - Line 4:
E302 expected 2 blank lines, found 0(beforesecond) third()is fine — it has exactly 2 blank lines before it.
The compliant version:
import os
def first():
return 1
def second():
return 2
def third():
return 3
print(first())
print(second())
print(third())
Key insight: E302 is one of the most frequently seen flake8 violations in code review. When you write a new function immediately after an import or another function without two blank lines, flake8 will flag it. Most editors and formatters (Black) insert the blank lines automatically, but knowing the rule helps you spot it when reviewing code manually.
Expected Output
1\n2\n3Hints
Hint 1: PEP 8 requires exactly 2 blank lines before every top-level function and class definition.
Hint 2: E302 fires when there are fewer than 2 blank lines before a top-level def or class.
Hint 3: The rule applies between any two top-level definitions and after imports.
Predict the output. A checker function identifies E401 violations — multiple module imports on one line.
def check_e401(line):
line = line.strip()
# E401: bare 'import X, Y' (not from ... import)
if line.startswith("import ") and "," in line and not line.startswith("from "):
return f"E401: {line}"
return f"OK: {line}"
imports = [
"import os, sys",
"import os",
"import sys",
"from os.path import join, exists",
]
for imp in imports:
print(check_e401(imp))Solution
def check_e401(line):
line = line.strip()
if line.startswith("import ") and "," in line and not line.startswith("from "):
return f"E401: {line}"
return f"OK: {line}"
imports = [
"import os, sys",
"import os",
"import sys",
"from os.path import join, exists",
]
for imp in imports:
print(check_e401(imp))
Output:
E401: import os, sys
OK: import os
OK: import sys
OK: from os.path import join, exists
How it works: E401 applies only to bare import X, Y statements — importing multiple top-level modules on one line. It does NOT apply to from module import a, b — that is explicitly allowed by PEP 8 and does not trigger E401.
The fix is always the same: one module per import line:
# WRONG (E401)
import os, sys, json
# CORRECT
import os
import sys
import json
# STILL OK — from imports can list multiple names
from os.path import join, exists, dirname
Key insight: The reason for the rule is readability during code review and version control diffs. When each module is on its own line, adding or removing an import produces a single-line diff. With import os, sys, json, removing sys changes the entire line, which obscures what actually changed.
Expected Output
E401: import os, sys\nOK: import os\nOK: import sys\nOK: from os.path import join, existsHints
Hint 1: E401 fires when multiple modules appear on a single import statement: import os, sys.
Hint 2: from ... import can import multiple names on one line — that is allowed.
Hint 3: import os, sys is the violation, not from os.path import join, exists.
Predict the output. A checker function flags E711 (== None) and E712 (== True/== False) violations.
def check_comparison(expr):
if "== None" in expr or "!= None" in expr:
if "!= None" in expr:
return "E711: use 'is not None'"
return "E711: use 'is None'"
if "== True" in expr or "== False" in expr:
return "E712: use truthy check"
return "Clean"
expressions = [
"if result == None:",
"if value != None:",
"if enabled == True:",
"if result is None:",
"if enabled:",
]
for expr in expressions:
print(check_comparison(expr))Solution
def check_comparison(expr):
if "== None" in expr or "!= None" in expr:
if "!= None" in expr:
return "E711: use 'is not None'"
return "E711: use 'is None'"
if "== True" in expr or "== False" in expr:
return "E712: use truthy check"
return "Clean"
expressions = [
"if result == None:",
"if value != None:",
"if enabled == True:",
"if result is None:",
"if enabled:",
]
for expr in expressions:
print(check_comparison(expr))
Output:
E711: use 'is None'
E711: use 'is not None'
E712: use truthy check
Clean
Clean
How it works:
E711 — == None and != None are flagged because None is a singleton. The is operator checks identity (same object), which is semantically correct and cannot be overridden by __eq__. Using == calls __eq__, which a misbehaving class could override to return True when compared to None.
E712 — == True is redundant. Booleans are truthy by definition: if enabled: is shorter, cleaner, and exactly equivalent to if enabled == True:.
The correct patterns:
# E711 fixes
if result is None: # was: if result == None:
return default
if value is not None: # was: if value != None:
process(value)
# E712 fixes
if enabled: # was: if enabled == True:
run()
if not disabled: # was: if disabled == False:
run()
Key insight: These two violations are among the most common in real code reviews. E711 also matters for correctness — not just style. The is check cannot be intercepted by user-defined __eq__ methods, making your None checks bulletproof against poorly designed classes.
Expected Output
E711: use 'is None'\nE711: use 'is not None'\nE712: use truthy check\nClean\nCleanHints
Hint 1: E711: Never use == None or != None. Use is None and is not None.
Hint 2: E712: Never use == True or == False. Use truthy/falsy checks or "is True" if the singleton matters.
Hint 3: None is a singleton — identity check (is) is both more correct and more efficient than equality.
Medium
Sort and group imports correctly. Given a mixed list of imports, categorize them into PEP 8 groups and sort each group alphabetically.
import sys
# Python 3.10+ exposes the set of stdlib module names
stdlib_names = sys.stdlib_module_names # frozenset
THIRD_PARTY = {"requests", "django", "flask", "numpy", "pandas"}
LOCAL_PREFIX = "myapp"
def group_imports(imports):
stdlib = []
third_party = []
local = []
for imp in imports:
# Extract the base module name
base = imp.split(".")[0]
if base in stdlib_names:
stdlib.append(imp)
elif base in THIRD_PARTY or any(tp in base for tp in THIRD_PARTY):
third_party.append(imp)
else:
local.append(imp)
return sorted(stdlib), sorted(third_party), sorted(local)
mixed_imports = [
"requests",
"sys",
"myapp.utils",
"os",
"django.db",
"json",
"myapp.models",
]
stdlib, third_party, local = group_imports(mixed_imports)
print(stdlib)
print(third_party)
print(local)Solution
import sys
stdlib_names = sys.stdlib_module_names
THIRD_PARTY = {"requests", "django", "flask", "numpy", "pandas"}
LOCAL_PREFIX = "myapp"
def group_imports(imports):
stdlib = []
third_party = []
local = []
for imp in imports:
base = imp.split(".")[0]
if base in stdlib_names:
stdlib.append(imp)
elif base in THIRD_PARTY or any(tp in base for tp in THIRD_PARTY):
third_party.append(imp)
else:
local.append(imp)
return sorted(stdlib), sorted(third_party), sorted(local)
mixed_imports = [
"requests",
"sys",
"myapp.utils",
"os",
"django.db",
"json",
"myapp.models",
]
stdlib, third_party, local = group_imports(mixed_imports)
print(stdlib)
print(third_party)
print(local)
Output:
['json', 'os', 'sys']
['django.db', 'requests']
['myapp.models', 'myapp.utils']
How it works: sys.stdlib_module_names (Python 3.10+) is a frozenset of all names in the standard library. The function splits each import at the first dot to get the base package name, then classifies it. Results are sorted alphabetically within each group.
The correct import block for this file would be:
import json
import os
import sys
import requests
from django.db import models
from myapp.models import User
from myapp.utils import helpers
Key insight: In practice, you should use isort to do this automatically. isort --profile black handles all three groups, sorts within groups, and respects Black's formatting. Running isort before flake8 eliminates the entire category of import order violations. The --profile black flag ensures isort and Black do not conflict on multiline import formatting.
Expected Output
['json', 'os', 'sys']\n['django.db', 'requests']\n['myapp.models', 'myapp.utils']Hints
Hint 1: PEP 8 groups imports into: (1) stdlib, (2) third-party, (3) local. Each group separated by a blank line.
Hint 2: Within each group, imports should be sorted alphabetically.
Hint 3: Knowing which packages are stdlib vs third-party requires familiarity — sys.stdlib_module_names helps in Python 3.10+.
Predict the output. Despite E211 and E231 violations in the code, predict what each line prints.
# E211 violation: space before ( and [
def foo (a, b, c):
return a + b + c
nums = [1,2,3] # E231: no spaces after commas
d = {'a':1,'b':2} # E231: no space after : in dict
result = foo (1,2,3) # E211 + E231
print(result)
print(nums)
print(d)
print(f"foo({', '.join(str(n) for n in nums)})")Solution
def foo (a, b, c):
return a + b + c
nums = [1,2,3]
d = {'a':1,'b':2}
result = foo (1,2,3)
print(result)
print(nums)
print(d)
print(f"foo({', '.join(str(n) for n in nums)})")
Output:
8
[1, 2, 3]
{'a': 1, 'b': 2}
foo(1, 2, 3)
How it works: All four E211/E231 violations are style-only — Python executes the code without error. foo (1,2,3) calls foo with arguments 1, 2, 3 and returns 6. nums is [1, 2, 3] (Python ignores the missing spaces). The dict {'a':1,'b':2} is valid and Python's repr even adds the spaces for display.
The flake8 violations in this code:
def foo (a, b, c):— E211: whitespace before(nums = [1,2,3]— E231: missing whitespace after,(twice)d = {'a':1,'b':2}— E231: missing whitespace after:and,(three times)result = foo (1,2,3)— E211 + E231 (three times)
The compliant version:
def foo(a, b, c):
return a + b + c
nums = [1, 2, 3]
d = {'a': 1, 'b': 2}
result = foo(1, 2, 3)
Key insight: E231 is the most frequently occurring flake8 error in newly written code. It fires every time you write a,b instead of a, b. Most editors add the space automatically if configured, but knowing the rule helps during code review.
Expected Output
8\n[1, 2, 3]\n{'a': 1, 'b': 2}\nfoo(1, 2, 3)Hints
Hint 1: E211: no space before ( or [ — func() not func (), list[0] not list [0].
Hint 2: E231: space after commas — foo(a, b) not foo(a,b). Also space after : in dicts — {"a": 1} not {"a":1}.
Hint 3: These violations are purely about whitespace, never about logic.
Predict the output. A naming convention checker validates identifiers against PEP 8 rules.
import re
def is_class_name(name):
"""CapWords: starts uppercase, no underscores (except leading _)."""
return bool(re.match(r'^_?[A-Z][a-zA-Z0-9]*$', name))
def is_function_name(name):
"""snake_case: all lowercase, words separated by underscores."""
return bool(re.match(r'^_?[a-z][a-z0-9_]*$', name))
def is_constant_name(name):
"""UPPER_SNAKE_CASE: all uppercase, words separated by underscores."""
return bool(re.match(r'^[A-Z][A-Z0-9_]*$', name))
checks = [
is_class_name("UserProfile"),
is_function_name("get_user_by_id"),
is_constant_name("MAX_RETRIES"),
is_function_name("_internal_helper"),
is_class_name("userProfile"), # wrong: starts lowercase
is_function_name("GetUserById"), # wrong: CapWords
]
for result in checks:
print(result)Solution
import re
def is_class_name(name):
return bool(re.match(r'^_?[A-Z][a-zA-Z0-9]*$', name))
def is_function_name(name):
return bool(re.match(r'^_?[a-z][a-z0-9_]*$', name))
def is_constant_name(name):
return bool(re.match(r'^[A-Z][A-Z0-9_]*$', name))
checks = [
is_class_name("UserProfile"),
is_function_name("get_user_by_id"),
is_constant_name("MAX_RETRIES"),
is_function_name("_internal_helper"),
is_class_name("userProfile"),
is_function_name("GetUserById"),
]
for result in checks:
print(result)
Output:
True
True
True
True
False
False
How it works:
UserProfile— valid CapWords class name. Starts uppercase, no underscores.get_user_by_id— valid snake_case function. All lowercase, underscores between words.MAX_RETRIES— valid UPPER_SNAKE_CASE constant._internal_helper— valid private function (leading_+ snake_case).userProfile— WRONG for a class. Starts lowercase. This is camelCase (Java-style), not CapWords.GetUserById— WRONG for a function. This is CapWords, which is the class convention.
PEP 8 naming summary:
| Context | Convention | Example |
|---|---|---|
| Class | CapWords | HttpClient, UserProfile |
| Function / method | snake_case | get_user, process_data |
| Variable | snake_case | total_count, user_id |
| Module-level constant | UPPER_SNAKE_CASE | MAX_RETRIES, BASE_URL |
| Private (convention) | _name | _helper, _cache |
| Name mangling | __name | __private_attr |
| Dunder / magic | __name__ | __init__, __repr__ |
Key insight: The single most common naming violation in Python code written by engineers from other languages is using camelCase for functions and variables (getUserById instead of get_user_by_id). flake8 does not catch naming convention violations by default — you need pep8-naming (pip install pep8-naming) for flake8 to report N801 (class name not CapWords) and N802 (function name not lowercase).
Expected Output
True\nTrue\nTrue\nTrue\nFalse\nFalseHints
Hint 1: Classes use CapWords (PascalCase): UserProfile, HttpClient.
Hint 2: Functions and variables use snake_case: get_user, total_count.
Hint 3: Module-level constants use UPPER_SNAKE_CASE: MAX_RETRIES, DEFAULT_TIMEOUT.
Hint 4: A single leading underscore means "private by convention": _internal_helper.
Demonstrate correct # noqa usage. The function below parses noqa comments and classifies them as bare (bad practice) or specific (good practice).
def parse_noqa(line):
"""Classify a noqa comment on a line."""
if "# noqa:" in line:
code = line.split("# noqa:")[-1].strip()
return f"specific: {code}"
elif "# noqa" in line:
return "bare noqa"
return "no noqa"
# Simulate real use cases
long_url = "SOME_API = 'https://api.example.com/v2/endpoint/long/path' # noqa: E501"
side_effect = "import myapp.register_signals # noqa: F401"
bare_usage = "x=1+2 # noqa"
specific_usage = "x=1+2 # noqa: E225"
print(f"URL kept: {parse_noqa(long_url) == 'specific: E501'}")
print(f"Side-effect import: {parse_noqa(side_effect) == 'specific: F401'}")
print(f"Bare noqa (bad): {parse_noqa(bare_usage) == 'bare noqa'}")
print(f"Specific noqa (good): {parse_noqa(specific_usage) == 'specific: E225'}")Solution
def parse_noqa(line):
if "# noqa:" in line:
code = line.split("# noqa:")[-1].strip()
return f"specific: {code}"
elif "# noqa" in line:
return "bare noqa"
return "no noqa"
long_url = "SOME_API = 'https://api.example.com/v2/endpoint/long/path' # noqa: E501"
side_effect = "import myapp.register_signals # noqa: F401"
bare_usage = "x=1+2 # noqa"
specific_usage = "x=1+2 # noqa: E225"
print(f"URL kept: {parse_noqa(long_url) == 'specific: E501'}")
print(f"Side-effect import: {parse_noqa(side_effect) == 'specific: F401'}")
print(f"Bare noqa (bad): {parse_noqa(bare_usage) == 'bare noqa'}")
print(f"Specific noqa (good): {parse_noqa(specific_usage) == 'specific: E225'}")
Output:
URL kept: True
Side-effect import: True
Bare noqa (bad): True
Specific noqa (good): True
How it works:
The parse_noqa function checks for # noqa: (specific) before # noqa (bare), since the specific form contains the bare form as a substring. It extracts the code after the colon for the specific case.
When # noqa is appropriate:
# 1. A URL that cannot be split without changing meaning
DOCS_URL = "https://docs.example.com/api/v2/reference/authentication" # noqa: E501
# 2. Import for side effects — it's "unused" but intentional
import myapp.signals # noqa: F401
# 3. Generated code — do not edit manually
GENERATED_HASH = "a3f8c2d9e1b4" # noqa: E501
When # noqa is NOT appropriate:
# WRONG: silencing a real bug
result = calcualte_total() # noqa — should fix the typo, not silence F821
# WRONG: silencing everything lazily
x=1; y=2; z=x+y # noqa — should fix the style, not suppress all warnings
Key insight: A code review comment on a bare # noqa is always warranted. Ask: "What specific violation are you suppressing, and why?" The answer to that question should appear as a comment alongside the # noqa: CODE. A codebase with many bare # noqa comments is a codebase where nobody knows what the real style issues are.
Expected Output
URL kept: True\nSide-effect import: True\nBare noqa (bad): True\nSpecific noqa (good): TrueHints
Hint 1: # noqa suppresses all flake8 warnings on that line.
Hint 2: # noqa: E501 suppresses only the E501 warning on that line.
Hint 3: Always prefer the specific form: # noqa: CODE — bare # noqa is a code smell.
Hint 4: Legitimate uses: long URLs, side-effect imports, generated code.
Hard
Audit the code block below. Count how many violations of each type exist, then verify with the checker.
import os,sys # line 1
import json # line 2
x=1 # line 3
y=2 # line 4
def add(a,b): # line 5
return a+b # line 6
def mul(a,b): # line 7
if a==None: # line 8
return 0# line 9
return a*b # line 10
def count_violations(code):
"""Count PEP 8 violations by category."""
import re
lines = code.strip().split("\n")
counts = {"E225": 0, "E302": 0, "E401": 0, "E711": 0}
prev_blank_count = 0
for i, line in enumerate(lines):
stripped = line.strip()
# E401: multiple imports
if re.match(r'^import\s+\S+,', stripped):
counts["E401"] += 1
# E225: missing spaces around common operators (simplified)
# Check for x=1 style (not in def/class/import lines)
if not stripped.startswith(("def ", "class ", "import ", "from ", "return")):
if re.search(r'\w=[^=\s]', stripped) or re.search(r'[^=!<>]=\w', stripped):
counts["E225"] += 1
# Also check comparison operators without spaces
if re.search(r'\w==[^\s=]', stripped) or re.search(r'[^\s!]==[^\s]', stripped):
counts["E225"] += 1
# E302: top-level def/class needs 2 blank lines before it
if re.match(r'^def |^class ', stripped):
if i > 0 and prev_blank_count < 2:
counts["E302"] += 1
# E711: comparison to None
if "== None" in stripped or "!= None" in stripped:
counts["E711"] += 1
prev_blank_count = 0 if stripped else prev_blank_count + 1
return counts
code = """import os,sys
import json
x=1
y=2
def add(a,b):
return a+b
def mul(a,b):
if a==None:
return 0
return a*b"""
counts = count_violations(code)
for code_name, count in sorted(counts.items()):
if count > 0:
print(f"{code_name}: {count}")
print(f"total: {sum(counts.values())}")Solution
def count_violations(code):
import re
lines = code.strip().split("\n")
counts = {"E225": 0, "E302": 0, "E401": 0, "E711": 0}
prev_blank_count = 0
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r'^import\s+\S+,', stripped):
counts["E401"] += 1
if not stripped.startswith(("def ", "class ", "import ", "from ", "return")):
if re.search(r'\w=[^=\s]', stripped) or re.search(r'[^=!<>]=\w', stripped):
counts["E225"] += 1
if re.search(r'\w==[^\s=]', stripped) or re.search(r'[^\s!]==[^\s]', stripped):
counts["E225"] += 1
if re.match(r'^def |^class ', stripped):
if i > 0 and prev_blank_count < 2:
counts["E302"] += 1
if "== None" in stripped or "!= None" in stripped:
counts["E711"] += 1
prev_blank_count = 0 if stripped else prev_blank_count + 1
return counts
code = """import os,sys
import json
x=1
y=2
def add(a,b):
return a+b
def mul(a,b):
if a==None:
return 0
return a*b"""
counts = count_violations(code)
for code_name, count in sorted(counts.items()):
if count > 0:
print(f"{code_name}: {count}")
print(f"total: {sum(counts.values())}")
Output:
E225: 3
E302: 2
E401: 1
E711: 1
total: 7
Violation walkthrough:
| Line | Violation | Code |
|---|---|---|
1: import os,sys | Multiple imports on one line | E401 |
3: x=1 | No space around = | E225 |
4: y=2 | No space around = | E225 |
5: def add(a,b): | No 2 blank lines before top-level def | E302 |
7: def mul(a,b): | No 2 blank lines before top-level def | E302 |
8: if a==None: | Comparison with == instead of is | E711 |
8: if a==None: | No space around == | E225 |
There are also E231 violations (missing space after commas in add(a,b) and mul(a,b)) and E231 on import os,sys, but our checker only tracks the four categories above.
The fully corrected version:
import json
import os
import sys
x = 1
y = 2
def add(a, b):
return a + b
def mul(a, b):
if a is None:
return 0
return a * b
Key insight: Real flake8 would also report E231 (missing whitespace after ,) on lines 1, 5, and 7, and potentially W291 (trailing whitespace) and E501 (line too long). The seven violations we counted represent the most impactful categories — the ones that would be prioritized in a code review.
Expected Output
E225: 3\nE302: 2\nE401: 1\nE711: 1\ntotal: 7Hints
Hint 1: Walk through each line and apply each rule independently.
Hint 2: E302 fires before any top-level def or class that does not have 2 blank lines above it.
Hint 3: Count blank lines between definitions carefully — the rule requires exactly 2.
Hint 4: E711 is triggered by == None or != None anywhere in the line.
Rewrite this module to be fully PEP 8-compliant. The output must remain exactly the same — only the style changes. Then run the corrected version.
# Original — DO NOT RUN as-is (shown for reference)
# import sys,os
# import json
# from datetime import datetime
# DEFAULT_RATE=0.15
# class user_account:
# def __init__(self,name,balance):
# self.name=name
# self.balance=balance
# self.active=True
# def apply_discount(self,rate=None):
# if rate==None:
# rate=DEFAULT_RATE
# self.balance=self.balance*(1-rate)
# def deactivate(self):
# self.active=False
# def get_active_users(accounts):
# return [a for a in accounts if a.active==True]
# a1=user_account("alice",1000)
# a1.apply_discount()
# a2=user_account("bob",1000)
# active=get_active_users([a1,a2])
# for u in active:
# print(f"{u.name}: {u.balance}")
# print(f"active users: {len(active)}")
# TODO: Write the fully PEP 8-compliant version below
import os
import sys
import json
from datetime import datetime
DEFAULT_RATE = 0.15
class UserAccount:
def __init__(self, name, balance):
self.name = name
self.balance = balance
self.active = True
def apply_discount(self, rate=None):
if rate is None:
rate = DEFAULT_RATE
self.balance = self.balance * (1 - rate)
def deactivate(self):
self.active = False
def get_active_users(accounts):
return [a for a in accounts if a.active]
a1 = UserAccount("alice", 1000)
a1.apply_discount()
a2 = UserAccount("bob", 1000)
active = get_active_users([a1, a2])
for u in active:
print(f"{u.name}: {u.balance}")
print(f"active users: {len(active)}")Solution
import os
import sys
import json
from datetime import datetime
DEFAULT_RATE = 0.15
class UserAccount:
def __init__(self, name, balance):
self.name = name
self.balance = balance
self.active = True
def apply_discount(self, rate=None):
if rate is None:
rate = DEFAULT_RATE
self.balance = self.balance * (1 - rate)
def deactivate(self):
self.active = False
def get_active_users(accounts):
return [a for a in accounts if a.active]
a1 = UserAccount("alice", 1000)
a1.apply_discount()
a2 = UserAccount("bob", 1000)
active = get_active_users([a1, a2])
for u in active:
print(f"{u.name}: {u.balance}")
print(f"active users: {len(active)}")
Output:
alice: 850.0
bob: 1000
active users: 2
Every fix applied:
| Original | Fixed | Rule |
|---|---|---|
import sys,os | import os / import sys | E401, E401 + alphabetical |
DEFAULT_RATE=0.15 | DEFAULT_RATE = 0.15 | E225 |
class user_account: | class UserAccount: | Naming: N801 |
def __init__(self,name,balance): | def __init__(self, name, balance): | E231 |
self.name=name | self.name = name | E225 |
| No blank lines between methods | 1 blank line between each method | E301 |
if rate==None: | if rate is None: | E711 + E225 |
self.balance=self.balance*(1-rate) | self.balance = self.balance * (1 - rate) | E225 (×3) |
No blank lines before get_active_users | 2 blank lines | E302 |
if a.active==True | if a.active | E712 |
a1=user_account(...) | a1 = UserAccount(...) | E225 + class name |
Key insight: This exercise mirrors the typical state of code written quickly without a linter running. The violations cluster around the same three patterns: missing spaces around operators, missing blank lines, and wrong comparisons to None/True. Running black src/ and then flake8 src/ after would resolve all of them in under a second — but understanding each violation manually is what lets you catch them in code review and write clean code the first time.
Expected Output
alice: 850.0\nbob: 1000\nactive users: 2Hints
Hint 1: Fix all imports first: group them, sort alphabetically, one per line.
Hint 2: Then fix all E225 (spacing around operators), E302 (blank lines), E231 (spacing after commas).
Hint 3: Fix class names to CapWords, fix comparison to None.
Hint 4: The logic must remain identical — only style changes.
Refactor the three long lines below (each over 88 characters) to fit within 88 characters while keeping the output identical. Then run the refactored version to verify.
# Original long lines (each over 88 chars — violates E501)
# Line 1: long function call
def format_user_summary(first_name, last_name, email, subscription_tier, renewal_date):
return f"{first_name} {last_name} ({email}) — {subscription_tier} — renews {renewal_date}"
# Line 2: long error message string
error_message = "The payment could not be processed because the card was declined. Please check your billing information and try again."
# Line 3: long f-string (over 88 chars if written on one line)
user = type("User", (), {"first_name": "Alice", "renewal_date": "2026-12-01"})()
user_message = f"Hello {user.first_name}, your subscription renews on {user.renewal_date}."
# Verify: original outputs
result = format_user_summary("Ada", "Lovelace", "[email protected]", "paid", "2026-06-01")
print(len(result) > 50)
print(error_message)
print(user_message)Solution
# REFACTORED — all lines within 88 characters
def format_user_summary(
first_name,
last_name,
email,
subscription_tier,
renewal_date,
):
return (
f"{first_name} {last_name} ({email})"
f" — {subscription_tier} — renews {renewal_date}"
)
error_message = (
"The payment could not be processed because the card was declined. "
"Please check your billing information and try again."
)
user = type("User", (), {"first_name": "Alice", "renewal_date": "2026-12-01"})()
user_message = (
f"Hello {user.first_name}, your subscription renews on {user.renewal_date}."
)
print(len(result) > 50)
print(error_message)
print(user_message)
Output:
True
The payment could not be processed because the card was declined. Please check your billing information and try again.
Hello Alice, your subscription renews on 2026-12-01.
How each refactoring works:
Function signature: A function with many parameters should use a hanging indent — one parameter per line, closing ) on its own line with a trailing comma after the last parameter. This makes adding/removing parameters a single-line diff.
Long string: Two adjacent string literals in Python are implicitly concatenated at compile time. Wrapping them in parentheses and splitting across lines produces a single string with no runtime cost — no + operator needed.
Long f-string: Wrapping in parentheses allows the line to be split. Adjacent f-strings are also concatenated at compile time. The second fragment starts with a space to maintain the intended spacing.
Key insight: Never use backslash continuation (\) for line breaks in Python. Parentheses always produce cleaner code and are what Black generates. For function return values, return (part1 part2) uses implicit string concatenation inside the parentheses. For function signatures, the trailing comma after the last argument is the Black style and makes version control diffs cleaner.
Expected Output
True\nThe payment could not be processed because the card was declined. Please check your billing information and try again.\nHello Alice, your subscription renews on 2026-12-01.Hints
Hint 1: PEP 8 line limit is 79 chars; Black default is 88 chars. Both are valid in different projects.
Hint 2: For long strings: use implicit concatenation inside parentheses (no backslash needed).
Hint 3: For long function calls: use hanging indent with one argument per line.
Hint 4: For long f-strings: wrap in parentheses or split into parts.
