We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 13 of 26
Limit a Type Parameter with Bounds and Constraints
Write two generic helpers. clamp keeps a number inside a range, and
dedupe_sorted removes repeated words. A plain [T] accepts any type, but
clamp must compare its arguments and dedupe_sorted must put items in a
set. So each helper limits what T can be, in a different way.
clamp(-3, 0, 10) # 0
clamp(11.5, 0.0, 10.0) # 10.0
dedupe_sorted(["c", "b", "c", "a"]) # ["c", "b", "a"]
Two ways to limit a type parameter
A bound says that T can be this type or any subtype of it. A subtype
can go anywhere the wider type is expected, as a bool can go anywhere an
int can. With a bound, mypy keeps the exact type the caller passed:
def keep_int[T: int](value: T) -> T:
return value
reveal_type(keep_int(True)) # bool, a subtype of int
A constraint says that T must be exactly one of the listed types. mypy
picks one of them, even when the caller passes a subtype:
def keep_one[T: (int, str)](value: T) -> T:
return value
reveal_type(keep_one(True)) # int, not bool
dedupe_sorted uses the bound Hashable, so a list of a str subclass comes
back as a list of that subclass. clamp uses the constraint (int, float),
so all three arguments share one of those two types.
mypy lets you pass an int where a float is expected. So
clamp(1, 0.0, 10) passes mypy, with T as float. Python converts nothing
at runtime, though, and that call returns the int 1. That is why solve
converts its endpoints to float itself.
What to write
from collections.abc import Hashable, Iterable
def clamp[T: (int, float)](value: T, low: T, high: T) -> T:
...
def dedupe_sorted[T: Hashable](items: Iterable[T]) -> list[T]:
...
clamp:
- If
low > high, raiseValueError("low must not exceed high"). Check this first, before you comparevalue. - Return
lowwhenvalueis belowlow, andhighwhenvalueis abovehigh. Otherwise returnvalueunchanged. - Both ends are inclusive, so
clamp(10, 0, 10)returns10. Whenlowequalshigh, every value becomes that number.
dedupe_sorted:
- Keep the first occurrence of each item, in the order the items arrive, and do not sort. Sorted input stays sorted, and unsorted input keeps its order.
- Read
itemsonce. Use a set to remember the items you have seen. - Return the original item objects, and leave the input unchanged.
What solve returns
def solve(
numbers: list[int],
reals: list[float],
low: int,
high: int,
words: list[str],
) -> tuple[list[int], list[float], list[str]]:
...
Return a tuple of three lists, each in input order:
- Each of
numbers, clamped tolowandhigh. - Each of
reals, clamped tofloat(low)andfloat(high). The conversion makes an endpoint come back as a float, such as10.0, not10. dedupe_sorted(words).
- Let a
ValueErrorfromclamppropagate. - If
numbersandrealsare both empty,clampnever runs. Return the two empty lists and the deduplicated words, even whenlow > high.
solve([-2, 7], [-0.5, 12.0], 0, 10, ["b", "a", "b"])
# ([0, 7], [0.0, 10.0], ["b", "a"])
Going further
A constraint also rejects calls that mix the listed types. With
def concat[S: (str, bytes)](a: S, b: S) -> S, mypy rejects
concat("a", b"b"), because no single listed type fits both arguments. The
typing specification covers
type parameters and
the rule for int and float.
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.