1

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.

4
  • It’s a bit unusual that the caller adjusts itself to how many arguments the callee can handle… Sounds like this could be designed better so this becomes a non-issue. Can you clarify the use case? Commented Jan 11, 2021 at 6:07
  • 1
    In Javascript, the caller always passes the same number of arguments; the callee simply doesn’t need to accept them explicitly. The callee still needs to conform to the interface the caller describes. And Python is more explicit, you will have to explicitly accept all arguments. Commented Jan 11, 2021 at 6:09
  • Also, to be more elegant, you can replace print(args[0], args[1], args[2]) with print(*args). Commented Jan 11, 2021 at 6:12
  • @deceze I'm trying to write a generic FileChooser popup in Kivy that returns the file name and possibly the file path. There are obviously ways to get around the multiple arguments in this use case, but I was mostly just curious how one might go about implementing it. Your explanation that JavaScript methods don't need to accept the arguments explicitly and that Python is more explicit makes a lot of sense. Thanks! Commented Jan 11, 2021 at 17:20

2 Answers 2

2

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)
Sign up to request clarification or add additional context in comments.

3 Comments

Your first method is much more Pythonic than my answer.
@Selcuk well, we came with almost the same answers :) At first I thought about named args too.
Thanks! Your code looks way better than how I was imagining. I was unfamiliar with the *_ trick, it looks really nice!
2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.