[Answered] Typehints for functions that have variable signatures - eviltoast

I know what I am asking is rather niche, but it has been bugging me for quite a while. Suppose I have the following function:

def foo(return_more: bool):
   ....
    if return_more:
        return data, more_data
   return data

You can imagine it is a function that may return more data if given a flag.

How should I typehint this function? When I use the function in both ways

data = foo(False)

data, more_data = foo(True)

either the first or the 2nd statement would say that the function cannot be assigned due to wrong size of return tuple.

Is having variable signature an anti-pattern? Is Python’s typehinting mechanism not powerful enough and thus I am forced to ignore this error?

Edit: Thanks for all the suggestions. I was enlightened by this suggestion about the existence of overload and this solution fit my requirements perfectly

from typing import overload, Literal

@overload
def foo(return_more: Literal[False]) -> Data: ...

@overload
def foo(return_more: Literal[True]) -> tuple[Data, OtherData]: ...

def foo(return_more: bool) -> Data | tuple[Data, OtherData]:
   ....
    if return_more:
        return data, more_data
   return data

a = foo(False)
a,b = foo(True)
a,b = foo(False) # correctly identified as illegal
    • 0WN3D@lemmy.cafeOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      edit-2
      1 year ago

      yea, this is pretty close to what I’m looking for.

      The only missing piece is the ability to define the overload methods on the bool

      something like

      @overload
      def foo(return_more: True) -> (Data, Data)
      
      @overload
      def foo(return_more: False) -> Data
      

      But I don’t think such constructs are possible? I know it is possible in Typescript to define the types using constants, but I don’t suppose Python allows for this?

      EDIT: At first, when I tried the above, the typechecker said Literal[True] was not expected and I thought it was not possible. But after experimenting some, I figured out that it is actually possible. Added my solution to the OP

      Thanks for the tip!

    • eternacht@programming.dev
      link
      fedilink
      arrow-up
      1
      ·
      1 year ago

      This is the real answer, overloads are meant for exactly this purpose.

      It’ll be something like this:

      from typing import Literal, overload
      
      @overload
      def foo() -> Data: …
      @overload
      def foo(return_more: Literal[True]) -> tuple[Data, Data]: …
      def foo(return_more: bool = False) -> Data | tuple[Data, Data]
          ...
          if return_more:
              return data, more_data
         return data