We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 12 of 26
Build a Generic Result Class
Some functions report a failure by returning it instead of raising it. The
caller gets back a Result that holds either a value or an error, and checks
which one before using it. Build Result[T, E]. It is a generic class, which
is a class with a type parameter, like list[T]. T is the type of a success
value, and E is the type of an error.
number = Result[int, str].ok(7)
number.map(lambda n: f"<{n}>").unwrap() # "<7>"
number.unwrap() # 7
Result[int, str].err("missing").unwrap_or(-1) # -1
Result[None, str].ok(None).is_ok() # True
Result[None, str].ok(None).unwrap() # None
What to write
The starter uses the older spelling from the article "Generic Functions from
Zero", class Result(Generic[T, E]) with two TypeVar lines. Rewrite it as
class Result[T, E]:, and delete Generic and the TypeVar lines. Then add
these methods:
from collections.abc import Callable
class Result[T, E]:
@classmethod
def ok(cls, value: T) -> "Result[T, E]": ...
@classmethod
def err(cls, error: E) -> "Result[T, E]": ...
def is_ok(self) -> bool: ...
def unwrap(self) -> T: ...
def unwrap_or(self, default: T) -> T: ...
def map[U](self, f: Callable[[T], U]) -> "Result[U, E]": ...
map declares its own type parameter U, because f can change the success
type. The error type E stays the same.
Create results only through ok or err. Each result holds either a value or
an error, never both.
| Method | On a success | On an error |
|---|---|---|
is_ok() |
Return True. |
Return False. |
unwrap() |
Return the value. | Raise ValueError. |
unwrap_or(default) |
Return the value. | Return default. |
map(f) |
Call f once on the value, and return a new success that holds its result. |
Do not call f. Return a new result that holds the same error. |
mapleaves the original result unchanged. Iffraises, let the exception propagate.None,0,Falseand""are valid success values.ok(None)is a success, and itsunwrap()returnsNone.- Keep the quotes around
"Result[T, E]"in the return types. Python 3.12 evaluates each annotation when it defines the method, and the nameResultdoes not exist yet at that point. - Write both type arguments when you create a result, as in
Result[int, str].ok(7). A bareResult.ok(7)gives mypy no error type, so mypy infersResult[int, Never].
Store the value so that None still counts
A single value: T | None attribute cannot tell ok(None) apart from an
error. Store each side as a tuple instead. self._value holds (value,) on a
success and () on an error, with the type tuple[T] | tuple[()].
self._error works the same way, with the type tuple[E] | tuple[()]. After
if len(self._value) == 1:, mypy narrows self._value to tuple[T], so
self._value[0] has type T. Another design passes too, as long as it keeps
ok(None) and an error apart.
What solve returns
def safe_div(numerator: int, denominator: int) -> Result[int, str]:
...
def solve(values: list[int], divisor: int) -> tuple[list[str], list[bool], list[int]]:
...
safe_divreturns an error that holds"division by zero"whendenominatoris zero.- Otherwise it returns a success that holds
numerator // denominator. Floor division rounds down, toward negative infinity, so-3 // 2 == -2and7 // -2 == -4.
solve calls safe_div(value, divisor) for each value, and returns three
lists in this order:
- Each result mapped with
f"<{n}>", then unwrapped with the default"n/a". - Each result's
is_ok(). - Each result unwrapped with the default
-1.
Empty values gives ([], [], []), even when divisor is zero.
solve([10, 7, -3], 2) # (["<5>", "<3>", "<-2>"], [True, True, True], [5, 3, -2])
solve([10], 0) # (["n/a"], [False], [-1])
Going further
from __future__ import annotations at the top of the file also fixes the
self-reference, because it makes Python store every annotation as a string.
Python 3.14 evaluates annotations only when code reads them, so the
quotes are optional there. See Python's
type parameter reference
and the mypy guide to generics.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.