pythonpylance

reportAssignmentType Pylance error when using TypeVar


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


Solution

  • 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