Search code examples
python-3.xoverloadinggeneric-functionsingle-dispatch

singledispatch on unpacked multiple arguments


I have a fancyfunction defined to do something to a single argument. I decorate it to become a generic function so that it knows what to do if it is given a tuple.

from functools import singledispatch

@singledispatch
def fancyfunction(arg):
    print(arg)

@fancyfunction.register
def _(arg: tuple):
    for a in arg:
        fancyfunction(a)

Sure enough the valid call signatures for the two functions above are:

fancyfunction(foo)
fancyfunction((foo, bar, ...))

What I want to do

I want to simplify the call signatures so that I don't need the extra pair of parentheses:

fancyfunction(foo)
fancyfunction(foo, bar, baz, ...)

In order to do that, I need the overloaded function to unpack its positional arguments:

@singledispatch
def fancyfunction(arg):
    print(arg)

@fancyfunction.register
def _(*arg):
    for a in arg:
        fancyfunction(a)

Of course the last code snippet above doesn't work. Doing this:

fancyfunction(foo, bar, baz)

would call the generic function instead of the overloaded function.

Is it possible to make singledispatch recognize that the decorated function is called with *-form of arguments? P.S. What is the official name of that kind of call signature?


Solution

  • It's a bit confusing - you say you want to "tell" the function what to do when first argument is tuple, yet you don't want to pass tuple (i.e. you don't want the brackets). it looks it's a third case - you want to pass tuple, but also to be able to pass multiple arguments.

    maybe something along these lines

    from functools import singledispatch
    
    @singledispatch
    def fancyfunction(arg, *args):
        print(arg)
        for a in args:
            print(a)
    
    @fancyfunction.register
    def _(arg: tuple, *args):
        fancyfunction(*(*arg, *args))
    

    another option is to have a helperfunction

    from functools import singledispatch
    
    def fancyfunction(*args):
        for a in args:
            helperfunction(a)
    
    @singledispatch
    def helperfunction(arg):
        print(arg)
    
    @helperfunction.register
    def _(arg: tuple):
        for a in arg:
            helperfunction(a)
    

    in both cases calling like this

    fancyfunction(1)
    fancyfunction(2, 3)
    fancyfunction((4,5))
    fancyfunction((6, 7) ,8, 9)
    fancyfunction((10, 11), (12, 13))
    fancyfunction([14, 15])
    

    will yield following output

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (12, 13)
    [14, 15]
    >>>