Asked 1 month ago by PulsarHunter171
How can I annotate a function that conditionally transforms str inputs while preserving its type?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by PulsarHunter171
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I have a function that accepts a single argument and guarantees to return a value of the same type as the argument. Inside the function, if the argument is of type str, it applies a transformation using a helper function; otherwise, it returns the argument unchanged.
A minimal example is shown below:
PYTHONfrom typing import TypeVar T = TypeVar("T") S = TypeVar('S', bound=str) def str_transform(s: S) -> S: return s def take_any_transform_only_str(obj: T) -> T: if isinstance(obj, str): return str_transform(obj) return obj
However, mypy (v1.15.0) emits the following error:
BASHtest.py:11: error: Incompatible return value type (got "str", expected "T") [return-value]
What is the correct way to annotate this function? I would prefer a solution based on type variables rather than manually annotating different cases (which might require overloads).
Comments 0
•Answers 2
•Views 81
Answer by TychoAI Agent • 1 month ago
The solution is to use overloads. In this case, you need to tell the type checker that when the argument is a str, the function returns a str, and in all other cases the type remains unchanged. For example:
PYTHONfrom typing import overload, TypeVar T = TypeVar("T") @overload def take_any_transform_only_str(obj: str) -> str: ... @overload def take_any_transform_only_str(obj: T) -> T: ... def take_any_transform_only_str(obj): if isinstance(obj, str): return str_transform(obj) return obj
With the above overloads, mypy understands that if a string is passed into the function, a string will be returned, and otherwise the return type will match the type of the argument. This resolves the type mismatch error.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by EclipseSeeker530 • 1 month ago
mypy
does not track the relationship between the types of symbols. Unfortunately, it means that it is unable to detect that T
is str
or a subclass of str
in the isinstance()
branch of your function.
If you do not want to use overload, then you could try a typing.cast()
. It is a useful escape hatch when you can determine something that a type checker is unable to. The type checker just takes your word that type safety is satisfied. At runtime, cast()
is an identity function that returns its second argument without performing any checks. As such, using cast()
will mask any errors should the return type of str_transform()
be changed. This means using typing.overload
is safer, if more verbose.
Example usage:
PYTHONreturn typing.cast(T, str_transform(obj))
Using overload
is the safer alternative as it means mypy can alert you if the return type of str_transform()
changes in a way that is incompatible with take_any_transform_only_str()
. However, it requires a somewhat strange overload to work as desired.
PYTHON@overload def take_any_transform_only_str(obj: S) -> S: # Using `S` over `str` in the overload means mypy will # preserve the type of subclasses of `str` pass @overload def take_any_transform_only_str(obj: T) -> T: pass def take_any_transform_only_str(obj: T | str) -> T | str: # `| str` is an escape hatch to allow returning `str` when the type # of` obj` has been narrowed to `str`. mypy would complain even if # the return type was `S`, as `S` is not the same as `str` if isinstance(obj, str): return str_transform(obj) else: return obj
This preserves the types as desired: eg.
PYTHONclass MyStr(str): pass reveal_type(take_any_transform_only_str(123)) # note: Revealed type is "int" reveal_type(take_any_transform_only_str('')) # note: Revealed type is "str" # Shows that types which are subclasses of str are preserverd reveal_type(take_any_transform_only_str(MyStr())) # note: Revealed type is "MyStr" # Also shows that complex types are preserved and allowed x: int | str = '' reveal_type(take_any_transform_only_str(x)) # note: Revealed type is "Union[int, str]"
No comments yet.
No comments yet.