We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 4 of 26
Limit a Parameter to a Few Exact Values with Literal
You are writing the resize step of an image service. Callers choose a fit
mode by name, and a typo such as "covr" should fail before the code runs. A
Literal type does that. Literal["contain", "cover", "stretch"] allows only
these three exact strings, so mypy rejects the typo:
resize((800, 600), (400, 400), "covr")
Argument 3 to "resize" has incompatible type "Literal['covr']"; expected "Literal['contain', 'cover', 'stretch']" [arg-type]
The starter gives you a name for the type and a default:
from typing import Final, Literal
type Fit = Literal["contain", "cover", "stretch"]
DEFAULT_FIT: Final = "contain"
type Fit = ... creates a type alias, a name for a type, so you can write
fit: Fit. Final means mypy rejects any later assignment to DEFAULT_FIT.
assert resize((800, 600), (400, 400)) == (400, 300)
assert resize((800, 600), (400, 400), "cover") == (533, 400)
assert resize((800, 600), (400, 400), "stretch") == (400, 400)
assert resize((1000, 1), (10, 10)) == (10, 1)
assert resize((2, 1), (5, 5)) == (5, 2)
What to write
def resize(
image: tuple[int, int],
target: tuple[int, int],
fit: Fit = DEFAULT_FIT,
) -> tuple[int, int]:
...
image and target are (width, height) pairs of positive integers. Call
the image size w, h and the target size tw, th.
| Mode | Result |
|---|---|
contain |
Scale the image by min(tw / w, th / h), so it fits inside the target. |
cover |
Scale the image by max(tw / w, th / h), so it fills the target and may stick out on one side. |
stretch |
Return (tw, th). |
- Annotate
fitasFit, withDEFAULT_FITas its default. Omittingfitmeans contain. - For contain and cover, compute each side as
max(1, round(side * factor)), using/division. - Use one factor for both sides, and apply it to the original width and height. Do not work out one side from the other side's rounded result.
- Return a tuple in
(width, height)order. You may scale up as well as down.
Handle the modes in a match statement. It compares fit with each case
in turn and runs the first one that matches:
match fit:
case "contain":
...
case "cover":
...
case "stretch":
...
case _:
assert_never(fit)
case _ matches whatever is left. assert_never comes from typing, and
it makes mypy report an error if any mode can reach that line. So a mode you
forget to handle fails mypy. The next exercise explains how it
works.
The diagram follows fit through the three comparisons. Choose else at
each step, and no mode is left for case _. Choose then to follow one
mode into its branch.
Loading visualization…
Edge cases
roundrounds an exact half to the even integer, soround(2.5)is2andround(3.5)is4. That is whyresize((2, 1), (5, 5))gives(5, 2)and not(5, 3).- A thin image keeps at least one pixel on each side, so
resize((1000, 1), (10, 10))gives(10, 1).
Keep the literal type
mypy gives a plain variable the wider type str. So this call fails, even
though the value is valid:
mode = "cover"
resize((800, 600), (400, 400), mode) # incompatible type "str"
Write mode: Fit = "cover", or pass the string directly. The default works
the same way. DEFAULT_FIT: Final = "contain" keeps the type
Literal["contain"]. DEFAULT_FIT: Final[str] = "contain" makes it a str,
and mypy then rejects it as the default for fit.
Going further
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.