Fluffy Doggo
Fluffy Doggo

Reputation: 45

Calling function with dynamic args

I'm creating a function that takes in a callback function as an argument. I want to be able to use it like this:

def callback1(result, found_index):
    # do stuffs

def callback2(result):
    # do same stuffs even though it's missing the found_index parameter

somefunct(callback1)
somefunct(callback2)

# somefunct calls the callback function like this:
def somefunct(callback):

    # do stuffs, and assign result and found_index


    callback(result, found_index) # should not throw error

For context, I am somewhat trying to replicate how javascript's callback functions work for the .forEach function on arrays. You can make a function that takes in only the array item on that specific iteration, or the array item and index, or even the array item, index, and original array:

let some_array = ["apple", "orange", "banana"];

function callback1(value, index) {
    console.log(`Item at index ${index}: ${value}`);
}

function callback2(value) {
    console.log(`Value: ${value}`);
}

some_array.forEach(callback1); // runs with no errors
some_array.forEach(callback2); // runs with no errors

Furthermore, I don't want the callback function to force the * operator, but also allow them to use it if needed. Thank you, wonderful people of python.

Upvotes: 2

Views: 574

Answers (5)

kaya3
kaya3

Reputation: 51034

(Posting this separately since it's fundamentally different to my other answer.)

If you need to pass a lot of values to some callbacks, without requiring other callbacks to declare a lot of unused parameters, a neat solution is to encapsulate all of those values in a single object. You can use collections.namedtuple to define a value type with named attributes, and then the callback can take one parameter and decide which attributes to use.

from collections import namedtuple
SomeFunctionResult = namedtuple('SomeFunctionResult', 'foo bar baz qux quz')

def some_function(callback):
    result = SomeFunctionResult('foo', 'bar', 'baz', 'qux', 'quz')
    callback(result)

Example:

>>> some_function(lambda r: print(r.foo, r.bar))
foo bar
>>> some_function(lambda r: print(r.baz, r.qux, r.quz))
baz qux quz

The downside is that this makes some_function less usable with existing functions which might expect to receive foo directly, rather than an object with a foo attribute. In that case, you have to write some_function(lambda r: blah(r.foo)) which is not as neat as some_function(blah).

Upvotes: 1

AhmedMaher
AhmedMaher

Reputation: 1771

this is the modified python code snippet you have provided that produces error , this works with no problem , you just have to unify the callback arguments number and type for each callback function called within the main function and define somefunc before calling it .

def callback1(result, found_index):
    # do stuffs 
    result="overridden result in callback 1"
    found_index ="overridden found_index in callback 1"
    print(result,found_index)

def callback2(result,found_index):
    # do same stuffs even though it's missing the found_index parameter
    result="overridden result in callback 2"
    print(result,found_index)

    # somefunct calls the callback function like this:
def somefunct(callback):
    # do stuffs, and assign result and found_index
    result = "overridden result in somefunct"
    found_index = "overridden index in somefunct"
    callback(result, found_index) # NOW it should not throw error as the callback is fed with the 2 arguments used in callback1 and ignored in callback2

somefunct(callback1)
somefunct(callback2)

Upvotes: 0

Brian61354270
Brian61354270

Reputation: 14423

The simplest approach would be to unify the signatures of your callbacks. Let's say you defined your forEach function as follows

def forEach(iterable, callback):
    for index, elem in enumerate(iterable):
        callback(elem, index)

You could then define Python analogs of the callack1 and callback2 Javascript functions as

def callback1(value, index):
    print(f"Item at index {index}: {value}")

def callback2(value, _index):
    print(f"Value: {value})

Rather than performing any complicated parameter-count-reasoning, exception handling, or dynamic dispatch within forEach, we delegate the decision of how to handle the value and index arguments to the callbacks themselves. If you need to adapt a single-parameter callback to work with forEach, you could simply use a wrapper lambda that discards the second argument:

forEach(some_iterable, lambda value, _index: callback(value))

However, at this point, you just have an obfuscated for loop, which would be much more cleanly expressed as

for elem in some_iterable:
    callback(elem)

Upvotes: 1

kaya3
kaya3

Reputation: 51034

In this case, it is easier to ask for forgiveness than permission.

def some_function(callback):
    result = 'foo'
    found_index = 5
    try:
        callback(result, found_index)
    except TypeError:
        callback(result)

Example:

>>> some_function(print)
foo 5
>>> some_function(lambda x: print(x))
foo

Upvotes: 0

yehezkel horoviz
yehezkel horoviz

Reputation: 208

use optional arguments and check how much elemnts returned, sort of switch case: https://linux.die.net/diveintopython/html/power_of_introspection/optional_arguments.html

Upvotes: -1

Related Questions