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.
5 Answers 5
(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).
3 Comments
new_callback = lambda *args: old_callback(SomeFunctionResult(*args)) and then call new_callback normally within the loop. But creating the SomeFunctionResult object in the loop directly seems like it should be fine anyway.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)
3 Comments
callback. Regarding your second comment, are you looking for the section on lambda expressions?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
7 Comments
try/catch this way - the official docs call this style "common", "clean and fast", so this is a losing battle, I'm afraid. There are far less justifiable uses in the Python standard library and popular third-party libraries, e.g. numpy.json module using except KeyError instead of using if to test whether a key is in a dictionary: github.com/python/cpython/blob/master/Lib/json/encoder.py#L48 if/else for normal control flow, but also coding style rules are never universal.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)
Comments
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
found_indexa keyword argument. As a more general note, it's not a great idea to try to replicate another language's patterns and idioms in Python. Write pythonic code.callback2accept two parameters, and simply ignore the second? You can either always call the callback in the same way, and let the callback decide what to do with its arguments, or you can add complicated introspection tosomefunctwhich which determines how it should treat the callback. The former seems much more maintainable to me.