I'm encountering an issue with my Python code using Pyright type checker, specifically when I try to remove Any from the return type declaration.
Error message:
Function with declared return type "int" must return value on all code paths. "None" is incompatible with "int".
Now every single path returns an int at some point, is this a bug or I just have to use the Union thing as a python thing.
from dataclasses import dataclass
from typing import Any, Union, List
@dataclass
class IntOrString:
I: Union[int, None] = None
S: Union[str, None] = None
def funny_sum(xs: List[IntOrString]) -> int | Any:
if not xs:
return 0
head, *tail = xs
match head:
case IntOrString(I=i) if i is not None:
return i + funny_sum(tail)
case IntOrString(S=s) if s is not None:
return len(s) + funny_sum(tail)
xs: List[IntOrString] = [IntOrString(I=1), IntOrString(S="hello"), IntOrString(I=3), IntOrString(S="world")]
# Output will be 1 + 5 + 3 + 5 = 14
result = funny_sum(xs)
Error
def funny_sum(xs: List[IntOrString]) -> int: { ... }
In Swift, enums support pattern matching, and this code would work fine. Does this mean that data classes in Python do not support pattern matching?
I appreciate any advice on resolving this issue with the return type declaration in Python.
Refer to this question for a similar approach that results in the same error: Handling Python Type Checker Errors with Return Type Declarations.
The reason the type checker warns about the function possibly returning None
is because not all patterns are exhausted.
If only (exactly) 1 of the fields of head
is None
, then one of the cases is bound to be matched. However, what happend if both I
and S
are None
?
In that case, non of the cases will match and the function will return None
.
Since Python's dataclass
doesn't allow defining mutually exclusive types, you have 2 options with this.
Either add a default case which should throw an error or you can change the IntOrString
to have a single field of type str | int
.
def funny_sum(xs: List[IntOrString]) -> int | Any:
if not xs:
return 0
head, *tail = xs
match head:
case IntOrString(I=i) if i is not None:
return i + funny_sum(tail)
case IntOrString(S=s) if s is not None:
return len(s) + funny_sum(tail)
case _:
raise ValueError("Either I or S must be set!")
xs: List[IntOrString] = [IntOrString(I=1), IntOrString(S="hello"), IntOrString(I=3), IntOrString(S="world")]
# Output will be 1 + 5 + 3 + 5 = 14
result = funny_sum(xs)
from dataclasses import dataclass
from typing import Any, Union, List
@dataclass
class IntOrString:
V: Union[int, str]
def funny_sum(xs: List[IntOrString]) -> int | Any:
if not xs:
return 0
head, *tail = xs
match head.V:
case int() as i:
return i + funny_sum(tail)
case str() as s:
return len(s) + funny_sum(tail)
xs: List[IntOrString] = [IntOrString(V=1), IntOrString(V="hello"), IntOrString(V=3), IntOrString(V="world")]
# Output will be 1 + 5 + 3 + 5 = 14
result = funny_sum(xs)
You can force the type-checker to ignore the inferred type and instead use the type you tell it.
def funny_sum(xs: List[IntOrString]) -> int: # pyright: ignore
...