Antyos
Antyos

Reputation: 545

Variable number of parameters in callback

I am trying to implement a callback system in Python that is similar to how JavaScript can have different numbers of parameters in its callbacks. Ideally, I want to achieve this without using *args or **kwargs in the parameters of my callbacks.

My Goal

What I want is something that looks roughly like this:

def callback1(val):
    print(val)

def callback2(x, y):
    print(x, y)

def callback3(a, b, c):
    print(a, b, c)

def foo(callback):
    callback(1, 2, 3) # Always has 3 arguments to pass

foo(callback1)  # Fails. Should print "1"
foo(callback2)  # Fails. Should print "1 2"
foo(callback3)  # Ok. Prints "1 2 3"

Perhaps a more verbose way of putting it would be:

# num_params() method isn't real (that I know of), but this is an
# inelegant example of how the callbacks might work if it were
def foo2(callback):
    if num_params(callback) == 1:
        callback(1)
    elif num_params(callback) == 2:
        callback(1, 2)
    elif num_params(callback) == 3:
        callback(1, 2, 3)

What I Don't Want

I don't want to use *args or **kwargs in each callback (unless this isn't possible any other way) like the following:

# This is just SO ugly
def callback1(*args):
    print(args[0])

def callback2(*args):
    print(args[0], args[1])

def callback3(*args):
    print(args[0], args[1], args[2])

JavaScript Equivalent

This is relatively common in JavaScript. For example, one can supply the callback of a .forEach() function with 1, 2, or 3 arguments:

let myArray = [1, 2, 3, 4]

// Valid
myArray.forEach((element) => {
   // Do stuff with the element only
});

// Valid
myArray.forEach((element, index) => {
   // Do stuff with the element AND the index
});

// Valid
myArray.forEach((element, index, array) => {
   // Do stuff with the element, index and the whole array
});

However, despite my best efforts in Google searching, I have no idea how to implement this in Python (or even in JavaScript for that matter, but that's beside the point; I hope this doesn't come back to bite me).

I would very much like to know if this is possible in Python and/or what the proper term is for this coding technique.

Upvotes: 1

Views: 1585

Answers (2)

Som-1
Som-1

Reputation: 661

What's wrong with args and kwargs? It is the pythonic way to do that. Python is not JavaScript. If you do not like accessing args by indexes like args[0], args[1], etc, you could just define some args like usual, and rest (unused args) - in *args:

def callback1(a, *args):
    print(a)

def callback2(a, b, *args):
    print(a, b)

def callback3(a, b, c):
    print(a, b, c)

Also you can unpack them in the function:

def callback1(*args):
    a, *rest = args
    print(a)

It makes it more verbose inside, but same definition for all callbacks.

Also it's common to name variables, you are not going to use with _ (underscore) instead of args, rest, etc.:

def callback1(a, *_):
    print(a)

def callback1(*args):
    a, *_ = args
    print(a)

Upvotes: 2

Selcuk
Selcuk

Reputation: 59184

You can define all your callback functions using the same number of arguments, i.e.:

def callback1(val, b=None, c=None):
    print(val)

def callback2(x, y, c=None):
    print(x, y)

def callback3(a, b, c):
    print(a, b, c)

Alternatively you can unpack *args within functions:

def callback1(*args):
    val, _, _ = args
    print(val)

def callback2(*args):
    x, y, _ = args
    print(x, y)

def callback3(*args):
    a, b, c = args
    print(a, b, c)

Finally, you can get creative using functools.partial.

Upvotes: 2

Related Questions