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.