1

I have a function that receives a class and returns an instance of that class. A simple example:

def instantiate(class_: Type[enum.Enum], param: str) -> enum.Enum:
    return class_(param)

The return value is the same as the type of the parameter class_, so if class_ is MyEnum, the return value will be of type MyEnum.

Is there some way to declare that?

user972014
  • 3,296
  • 6
  • 49
  • 89
  • I think you want some kind of [generic](https://docs.python.org/3/library/typing.html#generics), but note that `class_` is a callable _returning_ an instance of the type, not the instance itself. – jonrsharpe Sep 06 '21 at 21:56
  • No, `class_` is not meant to be a callable returning the instance, it is meant to be the class - which is returning an instance, when called, ofc. – Jonathan Scholbach Sep 06 '21 at 21:57
  • @jonathan.scholbach look at the _usage_, it's being called and returning the value. Python is _duck typed_. – jonrsharpe Sep 06 '21 at 21:58
  • @jonrsharpe I guess, the use is wrong in the example. OP clearly states: "I have a function that receives a class and returns an instance of that class. " That is what they want to achieve, and their code example fails to do so - that would be my interpretation of the question. That interpretation is strongly supported by the variable naming (`class_`), as well. – Jonathan Scholbach Sep 06 '21 at 22:00
  • @jonathan.scholbach why would you guess the usage is wrong? There's nothing wrong with the OP's code. The point is that a class _is_ a callable retuning an instance. – jonrsharpe Sep 06 '21 at 22:03
  • The code is wrong insofar the type annotations are flawed. If `class_` is of type `enum.Enum`, the return value of `instantiate` is not going to be of type `enum.Enum`. – Jonathan Scholbach Sep 06 '21 at 22:05
  • @jonathan.scholbach and the types are what the OP is asking about, so it shouldn't be surprising that they're not currently right! And yes, the return type is the result of calling the argument, which is not the same as the type of the argument itself, which is what I said to start with. – jonrsharpe Sep 06 '21 at 22:07
  • 1
    The annotation is wrong, `class_: enum.Enum` should be `class_: typing.Type[enum.Enum]` and for the behavior the OP want they actually want to use a type variable – juanpa.arrivillaga Sep 06 '21 at 22:07
  • Yes, the annotation is wrong - and that is not surprising, since this is the core of the question. I also think, the user was aware that `class_` is not the instance - they just didn't know how to type that. I think, the rest of our discussion was a misunderstanding, which was in my case triggered by your hint to use a Generic, which is wrong imho. The answer is to use `typing.Type` to achieve what the user wants. – Jonathan Scholbach Sep 06 '21 at 22:14
  • @jonathan.scholbach I don't see why you'd think that hint was wrong; the accepted answer does use a generic, to express the relationship between parameter and return value. – jonrsharpe Sep 06 '21 at 22:37
  • edited the question to clarified the typing of the `class_` parameters (which is a class that inherits from enum.Enum, and not and instance of it) – user972014 Sep 06 '21 at 22:54

2 Answers2

6

There sure is! This should work perfectly for your use case.

from enum import Enum
from typing import TypeVar, Type


E = TypeVar('E', bound=Enum)


def instantiate(class_: Type[E], param: str) -> E:
    return class_(param)

And to just do a quick test of it to confirm that type hinting is working as intended:

class MyEnum(Enum):
    ONE = '1'
    TWO = '2'


# I hover over 'e' and PyCharm is able to infer it's of type MyEnum.
e = instantiate(MyEnum, '1')

print(e)
# MyEnum.ONE

Note: As mentioned in comments, in Python 3.9, some typing constructs like Type, List, and Dict are deprecated, as you can just use the builtin types. So the above annotation could be defined like type[E] if you have a newer Python version.

rv.kvetch
  • 9,940
  • 3
  • 24
  • 53
0

That is pretty standard. You would have found this in the documentation pretty top level:

from typing import Type


class MyClass:
    def __init__(self, param: str) -> None:
        self.param = param


def instantiate(cls: Type[MyClass], param: str) -> MyClass:
   return cls(param)
Jonathan Scholbach
  • 4,925
  • 3
  • 23
  • 44