I'm trying to make a function that replaces some substrings inside strings with a preassigned value,
and I've extended it so that it works for list, dict, and tuple as well.
I used TypeVar to denote that the input type and output types are the same, but I keep getting pylance warning messages.
How should I type hint the following code so I don't get these messages?
T = TypeVar("T")
PLACEHOLDERS = ["1", "2", "3"]
VALUE = "value"
def replace_string(obj: T) -> T:
if isinstance(obj, str):
for placeholder in PLACEHOLDERS:
# Cannot access attribute "replace" for class "object*"
# Attribute "replace" is unknown, Type "str | Unknown" is not assignable to declared type "T@replace_string"
obj = obj.replace(placeholder, VALUE)
elif isinstance(obj, list):
# Type "list[Unknown]" is not assignable to declared type "T@replace_string"
obj = [replace_string(item) for item in obj]
elif isinstance(obj, tuple):
# Type "tuple[Unknown, ...]" is not assignable to declared type "T@replace_string"
obj = tuple(replace_string(item) for item in obj)
elif isinstance(obj, dict):
# Type "dict[Unknown, Unknown]" is not assignable to declared type "T@replace_string"
obj = {key: replace_string(value) for key, value in obj.items()}
return obj
You're getting Pylance warnings because you're using a generic TypeVar("T")
that promises to return the same type as the input, but inside the function you're sometimes transforming the input to a different type.
So instead of using a generic TypeVar
, you can use a union of known supported types for both input and return types:
from typing import Union, List, Tuple, Dict
PLACEHOLDERS = ["1", "2", "3"]
VALUE = "value"
Nested = Union[
str,
List["Nested"],
Tuple["Nested", ...],
Dict[str, "Nested"]
]
def replace_string(obj: Nested) -> Nested:
if isinstance(obj, str):
for placeholder in PLACEHOLDERS:
obj = obj.replace(placeholder, VALUE)
return obj
elif isinstance(obj, list):
return [replace_string(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(replace_string(item) for item in obj)
elif isinstance(obj, dict):
return {key: replace_string(value) for key, value in obj.items()}
return obj
This gives Pylance all the info it needs to type check correctly and stops the "Unknown" warnings.
The correct type hint If function accepts any type then use:
from typing import Any
def replace_string(obj: Any) -> Any:
## rest code will be the same
Other option:
from typing import Union, List, Tuple, Dict, Any
Nested = Union[
str,
List["Nested"],
Tuple["Nested", ...],
Dict[Any, "Nested"],
Any # fallback for unsupported types
]
def replace_string(obj: Nested) -> Nested:
# Same code