Home > database >  Type hint nominal inheritence bug Expected type 'Tuple[A]', got 'Tuple[B, ...]'
Type hint nominal inheritence bug Expected type 'Tuple[A]', got 'Tuple[B, ...]'

Time:10-14

Consider the following :

from typing import Tuple


class A(object):
    def __init__(self):
        pass
    
    
class B(A):
    def __init__(self):
        super(B, self).__init__()


def foo() -> Tuple[A]:
    return tuple([B()])


foo()

PyCharm gives a warning:

Expected type 'Tuple[A]', got 'Tuple[B, ...]' instead

on tuple([B()]).

Since B "is a" A, this is annoying and wrong.

How to best deal with this?

CodePudding user response:

Look at your return statement:

return tuple([B()])

Let's unpack that a bit.

B() has type B, of course.

[B()] is a list, with a single element of type B. The static type of this list is deduced as list[B], a list of B instances.

Now look at tuple([B()]). You're calling tuple with a single argument of static type list[B]. If you call tuple with an argument of static type list[B], the static type of the result is tuple[B, ...].

It's not tuple[A], or even tuple[B]. The static type of [B()] doesn't contain any length information, so the type of the tuple doesn't contain any length information either. It's just a tuple containing some number of B instances.


The problem is, you went through a list. You need to not do that. Make a tuple directly:

return (B(),)

Then static analyzers can directly see that this is a tuple with a single element of type B, in a context where a tuple[A] is expected, and deduce that the tuple can and should be treated as a tuple[A].

CodePudding user response:

How to best deal with this?

The easiest way is returning a literal declaration instead of using a constructor (the syntax is also more concise), if you write the return as:

def foo() -> Tuple[A]:
    return B(),

But you can also use the constructor provided it's initialized from a single element fixed length collection, the PyCharm linter won't complaint, for example:

def foo() -> Tuple[A]:
    return tuple((B(),))

Looking carefully at the error message:

got 'Tuple[B, ...]'

The ellipsis ... here implies you are returning an arbitrary length tuple (the static type checker infers this because you initialize the tuple using a list), while the type hint -> Tuple[A] requires a fixed length single element tuple, see:

PEP 484

  • Tuple, used by listing the element types, for example Tuple[int, int, str]. The empty tuple can be typed as Tuple[()]. Arbitrary-length homogeneous tuples can be expressed using one type and ellipsis, for example Tuple[int, ...]. (The ... here are part of the syntax, a literal ellipsis.)

I tested this both with Python 3.8 and Python 3.9 (notice that's the version when typing.Tuple was deprecated in favor of using parametrized builtins for type hints.)

  • Related