pythonfunctionooptype-hintingconstructor-overloading

Python function overloading recommended approach


Assume a function that takes an object as parameter. There could be various ways to express the parameter object creation, some of which expressive, and likely easier to be used.

To give a simple example, we have a function which takes DateTime. We also want to accept string representations of DateTime, if possible (for example '20220606').

# version 1, strict. must send a DateTime  
def UsefulFunc(startdate: DateTime) -> None:
    pass

# version 2, allow more types, but loose on type hints
def UsefulFunc(startdate: (DateTime, str)) -> None:
    # check if type is str, convert to DateTime if yes
    pass

# version 3, multiple signatures to accept and call the base function 
def UsefulFuncString(startdatestr: str) -> None:
    # convert startdatestr to DateTime
    UsefulFunc(startdate)

# … …

What approach is recommended in Python (I come from C# background)? If there's no clear indication/ or decision is based on situation, what are the considerations?


Solution

  • After some research, and taking inspirations from the @Copperfield answer, I found an elegant solution to the problem.

    Let's first rephrase the problem - we have a function that takes an object. We want to provide some overloads, which will do the validations/ conversions etc. and call the base function which accepts object. We also need to reject any call not following any function signature which are not implemented.

    The library that I found very useful was multipledispatch. An easy example:

    from multipledispatch import dispatch
    
    @dispatch(int, int)
    def add_nums(num1: int, num2: int) -> int:
      return num1 + num2
    
    @dispatch(str, str)
    def add_nums(num1: str, num2: str) -> int:
      # do some useful validations/ object transformations
      # implement any intermediate logic before calling the base func
      # this enables base function do it's intended feature rather than    
      # implementing overloads 
      return add_nums(int(num1), int(num2))
    
    

    if we call add_nums(40, 15), we get 55 as the (int, int) version get called. add_nums('10', '15') get us 25 as expected as (str, str) version get called.

    It becomes very interesting when we call add_nuns(10, 10.0) as this will fail saying NotImplementedError: Could not find signature for add_nums: <int, float>. Essentially any call not in (int, int) or (str, str) format, fail with NotImplementedError exception.

    This is by far the closest behaviour of function overloading, when comparing with typed languages.

    The only concern I have - this library was last updated on Aug 9, 2018.