0

I have a function in Python that takes a callback. This callback may take an parameter, but also may not. I want to call it in a uniform way.

In the (almost) simplest case (not the real case but abstracting the case will simpler the question), it'll looks like:

def wait_and_pass_square(callback, parameter):
    from time import sleep

    sleep(1000)
    callback(parameter * parameter)
    # Here's the problem - the callback may have the form:
    # def callback(square):
    # or the form:
    # def callback():
    # (without argument).

Now, if I choose one of the forms in the caller, the second form will trigger TypeError.

I know that I can check for an exception and work-around it:

def wait_and_pass_square(callback, parameter):
    from time import sleep

    sleep(1000)
    try:
        callback(parameter * parameter)
    except TypeError:
        callback()

But I don't want to do it, because:

  • It's ugly.
  • It's become impossible when you have dozens of optional arguments (although that's not my problem, since my case is only one argument)
  • It easy to make mistakes: for example, if the callback may throw a TypeError exception, it'll be caught incorrectly.

Another thing I can do is to change the callback, either to:

def callback(square=None):

Or to:

def callback(*args):
    if 0 != len(args): square = args[0]
    else: square = None

But I don't want to change the callback (because as it can be much more challenging, updating the library is one-time action versus updating the client code) and sometimes I can't even touch the client code.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • From my understanding there is absolutely nothing wrong with using try/except - it's a concept in python. Beauty and uglyness are in the eye of the beholder... – Fab Aug 16 '19 at 13:49
  • @Fab Yeah? and if there are 10 parameters? Try to write the code... – Chayim Friedman Aug 18 '19 at 09:26

1 Answers1

0

I've found a solution, at least for place arguments (not keyword arguments).

The idea is to check how many arguments the callback receives, then pass it only these parameters.

So:

def callback_with_optional_arguments(callback):
        """Call a callback that may accept parameter(s) and may not."""

        from inspect import signature
        
        callback_args_count = len(signature(callback).parameters)
        def wrapper(*args):
                return callback(*args[:callback_args_count])
        return wrapper

Then:

def func(callback):
    callback = callback_with_optional_arguments(callback)

    callback(1, 2, 3, 4)

func(lambda a: print(a))
func(lambda a, b, c, d: print(a, b, c, d))

(How can I find the number of arguments of a Python function?).

Warning: This does not work for functions written in C (builtins and CPython extensions). I'm still looking for a better solution.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77