Skip to main content

Python PEP8 and Style Practice Problems & Exercises

Practice: PEP8 and Style

11 problems4 Easy4 Medium3 Hard40-55 min
← Back to lesson

Easy

#1Spot the E225: Missing Whitespace Around OperatorsEasy
E225whitespaceoperatorsflake8

Predict the output. A checker function scans lines for the most common E225 patterns and reports whether each line is clean or violating.

Python
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 incidentally a+b also 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\nClean
Hints

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.

#2Fix E302: Expected 2 Blank Lines Before FunctionEasy
E302blank-linestop-levelflake8

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.

Python
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 (before first)
  • Line 4: E302 expected 2 blank lines, found 0 (before second)
  • 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\n3
Hints

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.

#3E401: One Import Per LineEasy
E401importsone-per-lineflake8

Predict the output. A checker function identifies E401 violations — multiple module imports on one line.

Python
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, exists
Hints

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.

#4E711 and E712: Comparison to None and BooleansEasy
E711E712Noneis Noneboolean-comparison

Predict the output. A checker function flags E711 (== None) and E712 (== True/== False) violations.

Python
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\nClean
Hints

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

#5Import Ordering: Three-Group StructureMedium
importsisortstdlibthird-partylocal

Sort and group imports correctly. Given a mixed list of imports, categorize them into PEP 8 groups and sort each group alphabetically.

Python
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+.

#6E211 and E231: Whitespace Around Function Calls and CommasMedium
E211E231whitespacefunction-callscommas

Predict the output. Despite E211 and E231 violations in the code, predict what each line prints.

Python
# 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.

#7Naming Conventions: CapWords, snake_case, UPPER_CONSTMedium
namingCapWordssnake_caseconstantsconventions

Predict the output. A naming convention checker validates identifiers against PEP 8 rules.

Python
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:

  1. UserProfile — valid CapWords class name. Starts uppercase, no underscores.
  2. get_user_by_id — valid snake_case function. All lowercase, underscores between words.
  3. MAX_RETRIES — valid UPPER_SNAKE_CASE constant.
  4. _internal_helper — valid private function (leading _ + snake_case).
  5. userProfile — WRONG for a class. Starts lowercase. This is camelCase (Java-style), not CapWords.
  6. GetUserById — WRONG for a function. This is CapWords, which is the class convention.

PEP 8 naming summary:

ContextConventionExample
ClassCapWordsHttpClient, UserProfile
Function / methodsnake_caseget_user, process_data
Variablesnake_casetotal_count, user_id
Module-level constantUPPER_SNAKE_CASEMAX_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\nFalse
Hints

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.

#8noqa: When to Use It and HowMedium
noqaflake8suppressionpragmas

Demonstrate correct # noqa usage. The function below parses noqa comments and classifies them as bare (bad practice) or specific (good practice).

Python
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): True
Hints

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

#9Full Style Audit: Count All Violations by CategoryHard
flake8auditE225E302E401E711comprehensive

Audit the code block below. Count how many violations of each type exist, then verify with the checker.

Python
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
Python
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:

LineViolationCode
1: import os,sysMultiple imports on one lineE401
3: x=1No space around =E225
4: y=2No space around =E225
5: def add(a,b):No 2 blank lines before top-level defE302
7: def mul(a,b):No 2 blank lines before top-level defE302
8: if a==None:Comparison with == instead of isE711
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: 7
Hints

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.

#10Rewrite: Fix a Realistic Messy ModuleHard
rewritecomprehensiveE302E225E711namingimports

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.

Python
# 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:

OriginalFixedRule
import sys,osimport os / import sysE401, E401 + alphabetical
DEFAULT_RATE=0.15DEFAULT_RATE = 0.15E225
class user_account:class UserAccount:Naming: N801
def __init__(self,name,balance):def __init__(self, name, balance):E231
self.name=nameself.name = nameE225
No blank lines between methods1 blank line between each methodE301
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_users2 blank linesE302
if a.active==Trueif a.activeE712
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: 2
Hints

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.

#11Line Length: Refactor Long Lines Without Breaking LogicHard
E501line-lengthcontinuationimplicit-concatenation

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.

Python
# 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}."
)

result = format_user_summary("Ada", "Lovelace", "[email protected]", "paid", "2026-06-01")
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.

© 2026 EngineersOfAI. All rights reserved.