Reputation: 45
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
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
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
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
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
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