Home > Blockchain >  How to use typehinting for a generic type when you need to create instances of that type in Python
How to use typehinting for a generic type when you need to create instances of that type in Python

Time:10-24

I'm making a class that gets dictionaries and creates an instance of a given class. The current version works but you need to tell the class twice what type it should convert the dictionaries to.

I could just get rid of the Generic but I'd like to keep using type hints.

from typing import Dict, Generic, List, Type, TypeVar

T = TypeVar("T")

class DictsToObjectsParser(Generic[T]):
    def __init__(self, object_type: Type[T]):
        self.object_type = object_type

    def try_to_parse_dicts_to_objects(self, list_dict: List[Dict]) -> List[T]:
        object_list: List[T] = []
        for my_dict in list_dict:
            parsed_object: T = self.object_type(**my_dict)
            object_list.append(parsed_object)
        return object_list

    @staticmethod
    def create_instance():
        return DictsToObjectsParser[MyClass](MyClass)

Do I really need to tell this class twice what type I want?

If there is no way around it, is there a way to check whether T and self.object_type are the same (preferably in the constructor)?

CodePudding user response:

It makes sense to tell it twice, both type checker and interpreter need to know. It may be possible for the interpreter to access type annotation at runtime, but I would not do it.

Also, many things are wrong with your approach, and if you fix them all you will arrive at cattrs. So I suggest to use cattrs immediately, cattrs is great and does all things.

CodePudding user response:

No, there is no need:

from typing import Generic, TypeVar

T = TypeVar("T")

class DictsToObjectsParser(Generic[T]):
    def __init__(self, object_type: type[T]) -> None:
        self.object_type = object_type

reveal_type(DictsToObjectsParser(int))
reveal_type(DictsToObjectsParser(str))

Running mypy --strict over this piece of code gives the following:

[...].py:9: note: Revealed type is "[...].DictsToObjectsParser[builtins.int]"
[...].py:10: note: Revealed type is "[...].DictsToObjectsParser[builtins.str]"

If you absolutely want that create_instance method, just make it a @classmethod instead of a @staticmethod. It makes sense; after all, you are using that specific class in it:

from __future__ import annotations
from typing import Generic, TypeVar

T = TypeVar("T")

class DictsToObjectsParser(Generic[T]):
    def __init__(self, object_type: type[T]) -> None:
        self.object_type = object_type

    @classmethod
    def create_instance(cls, object_type: type[T]) -> DictsToObjectsParser[T]:
        return cls(object_type)

reveal_type(DictsToObjectsParser.create_instance(float))

Again we get the following with mypy --strict:

[...].py:14: note: Revealed type is "[...].DictsToObjectsParser[builtins.float]"

Due to the scoping rules for type variables (PEP 484), the type variable T parameterizing your DictsToObjectsParser is always bound to any parameter of any method of DictsToObjectsParser using T. That can be __init__ or it can be any other method. The point is, once you call that method, and if the type variable T is not yet specified, the type of T is immediately inferred from the argument you pass to that method.

Note that this also means that you can't then call for example create_instance from a concrete instance of DictsToObjectsParser and pass a different type:

from __future__ import annotations
from typing import Generic, TypeVar

T = TypeVar("T")

class DictsToObjectsParser(Generic[T]):
    def __init__(self, object_type: type[T]) -> None:
        self.object_type = object_type

    @classmethod
    def create_instance(cls, object_type: type[T]) -> DictsToObjectsParser[T]:
        return cls(object_type)

obj = DictsToObjectsParser(float)
obj.create_instance(str)

This causes mypy to complain:

[...].py:15: error: Argument 1 to "create_instance" of "DictsToObjectsParser" has incompatible type "Type[str]"; expected "Type[float]"  [arg-type]

Hope this helps.

  • Related