Craig Hickman
Craig Hickman

Reputation: 51

Python Typing : Create a class function with a generic type, and also access that type through the class

I have a publisher that can publish messages of a certain type:

T = TypeVar('T')
class Publisher(Generic[T]):

    def __init__(self, topic: str) -> None:
        self.__topic = topic

    def publish(self, msg: T):
        pass

# As an example, create a publisher that can publish ints
p = Publisher[int]("chatter")
p.publish(1)

This works, and the publish function has the correct type hint, but I want to be able to access the type of the publisher with a get_type() function.

A simple way to do this is to pass the message type into the constructor:

T = TypeVar('T')
class Publisher(Generic[T]):

    def __init__(self, msg_type: type, topic: str) -> None:
        self.__msg_type = msg_type
        self.__topic = topic

    def publish(self, msg: T):
        pass

    def get_type(self) -> type:
        return self.__msg_type

p = Publisher[int](int, "chatter")
p.publish(1)

But this requires writing int twice in the line p = Publisher[int](int, "chatter") which seems a bit clumsy and redundant.

I tried wrapping the creation of the publisher in a function so that you don't have to write int twice, but I get a problem:

T = TypeVar('T', bound=type)
class Publisher(Generic[T]):

    def __init__(self, msg_type: type, topic: str) -> None:
        self.__msg_type = msg_type
        self.__topic = topic

    def publish(self, msg: T):
        pass

    def get_type(self) -> type:
        return self.__msg_type

def create_publisher(msg_type: T, topic: str) -> Publisher[T]:
    return Publisher[T](msg_type, topic)

p = create_publisher(int, "hello")

p.publish(1) #Fails because its expecting Type[int], not an instance of an int

So what I need, is a method to get convert Type[x] into x in a typehint context. Essentially the inverse of what Type does.

e.g, the last example would become:

T = TypeVar('T', bound=type)
class Publisher(Generic[T]):

    def __init__(self, msg_type: type, topic: str) -> None:
        self.__msg_type = msg_type
        self.__topic = topic

    def publish(self, msg: InstanceOf[T]):
        pass

    def get_type(self) -> type:
        return self.__msg_type

def create_publisher(msg_type: T, topic: str) -> Publisher[T]:
    return Publisher[T](msg_type, topic)

p = create_publisher(int, "hello")

p.publish(1)

But I do not know how to make the InstanceOf generic.

Is there anyway I can do this? Or any other way to get the functionality I want without having to write int twice in the line p = Publisher[int](int, "chatter")

edit

Here is another attempt that also doesn't work, but should clarify what I'm trying to do:

T = TypeVar('T')
class Publisher(Generic[T]):

    def __init__(self, topic: str) -> None:
        self.__topic = topic

    def publish(self, msg: T):
        pass

    def get_type(self) -> type:
        return get_args(Publisher[T])[0]

#This works
print(get_args(Publisher[int])[0])

#This doesn't
p = Publisher[int]("hello")
print(p.get_type())

In this example p.get_type() returns ~T instead of int

Upvotes: 2

Views: 1484

Answers (2)

MisterMiyagi
MisterMiyagi

Reputation: 52139

Pass in the type explicitly, but annotate it as Type[T]. This allows inference of T without having to specify it, making it enough to specify the type only once (as the argument).

class Publisher(Generic[T]):
    # knowing `msg_type` defines `T`
    def __init__(self, msg_type: Type[T], topic: str) -> None:
        self._msg_type = msg_type
        self._topic = topic

    def publish(self, msg: T):
        pass

    def get_type(self) -> Type[T]:
        return self._msg_type


# argument of `int` implies T = int
p = Publisher(int, "hello")
print(p.get_type())  # <class 'int'>
if TYPE_CHECKING:
    reveal_type(p)   # note: Revealed type is 'aaa_testbed.Publisher[builtins.int*]'

Upvotes: 3

Craig Hickman
Craig Hickman

Reputation: 51

I have an answer, based on this answer, but it depends on undocumented implementation details.

from typing import TypeVar, Generic, get_args, get_origin

T = TypeVar('T')

class Publisher(Generic[T]):

    def __init__(self, topic: str) -> None:
        self.__topic = topic

    def publish(self, msg: T):
        pass

    def get_type(self) -> type:
        return get_args(self.__orig_class__)[0]

p = Publisher[int]("hello")
print(p.get_type())

Upvotes: 0

Related Questions