-
I'm trying to add type annotations to a function used for config validation. def ensure_list(value):
"""Wrap value in list if it is not one."""
if value is None:
return []
return value if isinstance(value, list) else [value] At first glance it looks strait forward, but I'm struggling with some edge cases. This is what I've got so far from typing import Any, TypeVar, overload
T = TypeVar("T")
@overload
def ensure_list(value: None) -> list[Any]:
...
@overload
def ensure_list(value: list[T]) -> list[T]:
...
@overload
def ensure_list(value: T) -> list[T]:
...
def ensure_list(value: T) -> list[T] | list[Any]:
"""Wrap value in list if it is not one."""
if value is None:
return []
return value if isinstance(value, list) else [value] My test cases class X:
a1: str
a2: None
a3: list[int]
b1: list[int] | int
b2: str | list[str]
def __init__(self) -> None:
reveal_type(ensure_list(X.a1)) # should be 'list[str]'
reveal_type(ensure_list(X.a2)) # should be 'list[Any]'
reveal_type(ensure_list(X.a3)) # should be 'list[int]'
reveal_type(ensure_list(X.b1)) # should be 'list[int]'
reveal_type(ensure_list(X.b2)) # should be 'list[str]'
-- T1 = TypeVar("T1")
T2 = TypeVar("T2")
@overload
def ensure_list(value: list[T1] | T2) -> list[T1] | list[T2]:
...
@overload
def ensure_list(value: T1 | list[T2]) -> list[T1] | list[T2]:
... Mypy outputAfter the last one
Between 2 and 3
Pyright outputAfter the last one
Between 2 and 3
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
Any time you attempt to use a type variable (especially when it's unbound and unconstrained) in a union with other types, you're likely to run into problems. This inevitably generates ambiguities for the constraint solver. Take for example the annotation @overload
def ensure_list(value: None) -> list[Any]:
...
@overload
def ensure_list(value: list[T] | T) -> list[T]:
...
def ensure_list(value: list[T] | T | None) -> list[T] | list[Any]:
"""Wrap value in list if it is not one."""
if value is None:
return []
return cast(list[T], value) if isinstance(value, list) else [value] Note that I needed to add a The above sample mostly works for mypy, but it's emitting an error for |
Beta Was this translation helpful? Give feedback.
-
By coincidence I found an overload variation that works in both mypy and pyright. @overload
def ensure_list(value: None) -> list[Any]: ...
@overload
def ensure_list(value: list[T]) -> list[T]: ...
@overload
def ensure_list(value: list[T] | T) -> list[T]: ...
def ensure_list(value: T | None) -> list[T] | list[Any]: ... Extracting the concrete overload
Test casesclass X:
a1: str
a2: None
a3: list[int]
b1: list[int] | int
b2: str | list[str]
def __init__(self) -> None:
reveal_type(ensure_list(X.a1)) # should be 'list[str]'
reveal_type(ensure_list(X.a2)) # should be 'list[Any]'
reveal_type(ensure_list(X.a3)) # should be 'list[int]'
reveal_type(ensure_list(X.b1)) # should be 'list[int]'
reveal_type(ensure_list(X.b2)) # should be 'list[str]'
class Y:
c1: list
c2: list[Any]
c3: list[None]
def __init__(self) -> None:
reveal_type(ensure_list(Y.c1)) # should be 'list[Any]' or 'list[Unknown]'
reveal_type(ensure_list(Y.c2)) # should be 'list[Any]'
reveal_type(ensure_list(Y.c3)) # should be 'list[None]' Mypy output
Pyright output
|
Beta Was this translation helpful? Give feedback.
By coincidence I found an overload variation that works in both mypy and pyright.
Extracting the concrete overload
list[T] -> list[T]
while also keeping the union one Eric suggestedlist[T] | T - > list[T]
seems to work. Note the implementation type forvalue
is onlyT | None
. Unfortunatelylist[T] | T | None
doesn't work in mypy although arguably a bit more fitting.T | None
does work though in bothmypy
andpyright
.