Python Type Hints Quiz: mypy, PEP 8 and Logging Practice

Test yourself: 12 questions and three programs on Python type hints and narrowing, PEP 8 naming, logging levels and handlers, and Pythonic idioms.

  • Course: Python study plan
  • Module: Type hints and code quality
  • Kind: Checkpoint — cleared at 70%
  • Reading time: 22 min
  • Runtime: CPython 3.11

Checkpoint — Type hints and code quality is the checkpoint that closes the Type hints and code quality module: a graded quiz and whole-program exercises, passed at 70%.

Instructions

This checkpoint covers the whole module: the hint vocabulary — built-in generics, unions, Callable, TypeVar, Generic, ClassVar, Final, Literal, TypedDict, NewType — with parameters hinted wide and returns narrow; what a type checker verifies and how narrowing, cast, TYPE_CHECKING and get_type_hints work; PEP 8 layout, naming, docstrings and the linter/formatter workflow; logging with levels, the logger hierarchy, formats and handlers; and the idioms and anti-patterns that make code Pythonic.

How it works. Twelve questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • Which hint accepts a list, a tuple and a generator of ints, and which requires indexing?
  • What does Optional[int] mean, and what does it not mean?
  • How does an is None test change what the checker allows afterwards?
  • Which naming style applies to functions, classes and constants?
  • Why must a library never call logging.basicConfig?
  • Why write log.debug("%s", x) rather than an f-string?
  • Which five anti-patterns hide bugs rather than just looking foreign?

The three programs are a run-time validator driven by get_type_hints that handles unions and Literals, a linter-lite that flags PEP 8 naming and idiom violations line by line, and a logging pipeline whose records are routed by level to two in-memory handlers with different formats.

Common questions

Which type hint accepts a list, a tuple and a generator, and which requires indexing?

Iterable[int] accepts all three, because each can be looped over; use it when a function only iterates. Sequence[int] requires indexing and slicing, so it accepts lists and tuples but not a generator. Both come from collections.abc.

How does an is None test change what a type checker allows?

It narrows the type. After if x is None: return 0, a value hinted int | None is treated as int for the rest of the function, so calling an int method on it, which was an error before the test, is allowed.

Why must a library never call logging.basicConfig?

Configuring handlers, levels and formats belongs to the application that imports the library, which does it once at start-up. A library that calls basicConfig hijacks the application's output and can make the application's own later call a silent no-op, since basicConfig does nothing once the root logger has handlers.

Exercises

A validator that reads the hints

Given hinted functions greet(name: str, times: int = 1), scale(x: float, by: int | None = None), open_mode(mode: Literal["r", "w"]) and total(xs: list[int]), write check(f, *args) that reads typing.get_type_hints(f) and validates each positional argument: a plain class with isinstance (an int satisfies float; a bool never satisfies int or float); a union (get_origin is types.UnionType) if any member accepts; Literal if the value is one of get_args; list[T] if it is a list whose every element satisfies T. Missing arguments with defaults are fine. Each input line is a name followed by comma-separated argument literals; print ok <result> or type error: <param>.

Input: lines. Output: one line per call.

greet "ada", 2
scale 3, None
open_mode "x"
total [1, 2, "3"]

prints

ok Hello, ada! Hello, ada!
ok 3
type error: mode
type error: xs

Linter-lite

Read a Python source and report, per line, every rule it breaks (comma-separated, in rule order): tab, long (over 79 characters), trailing-space, def-name (not snake_case), class-name (not PascalCase), none-compare (== None/!= None), bare-except (except:), mutable-default (=[], ={} or =set() inside a def line's parameters), star-import (import *). Print line <n>: <rules> for each offending line, then problems <total rules>.

Input: source lines. Output: the report and the count.

from os import *
def Load(xs=[]):
    try:
        pass
    except:
        pass

prints

line 1: star-import
line 2: def-name, mutable-default
line 5: bare-except
problems 4

Routed logging

Build a logger svc at DEBUG with two StringIO handlers: errors at ERROR with format %(levelname)s %(name)s: %(message)s, and all at DEBUG with %(levelname)s: %(message)s. Process lines LEVEL message… through svc, with the special line fail message… raising and catching a RuntimeError and logging it with log.exception (which appends the traceback; count its records but print only their first line). Afterwards print --- errors ---, the first line of each record in the errors buffer, --- all ---, the first line of each record in the all buffer, then records <n> for the all buffer.

Input: lines. Output: as described.

info started
debug detail
fail disk full
error plain error

prints

--- errors ---
ERROR svc: disk full
ERROR svc: plain error
--- all ---
INFO: started
DEBUG: detail
ERROR: disk full
ERROR: plain error
records 4

In this module: Type hints and code quality

← Writing idiomatic Python — the idioms, the anti-patterns and a refactoring · Decorators — functions that wrap functions →