Search code examples
pythonpython-typing

How do I initialize the base `tuple` when deriving from `typing.Tuple`?


According to the docs, "built-in generic types .. are valid both as types and as base classes." It gives an example deriving from Dict[str, List[Node]].

However, when I try this with Tuple[int, int], the constructor doesn't forward properly. For example:

from typing import Tuple

class MyTuple(Tuple[int, int]):
    def hello(self):
        print("hello: " + str(len(self)))

MyTuple([1, 2]).hello()

prints hello: 0. If I derive from the builtin tuple instead, it prints hello: 2.

How can I initialize the base tuple object when deriving from Tuple[int, int]?


Solution

  • Types in the typing module are not really meant to be directly instantiated: they're supposed to be used as static type hints in annotations.

    But if you really need to, you can override __new__() to return a tuple:

    class MyTuple(Tuple[int, int]):
    
        def __new__(cls, *args, **kwargs):
            return tuple(*args, **kwargs)
    
    print(MyTuple('abc'))  # -> ('a', 'b', 'c')
    

    This will create plain tuple objects every time you call MyTuple(). If instead you want the objects to be instances of your class:

    class MyTuple(Tuple[int, int]):
    
        def __new__(cls, *args, **kwargs):
            return tuple.__new__(cls, *args, **kwargs)
    
    print(MyTuple('abc'))  # -> ('a', 'b', 'c')
    print(type(MyTuple('abc')))  # -> __main__.MyTuple
    

    This works because typing.Tuple is a subclass of tuple.

    Note that calling super().__new__() would not work, because typing.Tuple (and other generic types) discard the arguments that you pass to them. You have to "short-circuit" by calling tuple.__new__() directly.