2

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

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Can you please explain why you need the `[int]` part. It seems redundant and code cam be written without it. – Equinox Dec 12 '20 at 14:27
  • @venky__ I'm not sure I understand. The `int` is just an example. The publisher can be created with any type. Does this answer your question? – Craig Hickman Dec 12 '20 at 14:30
  • No I meant `Publisher[int](int, "chatter")` can be written as `Publisher (int, "chatter")`. If no then why? – Equinox Dec 12 '20 at 14:32
  • @venky__ I want to catch type errors before runtime using `mypy`. The `[int]` of `Publisher[int]` makes sure that from this point on, this publishers `publish` function can only be called with ints. Without this, I would have to do a runtme check in the publish function to check it has been called with the correct type. – Craig Hickman Dec 12 '20 at 14:34
  • Related: https://stackoverflow.com/questions/60985221/how-can-i-access-t-from-a-generict-instance-early-in-its-lifecycle – Craig Hickman Dec 12 '20 at 16:02

2 Answers2

3

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*]'
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
0

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())