6

I'm trying to define a class that takes another class as an attribute _model and will instantiate objects of that class.

from abc import ABC
from typing import Generic, TypeVar, Any, ClassVar, Type

Item = TypeVar("Item", bound=Any)


class SomeClass(Generic[Item], ABC):
    _model: ClassVar[Type[Item]]

    def _compose_item(self, **attrs: Any) -> Item:
        return self._model(**attrs)

I think it should be obvious that self._model(**attrs) returns an instance of Item, since _model is explicitly declared as Type[Item] and attrs is declared as Dict[str, Any].

But what I'm getting from mypy 0.910 is:

test.py: note: In member "_compose_item" of class "SomeClass":
test.py:11: error: Returning Any from function declared to return "Item"
            return self._model(**attrs)
            ^

What am I doing wrong?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
jpmelos
  • 3,283
  • 3
  • 23
  • 30
  • 1
    you know `**attrs: Any` is losing almost all type safety anyway? This is related to alex's answer: the `__init__` of the `Item` can have any signature. Have you considered being more specific e.g. with `Callable[[int, str, bool], Item]` (with the appropriate arguments types)? – joel Sep 26 '21 at 23:09
  • 1
    This: `bound=Any` really doesn't make any sense... It should just not have a bound. `Any` is a special type that basically means "don't type check this". I don't think it is even defined how it is supposed to work as a bound for a TypeVar. – juanpa.arrivillaga Sep 26 '21 at 23:25
  • Also, what version of mypy are you using? On my machine, `mypy 0.910` doesn't actually complain... but as I stated, I don't think `Item = TypeVar("Item", bound=Any)` is well defined – juanpa.arrivillaga Sep 26 '21 at 23:26
  • @juanpa.arrivillaga you only get the error if you run MyPy with the `--strict` setting (and you get it even if you don't specify `bound=Any` for the `TypeVar`, which I agree is pointless). https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=15af7faff026f30f18b411ac099ff478 – Alex Waygood Sep 26 '21 at 23:29
  • @AlexWaygood Im not sure it is *pointless* I think it is just *wrong*. It doesn't make any sense, and as far as I can tell, there is no documentation of how using `Any` in a bounded TypeVar should even work. – juanpa.arrivillaga Sep 26 '21 at 23:32
  • @juanpa.arrivillaga fair enough; I'm not arguing with you there; just pointing out that it doesn't appear to be the source of the error in the question – Alex Waygood Sep 26 '21 at 23:34
  • 1
    @AlexWaygood well, you get a totally different error if you remove it. – juanpa.arrivillaga Sep 26 '21 at 23:36
  • @juanpa.arrivillaga yes, that's a fair point! – Alex Waygood Sep 26 '21 at 23:36
  • Yea, I'm using `bound=Any` just so mypy will let me pass arguments to the class, otherwise it won't take even that. So yea, it does work, and it does help with the example I posted. – jpmelos Sep 27 '21 at 09:38
  • @joel If I'm parsing something like a CSV file, and instantiating classes from the data I read there (as if I was treating a CSV file as a database and my code is the ORM), how would you go about telling mypy that you don't know what is going to be passed to the class (given that you also don't know what the class is), if not via `**attrs: Any`? – jpmelos Sep 27 '21 at 09:50
  • It's difficult to answer without knowing more details, and I've never worked with ORMs, but I'd probably separate the concerns of sanitising the CSV and class construction (e.g. with a separate function). By sanitising the CSV you'd then be sure about the types – joel Sep 27 '21 at 10:11

1 Answers1

5

MyPy can sometimes be a bit funny about the types of classes. You can solve this by specifying _model as Callable[..., Item] (which, after all, isn't a lie) instead of Type[Item]:

from abc import ABC
from typing import Generic, TypeVar, Any, ClassVar, Callable

Item = TypeVar("Item")


class SomeClass(Generic[Item], ABC):
    _model: ClassVar[Callable[..., Item]]

    def _compose_item(self, **attrs: Any) -> Item:
        return self._model(**attrs)
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46