We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← From Everyday Python to Production Code step 10 of 223
Narrow a Union Without Treating Zero as Missing
You are writing a helper that turns a setting into text for a log line. The
setting can be a string, a number, a list of strings, or None. The obvious
first check is a trap:
def normalise(value: str | int | float | list[str] | None) -> str:
if not value:
return ""
...
mypy accepts this, because if not value rules out None. But not value is
also true for 0, 0.0, "" and [], so normalise(0) returns ""
instead of "0". When only None means missing, check value is None.
assert solve([None, 0, 0.0, "", [], ["", ""]]) == ["", "0", "0.00", "", "", ","]
assert solve([2.0, 2, True, False]) == ["2.00", "2", "True", "False"]
What to write
normalise(value: str | int | float | list[str] | None) -> str converts each
type like this:
| Input | Result |
|---|---|
None |
"" |
str |
The string, unchanged |
float |
f"{value:.2f}" |
int |
str(value) |
list[str] |
",".join(value) |
- Check
Nonewithis None. Then tellstr,floatandintapart withisinstance, and return from each branch. Once those branches have returned, mypy knows the value is alist[str], so the join needs nocast. - Use no
Any,castor# type: ignore. - Keep strings exactly as they are, including spaces and
"". - Join every list element in order, including empty strings. Do not sort, trim or drop elements.
solve(values: list[str | int | float | list[str] | None]) -> list[str]
returns a new list with normalise applied to each value, in order. It keeps
duplicates and leaves the input unchanged. solve([]) returns [].
Edge cases
boolis a subclass ofint, soTrueandFalsetake theintbranch and give"True"and"False". Do not turn them into1and0, and do not treatFalseas missing.2gives"2"and2.0gives"2.00". At runtimeisinstance(2, float)andisinstance(2.0, int)are bothFalse, so each number reaches its own branch.- Floats use Python's own formatting and rounding.
-0.0gives"-0.00", and2.675gives"2.67", because the stored float is slightly below 2.675. []gives"", and["", "a,b", ""]gives",a,b,".
The diagram follows a smaller int | str | None union. Each check removes a
type, and a return ends that branch, so later checks see only what is left.
Loading visualization…
Going further
- mypy narrows on truthiness too. For a
str | None, a value that failsif valueis eitherNoneor the empty string, and mypy tracks both. - mypy keeps an attribute such as
self.xnarrowed after you call a method, even if that method setsself.x = None. Copy the attribute to a local variable before you check it. - mypy's guide to narrowing lists the other checks mypy understands.
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.